1
0
Fork 0

Merge pull request #2571 from pixelfed/staging

v0.10.10
This commit is contained in:
daniel 2021-01-28 21:36:08 -07:00 committed by GitHub
commit 4deec1f6c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 15160 additions and 1155 deletions

View File

@ -1,16 +1,19 @@
# Release Notes
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.9...dev)
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.10...dev)
### Added
## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10)
### Added
- Direct Messages ([d63569c](https://github.com/pixelfed/pixelfed/commit/d63569c))
- ActivityPubFetchService for signed GET requests ([8763bfc5](https://github.com/pixelfed/pixelfed/commit/8763bfc5))
- ActivityPubFetchService for signed GET requests ([8763bfc5](https://github.com/pixelfed/pixelfed/commit/8763bfc5)) ([3ee1215a](https://github.com/pixelfed/pixelfed/commit/3ee1215a))
- Custom content warnings for remote posts ([6afc61a4](https://github.com/pixelfed/pixelfed/commit/6afc61a4))
- Thai translations ([74cd536](https://github.com/pixelfed/pixelfed/commit/74cd536))
- Added Bookmarks to v1 api ([99cb48c5](https://github.com/pixelfed/pixelfed/commit/99cb48c5))
- Added New Post notification to Timeline ([a0e7c4d5](https://github.com/pixelfed/pixelfed/commit/a0e7c4d5))
- Add Instagram Import ([e2a6bdd0](https://github.com/pixelfed/pixelfed/commit/e2a6bdd0))
- Add notification preview to NotificationCard ([28445e27](https://github.com/pixelfed/pixelfed/commit/28445e27))
- Add Grid Mode to Timelines ([c1853ca8](https://github.com/pixelfed/pixelfed/commit/c1853ca8))
- Add MediaPathService ([c54b29c5](https://github.com/pixelfed/pixelfed/commit/c54b29c5))
- Add Media Tags ([711fc020](https://github.com/pixelfed/pixelfed/commit/711fc020))
- Add MediaTagService ([524c6d45](https://github.com/pixelfed/pixelfed/commit/524c6d45))
@ -24,6 +27,7 @@
- Add autospam feature ([b892bcf0](https://github.com/pixelfed/pixelfed/commit/b892bcf0))
- Add hCaptcha ([082c1ccb](https://github.com/pixelfed/pixelfed/commit/082c1ccb))
- Add StatusView model to store views for discover algorithm ([7a68ee94](https://github.com/pixelfed/pixelfed/commit/7a68ee94))
- Add Year in Review feature (mysql only) ([f32072a3](https://github.com/pixelfed/pixelfed/commit/f32072a3))
### Updated
- Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc))
@ -145,8 +149,8 @@
- Updated avatars, use jpeg default. ([f6528c84](https://github.com/pixelfed/pixelfed/commit/f6528c84))
- Updated antispam bouncer, change recent from 1 week to 3 months. ([7d818197](https://github.com/pixelfed/pixelfed/commit/7d818197))
- Updated Post components, fix remote post and profile urls. ([cfcf17f3](https://github.com/pixelfed/pixelfed/commit/cfcf17f3))
- Update migrations, fix broken oauth change. ([4a885c88](https://github.com/pixelfed/pixelfed/commit/4a885c88))
- Update LikeController, store status_profile_id and is_comment attributes. ([799a4cba](https://github.com/pixelfed/pixelfed/commit/799a4cba))
- Updated migrations, fix broken oauth change. ([4a885c88](https://github.com/pixelfed/pixelfed/commit/4a885c88))
- Updated LikeController, store status_profile_id and is_comment attributes. ([799a4cba](https://github.com/pixelfed/pixelfed/commit/799a4cba))
- Updated Profile, fix status count. ([6dcd472b](https://github.com/pixelfed/pixelfed/commit/6dcd472b))
- Updated StatusService, cast response to array. ([0fbde91e](https://github.com/pixelfed/pixelfed/commit/0fbde91e))
- Updated status model, use scope over deprecated visibility attribute. ([f70826e1](https://github.com/pixelfed/pixelfed/commit/f70826e1))
@ -155,7 +159,44 @@
- Updated AP helpers, fixed federation bug. ([a52564f3](https://github.com/pixelfed/pixelfed/commit/a52564f3))
- Updated Helpers, cache profiles. ([1f672ecf](https://github.com/pixelfed/pixelfed/commit/1f672ecf))
- Updated DiscoverController, improve trending api performance. ([d8d3331f](https://github.com/pixelfed/pixelfed/commit/d8d3331f))
- Update InboxWorker, fix race condition in account deletes. ([4a4d8f00](https://github.com/pixelfed/pixelfed/commit/4a4d8f00))
- Updated InboxWorker, fix race condition in account deletes. ([4a4d8f00](https://github.com/pixelfed/pixelfed/commit/4a4d8f00))
- Updated StoryItemTransformer, increase story duration from 5 seconds to 10 seconds. ([5b0b14fc](https://github.com/pixelfed/pixelfed/commit/5b0b14fc))
- Updated StatusController, add view method. ([0cfc12c5](https://github.com/pixelfed/pixelfed/commit/0cfc12c5))
- Updated MediaPathService, add story method. ([aac44309](https://github.com/pixelfed/pixelfed/commit/aac44309))
- Updated StatusDelete job, handle cloud storage media deletes. ([4b1a0fd7](https://github.com/pixelfed/pixelfed/commit/4b1a0fd7))
- Updated ImageOptimizePipeline, add skip_optimize and MediaStorageService support. ([234f72f3](https://github.com/pixelfed/pixelfed/commit/234f72f3))
- Updated Media model, add cdn support to url and thumbnailUrl methods. ([57fa889d](https://github.com/pixelfed/pixelfed/commit/57fa889d))
- Updated MediaController, remove deprecated endpoint. ([8132db74](https://github.com/pixelfed/pixelfed/commit/8132db74))
- Updated api controllers, deprecate old endpoints. ([4415af1b](https://github.com/pixelfed/pixelfed/commit/4415af1b))
- Updated mobile apis, add blurhash. ([cf40526e](https://github.com/pixelfed/pixelfed/commit/cf40526e))
- Updated Image media util, store dimensions of media not thumbnail. ([40bd64aa](https://github.com/pixelfed/pixelfed/commit/40bd64aa))
- Updated MediaTransformers, include meta attribute with focus and dimensions. ([f8cbe1e4](https://github.com/pixelfed/pixelfed/commit/f8cbe1e4))
- Updated storage, add remote media cache directory. ([0eabbfdd](https://github.com/pixelfed/pixelfed/commit/0eabbfdd))
- Updated backup config, prevents gateway timeouts for large databases using mysql. ([9cd4bd74](https://github.com/pixelfed/pixelfed/commit/9cd4bd74))
- Updated MediaPipeline, handle cloud object storage. ([be6d12fc](https://github.com/pixelfed/pixelfed/commit/be6d12fc))
- Updated AP Helpers, use MediaStoragePipeline. ([01a1ffd6](https://github.com/pixelfed/pixelfed/commit/01a1ffd6))
- Updated RemoteProfile component, change thumbnail url. ([c1118956](https://github.com/pixelfed/pixelfed/commit/c1118956))
- Updated blade views. ([9683e846](https://github.com/pixelfed/pixelfed/commit/9683e846))
- Updated cache config, use phpredis by default. ([ed6877df](https://github.com/pixelfed/pixelfed/commit/ed6877df))
- Updated components, fix url rewriter. Closes #2538. ([e8cc66dc](https://github.com/pixelfed/pixelfed/commit/e8cc66dc))
- Updated UserCreate command, closes #2581. ([b2b8c9f9](https://github.com/pixelfed/pixelfed/commit/b2b8c9f9))
- Updated AvatarController, remove deprecated thumb_path. ([889c3d87](https://github.com/pixelfed/pixelfed/commit/889c3d87))
- Updated VideoThumbnail, add MediaStoragePipeline. ([98c44f7b](https://github.com/pixelfed/pixelfed/commit/98c44f7b))
- Updated StatusDelete pipeline, fix object storage thumbnail deletion. ([f930c4bd](https://github.com/pixelfed/pixelfed/commit/f930c4bd))
- Updated MediaStorageService, clear transformer cache after storing media. ([ce6ab80d](https://github.com/pixelfed/pixelfed/commit/ce6ab80d))
- Updated MediaTransformer, remove cache busting. ([258b2729](https://github.com/pixelfed/pixelfed/commit/258b2729))
- Updated AP helpers, only run MediaStoragePipeline if using cloud storage. ([77f21b4b](https://github.com/pixelfed/pixelfed/commit/77f21b4b))
- Updated AvatarObserver, add logic to delete avatars stored in S3. ([9eafc31e](https://github.com/pixelfed/pixelfed/commit/9eafc31e))
- Updated Profile model, use cdn_url for avatars. ([ea8e4261](https://github.com/pixelfed/pixelfed/commit/ea8e4261))
- Updated ActivityPubFetchService, add url validation. ([654b08d3](https://github.com/pixelfed/pixelfed/commit/654b08d3))
- Updated MediaStorageService, add avatar method. ([94a9f685](https://github.com/pixelfed/pixelfed/commit/94a9f685))
- Updated AvatarPipeline, add remote avatar fetch. ([4c148055](https://github.com/pixelfed/pixelfed/commit/4c148055))
- Updated ComposeController, update media version. ([cc2d4bf8](https://github.com/pixelfed/pixelfed/commit/cc2d4bf8))
- Updated AP Helpers, add blurhash and RemoteAvatarFetch. ([de8828e8](https://github.com/pixelfed/pixelfed/commit/de8828e8))
- Updated Timeline, prevent nextTick() when reloading same comment modal. Fixes #2584. ([cc84125b](https://github.com/pixelfed/pixelfed/commit/cc84125b))
- Updated site config, add labels to config. ([abe9cb3d](https://github.com/pixelfed/pixelfed/commit/abe9cb3d))
- Update StatusLabelService, change config key. ([4abfe76a](https://github.com/pixelfed/pixelfed/commit/4abfe76a))
## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9)
### Added
@ -213,7 +254,7 @@
- Updated StatusTransformer, fixes #[2113](https://github.com/pixelfed/pixelfed/issues/2113) ([eefa6e0d](https://github.com/pixelfed/pixelfed/commit/eefa6e0d))
- Updated InternalApiController, limit remote profile ui to remote profiles ([d918a68e](https://github.com/pixelfed/pixelfed/commit/d918a68e))
- Updated NotificationCard, fix pagination bug #[2019](https://github.com/pixelfed/pixelfed/issues/2019) ([32beaad5](https://github.com/pixelfed/pixelfed/commit/32beaad5))
-
## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8)
### Added

View File

@ -14,9 +14,21 @@ class Avatar extends Model
*
* @var array
*/
protected $dates = ['deleted_at'];
protected $dates = [
'deleted_at',
'last_fetched_at',
'last_processed_at'
];
protected $fillable = ['profile_id'];
protected $visible = [
'id',
'profile_id',
'media_path',
'size',
];
public function profile()
{
return $this->belongsTo(Profile::class);

View File

@ -12,7 +12,7 @@ class UserCreate extends Command
*
* @var string
*/
protected $signature = 'user:create';
protected $signature = 'user:create {--name=} {--username=} {--email=} {--password=} {--is_admin=0} {--confirm_email=0}';
/**
* The console command description.
@ -40,6 +40,26 @@ class UserCreate extends Command
{
$this->info('Creating a new user...');
$o = $this->options();
if( $o['name'] &&
$o['username'] &&
$o['email'] &&
$o['password']
) {
$user = new User;
$user->username = $o['username'];
$user->name = $o['name'];
$user->email = $o['email'];
$user->password = bcrypt($o['password']);
$user->is_admin = (bool) $o['is_admin'];
$user->email_verified_at = (bool) $o['confirm_email'] ? now() : null;
$user->save();
$this->info('Successfully created user!');
return;
}
$name = $this->ask('Name');
$username = $this->ask('Username');

View File

@ -139,6 +139,9 @@ class AdminController extends Controller
$appeal->appeal_handled_at = now();
$appeal->save();
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $status->profile_id);
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $status->profile_id);
return redirect('/i/admin/reports/autospam');
}
@ -151,6 +154,9 @@ class AdminController extends Controller
$appeal->appeal_handled_at = now();
$appeal->save();
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $status->profile_id);
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $status->profile_id);
return redirect('/i/admin/reports/autospam');
}

View File

@ -53,7 +53,6 @@ use App\Services\{
MediaBlocklistService
};
class ApiV1Controller extends Controller
{
protected $fractal;
@ -98,6 +97,7 @@ class ApiV1Controller extends Controller
'client_secret' => $client->secret,
'vapid_key' => null
];
return response()->json($res, 200, [
'Access-Control-Allow-Origin' => '*'
]);
@ -113,14 +113,18 @@ class ApiV1Controller extends Controller
{
abort_if(!$request->user(), 403);
$id = $request->user()->id;
$key = 'user:last_active_at:id:'.$id;
$ttl = now()->addMinutes(5);
Cache::remember($key, $ttl, function() use($id) {
$user = User::findOrFail($id);
$user->last_active_at = now();
$user->save();
return;
});
if($request->user()->last_active_at) {
$key = 'user:last_active_at:id:'.$id;
$ttl = now()->addMinutes(5);
Cache::remember($key, $ttl, function() use($id) {
$user = User::findOrFail($id);
$user->last_active_at = now();
$user->save();
return;
});
}
$profile = Profile::whereNull('status')->whereUserId($id)->firstOrFail();
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray();
@ -1031,6 +1035,11 @@ class ApiV1Controller extends Controller
]);
$user = $request->user();
if($user->last_active_at == null) {
return [];
}
$profile = $user->profile;
if(config('pixelfed.enforce_account_limit') == true) {
@ -1087,8 +1096,8 @@ class ApiV1Controller extends Controller
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
$res = $this->fractal->createData($resource)->toArray();
$res['preview_url'] = url('/storage/no-preview.png');
$res['url'] = url('/storage/no-preview.png');
$res['preview_url'] = $media->url(). '?cb=1&_v=' . time();
$res['url'] = $media->url(). '?cb=1&_v=' . time();
return response()->json($res);
}
@ -1322,13 +1331,15 @@ class ApiV1Controller extends Controller
$limit = $request->input('limit') ?? 3;
$user = $request->user();
$key = 'user:last_active_at:id:'.$user->id;
$ttl = now()->addMinutes(5);
Cache::remember($key, $ttl, function() use($user) {
$user->last_active_at = now();
$user->save();
return;
});
if($user->last_active_at) {
$key = 'user:last_active_at:id:'.$user->id;
$ttl = now()->addMinutes(5);
Cache::remember($key, $ttl, function() use($user) {
$user->last_active_at = now();
$user->save();
return;
});
}
$pid = $request->user()->profile_id;
@ -1739,6 +1750,10 @@ class ApiV1Controller extends Controller
$in_reply_to_id = $request->input('in_reply_to_id');
$user = $request->user();
if($user->last_active_at == null) {
return [];
}
if($in_reply_to_id) {
$parent = Status::findOrFail($in_reply_to_id);
@ -1752,6 +1767,13 @@ class ApiV1Controller extends Controller
$status->in_reply_to_profile_id = $parent->profile_id;
$status->save();
} else if($ids) {
if(Media::whereUserId($user->id)
->whereNull('status_id')
->find($ids)
->count() == 0
) {
abort(400, 'Invalid media_ids');
}
$status = new Status;
$status->caption = strip_tags($request->input('status'));
$status->profile_id = $user->profile_id;
@ -1765,7 +1787,7 @@ class ApiV1Controller extends Controller
if($k + 1 > config('pixelfed.max_album_length')) {
continue;
}
$m = Media::findOrFail($v);
$m = Media::whereUserId($user->id)->whereNull('status_id')->findOrFail($v);
if($m->profile_id !== $user->profile_id || $m->status_id) {
abort(403, 'Invalid media id');
}
@ -1776,7 +1798,7 @@ class ApiV1Controller extends Controller
if(empty($mimes)) {
$status->delete();
abort(500, 'Invalid media ids');
abort(400, 'Invalid media ids');
}
$status->scope = $request->input('visibility', 'public');
@ -1786,8 +1808,7 @@ class ApiV1Controller extends Controller
}
if(!$status) {
$oops = 'An error occured. RefId: '.time().'-'.$user->profile_id.':'.Str::random(5).':'.Str::random(10);
abort(500, $oops);
abort(500, 'An error occured.');
}
NewStatusPipeline::dispatch($status);

View File

@ -183,7 +183,6 @@ class BaseApiController extends Controller
$avatar = Avatar::whereProfileId($profile->id)->firstOrFail();
$opath = $avatar->media_path;
$avatar->media_path = "$public/$name";
$avatar->thumb_path = null;
$avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = null;
$avatar->save();
@ -201,117 +200,17 @@ class BaseApiController extends Controller
public function showTempMedia(Request $request, $profileId, $mediaId, $timestamp)
{
abort_if(!$request->user(), 403);
abort_if(!$request->hasValidSignature(), 404);
abort_if(Auth::user()->profile_id != $profileId, 404);
$media = Media::whereProfileId(Auth::user()->profile_id)->findOrFail($mediaId);
$path = storage_path('app/'.$media->media_path);
return response()->file($path);
abort(400, 'Endpoint deprecated');
}
public function uploadMedia(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'file.*' => function() {
return [
'required',
'mimes:' . config('pixelfed.media_types'),
'max:' . config('pixelfed.max_photo_size'),
];
},
'filter_name' => 'nullable|string|max:24',
'filter_class' => 'nullable|alpha_dash|max:24'
]);
$user = Auth::user();
$profile = $user->profile;
if(config('pixelfed.enforce_account_limit') == true) {
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
return Media::whereUserId($user->id)->sum('size') / 1000;
});
$limit = (int) config('pixelfed.max_account_size');
if ($size >= $limit) {
abort(403, 'Account size limit reached.');
}
}
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
$photo = $request->file('file');
$mimes = explode(',', config('pixelfed.media_types'));
if(in_array($photo->getMimeType(), $mimes) == false) {
return;
}
$storagePath = MediaPathService::get($user, 2);
$path = $photo->store($storagePath);
$hash = \hash_file('sha256', $photo);
abort_if(MediaBlocklistService::exists($hash) == true, 451);
$media = new Media();
$media->status_id = null;
$media->profile_id = $profile->id;
$media->user_id = $user->id;
$media->media_path = $path;
$media->original_sha256 = $hash;
$media->size = $photo->getSize();
$media->mime = $photo->getMimeType();
$media->filter_class = $filterClass;
$media->filter_name = $filterName;
$media->save();
$url = URL::temporarySignedRoute(
'temp-media', now()->addHours(1), ['profileId' => $profile->id, 'mediaId' => $media->id, 'timestamp' => time()]
);
switch ($media->mime) {
case 'image/jpeg':
case 'image/png':
ImageOptimize::dispatch($media);
break;
case 'video/mp4':
VideoThumbnail::dispatch($media);
$preview_url = '/storage/no-preview.png';
$url = '/storage/no-preview.png';
break;
default:
break;
}
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
$res = $this->fractal->createData($resource)->toArray();
$res['preview_url'] = $url;
$res['url'] = $url;
return response()->json($res);
abort(400, 'Endpoint deprecated');
}
public function deleteMedia(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'id' => 'required|integer|min:1|exists:media,id'
]);
$media = Media::whereNull('status_id')
->whereUserId(Auth::id())
->findOrFail($request->input('id'));
Storage::delete($media->media_path);
Storage::delete($media->thumbnail_path);
$media->forceDelete();
return response()->json([
'msg' => 'Successfully deleted',
'code' => 200
]);
abort(400, 'Endpoint deprecated');
}
public function verifyCredentials(Request $request)

View File

@ -35,7 +35,6 @@ class AvatarController extends Controller
$avatar = Avatar::firstOrNew(['profile_id' => $profile->id]);
$currentAvatar = $avatar->recentlyCreated ? null : storage_path('app/'.$profile->avatar->media_path);
$avatar->media_path = "$public/$name";
$avatar->thumb_path = null;
$avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = null;
$avatar->save();
@ -121,10 +120,7 @@ class AvatarController extends Controller
$avatar = $profile->avatar;
if( $avatar->media_path == 'public/avatars/default.png' ||
$avatar->thumb_path == 'public/avatars/default.png' ||
$avatar->media_path == 'public/avatars/default.jpg' ||
$avatar->thumb_path == 'public/avatars/default.jpg'
$avatar->media_path == 'public/avatars/default.jpg'
) {
return;
}
@ -133,12 +129,7 @@ class AvatarController extends Controller
@unlink(storage_path('app/' . $avatar->media_path));
}
if(is_file(storage_path('app/' . $avatar->thumb_path))) {
@unlink(storage_path('app/' . $avatar->thumb_path));
}
$avatar->media_path = 'public/avatars/default.jpg';
$avatar->thumb_path = 'public/avatars/default.jpg';
$avatar->change_count = $avatar->change_count + 1;
$avatar->save();

View File

@ -0,0 +1,515 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth, Cache, Storage, URL;
use Carbon\Carbon;
use App\{
Avatar,
Like,
Media,
MediaTag,
Notification,
Profile,
Place,
Status,
UserFilter
};
use App\Transformer\Api\{
MediaTransformer,
MediaDraftTransformer,
StatusTransformer,
StatusStatelessTransformer
};
use League\Fractal;
use App\Util\Media\Filter;
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Jobs\AvatarPipeline\AvatarOptimize;
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
use App\Jobs\ImageOptimizePipeline\ImageThumbnail;
use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Jobs\VideoPipeline\{
VideoOptimize,
VideoPostProcess,
VideoThumbnail
};
use App\Services\NotificationService;
use App\Services\MediaPathService;
use App\Services\MediaBlocklistService;
use App\Services\MediaTagService;
use Illuminate\Support\Str;
use App\Util\Lexer\Autolink;
use App\Util\Lexer\Extractor;
class ComposeController extends Controller
{
protected $fractal;
public function __construct()
{
$this->middleware('auth');
$this->fractal = new Fractal\Manager();
$this->fractal->setSerializer(new ArraySerializer());
}
public function show(Request $request)
{
return view('status.compose');
}
public function mediaUpload(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'file.*' => function() {
return [
'required',
'mimes:' . config('pixelfed.media_types'),
'max:' . config('pixelfed.max_photo_size'),
];
},
'filter_name' => 'nullable|string|max:24',
'filter_class' => 'nullable|alpha_dash|max:24'
]);
$user = Auth::user();
$profile = $user->profile;
if(config('pixelfed.enforce_account_limit') == true) {
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
return Media::whereUserId($user->id)->sum('size') / 1000;
});
$limit = (int) config('pixelfed.max_account_size');
if ($size >= $limit) {
abort(403, 'Account size limit reached.');
}
}
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
$photo = $request->file('file');
$mimes = explode(',', config('pixelfed.media_types'));
if(in_array($photo->getMimeType(), $mimes) == false) {
return;
}
$storagePath = MediaPathService::get($user, 2);
$path = $photo->store($storagePath);
$hash = \hash_file('sha256', $photo);
abort_if(MediaBlocklistService::exists($hash) == true, 451);
$media = new Media();
$media->status_id = null;
$media->profile_id = $profile->id;
$media->user_id = $user->id;
$media->media_path = $path;
$media->original_sha256 = $hash;
$media->size = $photo->getSize();
$media->mime = $photo->getMimeType();
$media->filter_class = $filterClass;
$media->filter_name = $filterName;
$media->version = 3;
$media->save();
// $url = URL::temporarySignedRoute(
// 'temp-media', now()->addHours(1), ['profileId' => $profile->id, 'mediaId' => $media->id, 'timestamp' => time()]
// );
switch ($media->mime) {
case 'image/jpeg':
case 'image/png':
ImageOptimize::dispatch($media);
break;
case 'video/mp4':
VideoThumbnail::dispatch($media);
$preview_url = '/storage/no-preview.png';
$url = '/storage/no-preview.png';
break;
default:
break;
}
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
$res = $this->fractal->createData($resource)->toArray();
$res['preview_url'] = $media->url() . '?v=' . time();
$res['url'] = $media->url() . '?v=' . time();
return response()->json($res);
}
public function mediaUpdate(Request $request)
{
$this->validate($request, [
'id' => 'required',
'file' => function() {
return [
'required',
'mimes:' . config('pixelfed.media_types'),
'max:' . config('pixelfed.max_photo_size'),
];
},
]);
$user = Auth::user();
$photo = $request->file('file');
$id = $request->input('id');
$media = Media::whereUserId($user->id)
->whereProfileId($user->profile_id)
->whereNull('status_id')
->findOrFail($id);
$media->save();
$fragments = explode('/', $media->media_path);
$name = last($fragments);
array_pop($fragments);
$dir = implode('/', $fragments);
$path = $photo->storeAs($dir, $name);
$res = [
'url' => $media->url() . '?v=' . time()
];
ImageOptimize::dispatch($media);
return $res;
}
public function mediaDelete(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'id' => 'required|integer|min:1|exists:media,id'
]);
$media = Media::whereNull('status_id')
->whereUserId(Auth::id())
->findOrFail($request->input('id'));
Storage::delete($media->media_path);
Storage::delete($media->thumbnail_path);
$media->forceDelete();
return response()->json([
'msg' => 'Successfully deleted',
'code' => 200
]);
}
public function searchTag(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'q' => 'required|string|min:1|max:50'
]);
$q = $request->input('q');
if(Str::of($q)->startsWith('@')) {
if(strlen($q) < 3) {
return [];
}
$q = mb_substr($q, 1);
}
$blocked = UserFilter::whereFilterableType('App\Profile')
->whereFilterType('block')
->whereFilterableId($request->user()->profile_id)
->pluck('user_id');
$blocked->push($request->user()->profile_id);
$results = Profile::select('id','domain','username')
->whereNotIn('id', $blocked)
->whereNull('domain')
->where('username','like','%'.$q.'%')
->limit(15)
->get()
->map(function($r) {
return [
'id' => (string) $r->id,
'name' => $r->username,
'privacy' => true,
'avatar' => $r->avatarUrl()
];
});
return $results;
}
public function searchUntag(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'status_id' => 'required',
'profile_id' => 'required'
]);
$user = $request->user();
$status_id = $request->input('status_id');
$profile_id = (int) $request->input('profile_id');
abort_if((int) $user->profile_id !== $profile_id, 400);
$tag = MediaTag::whereStatusId($status_id)
->whereProfileId($profile_id)
->first();
if(!$tag) {
return [];
}
Notification::whereItemType('App\MediaTag')
->whereItemId($tag->id)
->whereProfileId($profile_id)
->whereAction('tagged')
->delete();
MediaTagService::untag($status_id, $profile_id);
return [200];
}
public function searchLocation(Request $request)
{
abort_if(!Auth::check(), 403);
$this->validate($request, [
'q' => 'required|string|max:100'
]);
$q = filter_var($request->input('q'), FILTER_SANITIZE_STRING);
$hash = hash('sha256', $q);
$key = 'search:location:id:' . $hash;
$places = Cache::remember($key, now()->addMinutes(15), function() use($q) {
$q = '%' . $q . '%';
return Place::where('name', 'like', $q)
->take(80)
->get()
->map(function($r) {
return [
'id' => $r->id,
'name' => $r->name,
'country' => $r->country,
'url' => $r->url()
];
});
});
return $places;
}
public function store(Request $request)
{
$this->validate($request, [
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
'media.*' => 'required',
'media.*.id' => 'required|integer|min:1',
'media.*.filter_class' => 'nullable|alpha_dash|max:30',
'media.*.license' => 'nullable|string|max:140',
'media.*.alt' => 'nullable|string|max:140',
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
'place' => 'nullable',
'comments_disabled' => 'nullable',
'tagged' => 'nullable',
// 'optimize_media' => 'nullable'
]);
if(config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
if($blockedKeywords !== null && $request->caption) {
$keywords = config('costar.keyword.block');
foreach($keywords as $kw) {
if(Str::contains($request->caption, $kw) == true) {
abort(400, 'Invalid object');
}
}
}
}
$user = Auth::user();
$profile = $user->profile;
$visibility = $request->input('visibility');
$medias = $request->input('media');
$attachments = [];
$status = new Status;
$mimes = [];
$place = $request->input('place');
$cw = $request->input('cw');
$tagged = $request->input('tagged');
$optimize_media = (bool) $request->input('optimize_media');
foreach($medias as $k => $media) {
if($k + 1 > config('pixelfed.max_album_length')) {
continue;
}
$m = Media::findOrFail($media['id']);
if($m->profile_id !== $profile->id || $m->status_id) {
abort(403, 'Invalid media id');
}
$m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null;
$m->license = $media['license'];
$m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null;
$m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k;
// if($optimize_media == false) {
// $m->skip_optimize = true;
// ImageThumbnail::dispatch($m);
// } else {
// ImageOptimize::dispatch($m);
// }
if($cw == true || $profile->cw == true) {
$m->is_nsfw = $cw;
$status->is_nsfw = $cw;
}
$m->save();
$attachments[] = $m;
array_push($mimes, $m->mime);
}
$mediaType = StatusController::mimeTypeCheck($mimes);
if(in_array($mediaType, ['photo', 'video', 'photo:album']) == false) {
abort(400, __('exception.compose.invalid.album'));
}
if($place && is_array($place)) {
$status->place_id = $place['id'];
}
if($request->filled('comments_disabled')) {
$status->comments_disabled = (bool) $request->input('comments_disabled');
}
$status->caption = strip_tags($request->caption);
$status->scope = 'draft';
$status->profile_id = $profile->id;
$status->save();
foreach($attachments as $media) {
$media->status_id = $status->id;
$media->save();
}
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
$cw = $profile->cw == true ? true : $cw;
$status->is_nsfw = $cw;
$status->visibility = $visibility;
$status->scope = $visibility;
$status->type = $mediaType;
$status->save();
foreach($tagged as $tg) {
$mt = new MediaTag;
$mt->status_id = $status->id;
$mt->media_id = $status->media->first()->id;
$mt->profile_id = $tg['id'];
$mt->tagged_username = $tg['name'];
$mt->is_public = true;
$mt->metadata = json_encode([
'_v' => 1,
]);
$mt->save();
MediaTagService::set($mt->status_id, $mt->profile_id);
MediaTagService::sendNotification($mt);
}
NewStatusPipeline::dispatch($status);
Cache::forget('user:account:id:'.$profile->user_id);
Cache::forget('_api:statuses:recent_9:'.$profile->id);
Cache::forget('profile:status_count:'.$profile->id);
Cache::forget('status:transformer:media:attachments:'.$status->id);
Cache::forget($user->storageUsedKey());
return $status->url();
}
public function storeText(Request $request)
{
abort_unless(config('exp.top'), 404);
$this->validate($request, [
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
'place' => 'nullable',
'comments_disabled' => 'nullable',
'tagged' => 'nullable',
]);
if(config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
if($blockedKeywords !== null && $request->caption) {
$keywords = config('costar.keyword.block');
foreach($keywords as $kw) {
if(Str::contains($request->caption, $kw) == true) {
abort(400, 'Invalid object');
}
}
}
}
$user = Auth::user();
$profile = $user->profile;
$visibility = $request->input('visibility');
$status = new Status;
$place = $request->input('place');
$cw = $request->input('cw');
$tagged = $request->input('tagged');
if($place && is_array($place)) {
$status->place_id = $place['id'];
}
if($request->filled('comments_disabled')) {
$status->comments_disabled = (bool) $request->input('comments_disabled');
}
$status->caption = strip_tags($request->caption);
$status->profile_id = $profile->id;
$entities = Extractor::create()->extract($status->caption);
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
$cw = $profile->cw == true ? true : $cw;
$status->is_nsfw = $cw;
$status->visibility = $visibility;
$status->scope = $visibility;
$status->type = 'text';
$status->rendered = Autolink::create()->autolink($status->caption);
$status->entities = json_encode(array_merge([
'timg' => [
'version' => 0,
'bg_id' => 1,
'font_size' => strlen($status->caption) <= 140 ? 'h1' : 'h3',
'length' => strlen($status->caption),
]
], $entities), JSON_UNESCAPED_SLASHES);
$status->save();
foreach($tagged as $tg) {
$mt = new MediaTag;
$mt->status_id = $status->id;
$mt->media_id = $status->media->first()->id;
$mt->profile_id = $tg['id'];
$mt->tagged_username = $tg['name'];
$mt->is_public = true;
$mt->metadata = json_encode([
'_v' => 1,
]);
$mt->save();
MediaTagService::set($mt->status_id, $mt->profile_id);
MediaTagService::sendNotification($mt);
}
Cache::forget('user:account:id:'.$profile->user_id);
Cache::forget('_api:statuses:recent_9:'.$profile->id);
Cache::forget('profile:status_count:'.$profile->id);
return $status->url();
}
}

View File

@ -144,12 +144,12 @@ class DiscoverController extends Controller
public function profilesDirectoryApi(Request $request)
{
return ['error' => 'Temporarily unavailable.'];
$this->validate($request, [
'page' => 'integer|max:10'
]);
return ['error' => 'Temporarily unavailable.'];
$page = $request->input('page') ?? 1;
$key = 'discover:profiles:page:' . $page;
$ttl = now()->addHours(12);
@ -214,6 +214,8 @@ class DiscoverController extends Controller
public function trendingHashtags(Request $request)
{
return [];
$res = StatusHashtag::select('hashtag_id', \DB::raw('count(*) as total'))
->groupBy('hashtag_id')
->orderBy('total','desc')
@ -234,6 +236,8 @@ class DiscoverController extends Controller
public function trendingPlaces(Request $request)
{
return [];
$res = Status::select('place_id',DB::raw('count(place_id) as total'))
->whereNotNull('place_id')
->where('created_at','>',now()->subDays(14))
@ -250,6 +254,6 @@ class DiscoverController extends Controller
];
});
return $res;
return [];
}
}

View File

@ -22,39 +22,6 @@ class MediaController extends Controller
public function composeUpdate(Request $request, $id)
{
$this->validate($request, [
'file' => function() {
return [
'required',
'mimes:' . config('pixelfed.media_types'),
'max:' . config('pixelfed.max_photo_size'),
];
},
]);
$user = Auth::user();
$photo = $request->file('file');
$media = Media::whereUserId($user->id)
->whereProfileId($user->profile_id)
->whereNull('status_id')
->findOrFail($id);
$media->version = 2;
$media->save();
$fragments = explode('/', $media->media_path);
$name = last($fragments);
array_pop($fragments);
$dir = implode('/', $fragments);
$path = $photo->storeAs($dir, $name);
$res = [];
$res['url'] = URL::temporarySignedRoute(
'temp-media', now()->addHours(1), ['profileId' => $media->profile_id, 'mediaId' => $media->id, 'timestamp' => time()]
);
ImageOptimize::dispatch($media);
return $res;
abort(400, 'Endpoint deprecated');
}
}

View File

@ -20,44 +20,7 @@ class MediaTagController extends Controller
public function usernameLookup(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'q' => 'required|string|min:1|max:50'
]);
$q = $request->input('q');
if(Str::of($q)->startsWith('@')) {
if(strlen($q) < 3) {
return [];
}
$q = mb_substr($q, 1);
}
$blocked = UserFilter::whereFilterableType('App\Profile')
->whereFilterType('block')
->whereFilterableId($request->user()->profile_id)
->pluck('user_id');
$blocked->push($request->user()->profile_id);
$results = Profile::select('id','domain','username')
->whereNotIn('id', $blocked)
->whereNull('domain')
->where('username','like','%'.$q.'%')
->limit(15)
->get()
->map(function($r) {
return [
'id' => (string) $r->id,
'name' => $r->username,
'privacy' => true,
'avatar' => $r->avatarUrl()
];
});
return $results;
abort(404);
}
public function untagProfile(Request $request)

View File

@ -4,17 +4,235 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use App\AccountLog;
use App\Follower;
use App\Like;
use App\Status;
use App\StatusHashtag;
use Illuminate\Support\Facades\Cache;
class SeasonalController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function __construct()
{
$this->middleware('auth');
}
public function yearInReview()
{
$profile = Auth::user()->profile;
return view('account.yir', compact('profile'));
}
public function yearInReview()
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(config('database.default') != 'mysql', 404);
$profile = Auth::user()->profile;
return view('account.yir', compact('profile'));
}
public function getData(Request $request)
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(config('database.default') != 'mysql', 404);
$uid = $request->user()->id;
$pid = $request->user()->profile_id;
$epoch = '2020-01-01 00:00:00';
$epochStart = '2020-01-01 00:00:00';
$epochEnd = '2020-12-31 23:59:59';
$siteKey = 'seasonal:my2020:shared';
$siteTtl = now()->addMonths(3);
$userKey = 'seasonal:my2020:user:' . $uid;
$userTtl = now()->addMonths(3);
$shared = Cache::remember($siteKey, $siteTtl, function() use($epochStart, $epochEnd) {
return [
'average' => [
'posts' => round(Status::selectRaw('*, count(profile_id) as count')
->whereNull('uri')
->whereIn('type', ['photo','photo:album','video','video:album','photo:video:album'])
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->pluck('count')
->avg()),
'likes' => round(Like::selectRaw('*, count(profile_id) as count')
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->pluck('count')
->avg()),
],
'popular' => [
'hashtag' => StatusHashtag::selectRaw('*,count(hashtag_id) as count')
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('hashtag_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->hashtag->name,
'count' => $sh->count
];
})
->first(),
'post' => Status::whereScope('public')
->where('likes_count', '>', 1)
->whereIsNsfw(false)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->orderByDesc('likes_count')
->take(1)
->get()
->map(function($status) {
return [
'id' => (string) $status->id,
'username' => (string) $status->profile->username,
'created_at' => $status->created_at->format('M d, Y'),
'type' => $status->type,
'url' => $status->url(),
'thumb' => $status->thumb(),
'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count,
'reply_count' => $status->reply_count ?? 0,
];
})
->first(),
'places' => Status::selectRaw('*, count(place_id) as count')
->whereNotNull('place_id')
->having('count', '>', 1)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('place_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->place->getName(),
'url' => $sh->place->url(),
'count' => $sh->count
];
})
->first()
],
];
});
$res = Cache::remember($userKey, $userTtl, function() use($uid, $pid, $epochStart, $epochEnd, $request) {
return [
'account' => [
'user_id' => $request->user()->id,
'created_at' => $request->user()->created_at->format('M d, Y'),
'created_this_year' => $request->user()->created_at->gt('2020-01-01 00:00:00'),
'created_months_ago' => $request->user()->created_at->diffInMonths(now()),
'followers_this_year' => Follower::whereFollowingId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'followed_this_year' => Follower::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'most_popular' => Status::whereProfileId($pid)
->where('likes_count', '>', 1)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->orderByDesc('likes_count')
->take(1)
->get()
->map(function($status) {
return [
'id' => (string) $status->id,
'username' => (string) $status->profile->username,
'created_at' => $status->created_at->format('M d, Y'),
'type' => $status->type,
'url' => $status->url(),
'thumb' => $status->thumb(),
'likes_count' => $status->likes_count,
'reblogs_count' => $status->reblogs_count,
'reply_count' => $status->reply_count ?? 0,
];
})
->first(),
'posts_count' => Status::whereProfileId($pid)
->whereIn('type', ['photo','photo:album','video','video:album','photo:video:album'])
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'likes_count' => Like::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->count(),
'hashtag' => StatusHashtag::selectRaw('*, count(hashtag_id) as count')
->whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('profile_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->hashtag->name,
'count' => $sh->count
];
})
->first(),
'places' => Status::selectRaw('*, count(place_id) as count')
->whereNotNull('place_id')
->having('count', '>', 1)
->whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->groupBy('place_id')
->orderByDesc('count')
->take(1)
->get()
->map(function($sh) {
return [
'name' => $sh->place->getName(),
'url' => $sh->place->url(),
'count' => $sh->count
];
})
->first(),
'places_total' => Status::whereProfileId($pid)
->where('created_at', '>', $epochStart)
->where('created_at', '<', $epochEnd)
->whereNotNull('place_id')
->count()
]
];
});
return response()->json(array_merge($res, $shared));
}
public function store(Request $request)
{
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
abort_if(config('database.default') != 'mysql', 404);
$this->validate($request, [
'profile_id' => 'required',
'type' => 'required|string|in:view,hide'
]);
$user = $request->user();
$log = new AccountLog();
$log->user_id = $user->id;
$log->item_type = 'App\User';
$log->item_id = $user->id;
$log->action = $request->input('type') == 'view' ? 'seasonal.my2020.view' : 'seasonal.my2020.hide';
$log->ip_address = $request->ip();
$log->user_agent = $request->user_agent();
$log->save();
}
}

View File

@ -9,6 +9,7 @@ use App\Util\Lexer\PrettyNumber;
use App\{Follower, Page, Profile, Status, User, UserFilter};
use App\Util\Localization\Localization;
use App\Services\FollowerService;
use App\Util\ActivityPub\Helpers;
class SiteController extends Controller
{
@ -108,10 +109,12 @@ class SiteController extends Controller
public function redirectUrl(Request $request)
{
abort_if(!$request->user(), 404);
$this->validate($request, [
'url' => 'required|url'
]);
$url = request()->input('url');
abort_if(Helpers::validateUrl($url) == false, 404);
return view('site.redirect', compact('url'));
}

View File

@ -282,7 +282,7 @@ class StatusController extends Controller
$resource = new Fractal\Resource\Item($status, new Note());
$res = $fractal->createData($resource)->toArray();
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT);
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
}
public function edit(Request $request, $username, $id)
@ -408,4 +408,25 @@ class StatusController extends Controller
return response()->json([200]);
}
public function storeView(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'status_id' => 'required|integer|exists:statuses,id',
'profile_id' => 'required|integer|exists:profiles,id'
]);
$sid = (int) $request->input('status_id');
$pid = (int) $request->input('profile_id');
StatusView::firstOrCreate([
'status_id' => $sid,
'status_profile_id' => $pid,
'profile_id' => $request->user()->profile_id
]);
return response()->json(1);
}
}

View File

@ -58,7 +58,6 @@ class AvatarOptimize implements ShouldQueue
$img->save($file, $quality);
$avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail();
$avatar->thumb_path = $avatar->media_path;
$avatar->change_count = ++$avatar->change_count;
$avatar->last_processed_at = Carbon::now();
$avatar->save();

View File

@ -45,7 +45,6 @@ class CreateAvatar implements ShouldQueue
$avatar = new Avatar();
$avatar->profile_id = $profile->id;
$avatar->media_path = $path;
$avatar->thumb_path = $path;
$avatar->change_count = 0;
$avatar->last_processed_at = \Carbon\Carbon::now();
$avatar->save();

View File

@ -0,0 +1,98 @@
<?php
namespace App\Jobs\AvatarPipeline;
use App\Avatar;
use App\Profile;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Util\ActivityPub\Helpers;
use Illuminate\Support\Str;
use Zttp\Zttp;
use App\Http\Controllers\AvatarController;
use Storage;
use Log;
use Illuminate\Http\File;
use App\Services\MediaStorageService;
use App\Services\ActivityPubFetchService;
class RemoteAvatarFetch implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $profile;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Profile $profile)
{
$this->profile = $profile;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$profile = $this->profile;
if($profile->domain == null || $profile->private_key) {
return 1;
}
$avatar = Avatar::firstOrCreate([
'profile_id' => $profile->id
]);
if($avatar->media_path == null && $avatar->remote_url == null) {
$avatar->media_path = 'public/avatars/default.jpg';
$avatar->is_remote = true;
$avatar->save();
}
$person = Helpers::fetchFromUrl($profile->remote_url);
if(!$person || !isset($person['@context'])) {
return 1;
}
if( !isset($person['icon']) ||
!isset($person['icon']['type']) ||
!isset($person['icon']['url'])
) {
return 1;
}
if($person['icon']['type'] !== 'Image') {
return 1;
}
if(!Helpers::validateUrl($person['icon']['url'])) {
return 1;
}
$icon = $person['icon'];
$avatar->remote_url = $icon['url'];
$avatar->save();
MediaStorageService::avatar($avatar);
return 1;
}
}

View File

@ -41,7 +41,7 @@ class ImageOptimize implements ShouldQueue
{
$media = $this->media;
$path = storage_path('app/'.$media->media_path);
if (!is_file($path)) {
if (!is_file($path) || $media->skip_optimize) {
return;
}

View File

@ -45,7 +45,7 @@ class ImageResize implements ShouldQueue
return;
}
$path = storage_path('app/'.$media->media_path);
if (!is_file($path)) {
if (!is_file($path) || $media->skip_optimize) {
return;
}

View File

@ -11,6 +11,8 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use ImageOptimizer;
use Illuminate\Http\File;
use App\Services\MediaPathService;
use App\Jobs\MediaPipeline\MediaStoragePipeline;
class ImageUpdate implements ShouldQueue
{
@ -60,7 +62,9 @@ class ImageUpdate implements ShouldQueue
if (in_array($media->mime, $this->protectedMimes) == true) {
ImageOptimizer::optimize($thumb);
ImageOptimizer::optimize($path);
if(!$media->skip_optimize) {
ImageOptimizer::optimize($path);
}
}
if (!is_file($path) || !is_file($thumb)) {
@ -73,19 +77,7 @@ class ImageUpdate implements ShouldQueue
$media->size = $total;
$media->save();
if(config('pixelfed.cloud_storage') == true) {
$p = explode('/', $media->media_path);
$monthHash = $p[2];
$userHash = $p[3];
$storagePath = "public/m/{$monthHash}/{$userHash}";
$file = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($path), 'public');
$url = Storage::disk(config('filesystems.cloud'))->url($file);
$thumbFile = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($thumb), 'public');
$thumbUrl = Storage::disk(config('filesystems.cloud'))->url($thumbFile);
$media->thumbnail_url = $thumbUrl;
$media->cdn_url = $url;
$media->optimized_url = $url;
$media->save();
}
MediaStoragePipeline::dispatch($media);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Jobs\MediaPipeline;
use App\Media;
use Cache;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Redis;
use App\Services\MediaStorageService;
class MediaStoragePipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $media;
public function __construct(Media $media)
{
$this->media = $media;
}
public function handle()
{
MediaStorageService::store($this->media);
}
}

View File

@ -2,7 +2,7 @@
namespace App\Jobs\StatusPipeline;
use DB;
use DB, Storage;
use App\{
AccountInterstitial,
MediaTag,
@ -17,6 +17,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use League\Fractal;
use Illuminate\Support\Str;
use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\DeleteNote;
use App\Util\ActivityPub\Helpers;
@ -89,6 +90,24 @@ class StatusDelete implements ShouldQueue
if (is_file($photo)) {
unlink($photo);
}
if( config('pixelfed.cloud_storage') == true) {
if( Str::of($media->media_path)
->startsWith('public/') &&
Storage::disk(config('filesystems.cloud'))
->exists($media->media_path)
) {
Storage::disk(config('filesystems.cloud'))
->delete($media->media_path);
}
if( Str::of($media->thumbnail_path)
->startsWith('public/') &&
Storage::disk(config('filesystems.cloud'))
->exists($media->thumbnail_path)
) {
Storage::disk(config('filesystems.cloud'))
->delete($media->thumbnail_path);
}
}
$media->delete();
} catch (Exception $e) {
}

View File

@ -12,6 +12,7 @@ use Cache;
use FFMpeg;
use Storage;
use App\Media;
use App\Jobs\MediaPipeline\MediaStoragePipeline;
class VideoThumbnail implements ShouldQueue
{
@ -62,26 +63,10 @@ class VideoThumbnail implements ShouldQueue
}
if(config('pixelfed.cloud_storage') == true) {
$path = storage_path('app/'.$media->media_path);
$thumb = storage_path('app/'.$media->thumbnail_path);
$p = explode('/', $media->media_path);
$monthHash = $p[2];
$userHash = $p[3];
$storagePath = "public/m/{$monthHash}/{$userHash}";
$file = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($path), 'public');
$url = Storage::disk(config('filesystems.cloud'))->url($file);
$thumbFile = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($thumb), 'public');
$thumbUrl = Storage::disk(config('filesystems.cloud'))->url($thumbFile);
$media->thumbnail_url = $thumbUrl;
$media->cdn_url = $url;
$media->optimized_url = $url;
$media->save();
}
if($media->status_id) {
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
}
MediaStoragePipeline::dispatch($media);
}
}

View File

@ -29,25 +29,28 @@ class Media extends Model
public function url()
{
if(!empty($this->remote_media) && $this->remote_url) {
//$url = \App\Services\MediaProxyService::get($this->remote_url, $this->mime);
$url = $this->remote_url;
} else {
$path = $this->media_path;
$url = $this->cdn_url ?? config('app.url') . Storage::url($path);
if($this->cdn_url) {
return $this->cdn_url;
}
return $url;
if($this->remote_media && $this->remote_url) {
return $this->remote_url;
}
return url(Storage::url($this->media_path));
}
public function thumbnailUrl()
{
if($this->remote_media == true) {
return $this->remote_url;
} else {
$path = $this->thumbnail_path ?? 'public/no-preview.png';
return url(Storage::url($path));
if($this->thumbnail_url) {
return $this->thumbnail_url;
}
if(!$this->remote_media && $this->thumbnail_path) {
return url(Storage::url($this->thumbnail_path));
}
return url(Storage::url('public/no-preview.png'));
}
public function thumb()

View File

@ -35,7 +35,7 @@ class InstanceActor extends Model
'publicKeyPem' => $this->public_key
],
'manuallyApprovesFollowers' => true,
'url' => route('help.instance-actor')
'url' => url('/site/kb/instance-actor')
];
}
}

View File

@ -3,6 +3,8 @@
namespace App\Observers;
use App\Avatar;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class AvatarObserver
{
@ -54,12 +56,13 @@ class AvatarObserver
) {
@unlink($path);
}
$path = storage_path('app/'.$avatar->thumb_path);
if( is_file($path) &&
$avatar->thumb_path != 'public/avatars/default.png' &&
$avatar->media_path != 'public/avatars/default.jpg'
) {
@unlink($path);
if($avatar->cdn_url) {
$disk = Storage::disk(config('filesystems.cloud'));
$base = Str::startsWith($avatar->media_path, 'cache/avatars/');
if($base && $disk->exists($avatar->media_path)) {
$disk->delete($avatar->media_path);
}
}
}

View File

@ -151,6 +151,15 @@ class Profile extends Model
{
$url = Cache::remember('avatar:'.$this->id, now()->addYears(1), function () {
$avatar = $this->avatar;
if($avatar->cdn_url) {
return $avatar->cdn_url ?? url('/storage/avatars/default.jpg');
}
if($avatar->is_remote) {
return $avatar->cdn_url ?? url('/storage/avatars/default.jpg');
}
$path = $avatar->media_path;
$path = "{$path}?v={$avatar->change_count}";

View File

@ -9,51 +9,20 @@ use App\Util\ActivityPub\HttpSignature;
class ActivityPubFetchService
{
public $signed = true;
public $actor;
public $url;
public $headers = [
'Accept' => 'application/activity+json, application/json',
'User-Agent' => '(Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')'
];
public static function queue()
{
return new self;
}
public function signed($signed = true)
{
$this->signed = $signed;
return $this;
}
public function actor($profile)
{
$this->actor = $profile;
return $this;
}
public function url($url)
public static function get($url)
{
if(!Helpers::validateUrl($url)) {
throw new \Exception('Invalid URL');
return 0;
}
$this->url = $url;
return $this;
}
public function get()
{
if($this->signed == true && $this->actor == null) {
throw new \Exception('Cannot sign request without actor');
}
return $this->signedRequest();
}
$headers = HttpSignature::instanceActorSign($url, false, [
'Accept' => 'application/activity+json, application/json',
'User-Agent' => '(Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')'
]);
protected function signedRequest()
{
$this->headers = HttpSignature::sign($this->actor, $this->url, false, $this->headers);
return Zttp::withHeaders($this->headers)->get($this->url)->body();
return Zttp::withHeaders($headers)
->timeout(30)
->get($url)
->body();
}
}

File diff suppressed because one or more lines are too long

View File

@ -48,4 +48,30 @@ class MediaPathService {
return $path;
}
public static function story($account, $version = 1)
{
$mh = hash('sha256', date('Y').'-.-'.date('m'));
$monthHash = date('Y').date('m').substr($mh, 0, 6).substr($mh, 58, 6);
$random = '03'.Str::random(random_int(6,9)).'_'.Str::random(random_int(6,17));
if($account instanceOf User) {
switch ($version) {
case 1:
$userHash = $account->profile_id;
$path = "public/_esm.t3/{$monthHash}/{$userHash}/{$random}";
break;
default:
$userHash = $account->profile_id;
$path = "public/_esm.t3/{$monthHash}/{$userHash}/{$random}";
break;
}
}
if($account instanceOf Profile) {
$userHash = $account->id;
$path = "public/_esm.t3/{$monthHash}/{$userHash}/{$random}";
}
return $path;
}
}

View File

@ -0,0 +1,230 @@
<?php
namespace App\Services;
use App\Util\ActivityPub\Helpers;
use Illuminate\Http\File;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use App\Media;
use App\Profile;
use App\User;
use GuzzleHttp\Client;
use App\Http\Controllers\AvatarController;
use GuzzleHttp\Exception\RequestException;
class MediaStorageService {
public static function store(Media $media)
{
if(config('pixelfed.cloud_storage') == true) {
(new self())->cloudStore($media);
}
return;
}
public static function avatar($avatar)
{
return (new self())->fetchAvatar($avatar);
}
public static function head($url)
{
$c = new Client();
try {
$r = $c->request('HEAD', $url);
} catch (RequestException $e) {
return false;
}
$h = $r->getHeaders();
return [
'length' => $h['Content-Length'][0],
'mime' => $h['Content-Type'][0]
];
}
protected function cloudStore($media)
{
if($media->remote_media == true) {
(new self())->remoteToCloud($media);
} else {
(new self())->localToCloud($media);
}
}
protected function localToCloud($media)
{
$path = storage_path('app/'.$media->media_path);
$thumb = storage_path('app/'.$media->thumbnail_path);
$p = explode('/', $media->media_path);
$name = array_pop($p);
$pt = explode('/', $media->thumbnail_path);
$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;
$media->cdn_url = $url;
$media->optimized_url = $url;
$media->replicated_at = now();
$media->save();
if($media->status_id) {
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
}
}
protected function remoteToCloud($media)
{
$url = $media->remote_url;
if(!Helpers::validateUrl($url)) {
return;
}
$head = $this->head($media->remote_url);
if(!$head) {
return;
}
$mimes = [
'image/jpeg',
'image/png',
'video/mp4'
];
$mime = $head['mime'];
$max_size = (int) config('pixelfed.max_photo_size') * 1000;
$media->size = $head['length'];
$media->remote_media = true;
$media->save();
if(!in_array($mime, $mimes)) {
return;
}
if($head['length'] >= $max_size) {
return;
}
switch ($mime) {
case 'image/png':
$ext = '.png';
break;
case 'image/gif':
$ext = '.gif';
break;
case 'image/jpeg':
$ext = '.jpg';
break;
case 'video/mp4':
$ext = '.mp4';
break;
}
$base = MediaPathService::get($media->profile);
$path = Str::random(40) . $ext;
$tmpBase = storage_path('app/remcache/');
$tmpPath = $media->profile_id . '-' . $path;
$tmpName = $tmpBase . $tmpPath;
$data = file_get_contents($url, false, null, 0, $head['length']);
file_put_contents($tmpName, $data);
$hash = hash_file('sha256', $tmpName);
$disk = Storage::disk(config('filesystems.cloud'));
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
$permalink = $disk->url($file);
$media->media_path = $base . $path;
$media->cdn_url = $permalink;
$media->original_sha256 = $hash;
$media->replicated_at = now();
$media->save();
if($media->status_id) {
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
}
unlink($tmpName);
}
protected function fetchAvatar($avatar)
{
$url = $avatar->remote_url;
if($url == null || Helpers::validateUrl($url) == false) {
return;
}
$head = $this->head($url);
if($head == false) {
return;
}
$mimes = [
'image/jpeg',
'image/png',
];
$mime = $head['mime'];
$max_size = (int) config('pixelfed.max_avatar_size') * 1000;
if($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subDay())) {
return;
}
// handle pleroma edge case
if(Str::endsWith($mime, '; charset=utf-8')) {
$mime = str_replace('; charset=utf-8', '', $mime);
}
if(!in_array($mime, $mimes)) {
return;
}
if($head['length'] >= $max_size) {
return;
}
if($avatar->size && $head['length'] == $avatar->size) {
return;
}
$base = 'cache/avatars/' . $avatar->profile_id;
$ext = $head['mime'] == 'image/jpeg' ? 'jpg' : 'png';
$path = Str::random(20) . '_avatar.' . $ext;
$tmpBase = storage_path('app/remcache/');
$tmpPath = 'avatar_' . $avatar->profile_id . '-' . $path;
$tmpName = $tmpBase . $tmpPath;
$data = file_get_contents($url, false, null, 0, $head['length']);
file_put_contents($tmpName, $data);
$disk = Storage::disk(config('filesystems.cloud'));
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
$permalink = $disk->url($file);
$avatar->media_path = $base . $path;
$avatar->is_remote = true;
$avatar->cdn_url = $permalink;
$avatar->size = $head['length'];
$avatar->change_count = $avatar->change_count + 1;
$avatar->last_fetched_at = now();
$avatar->save();
Cache::forget('avatar:' . $avatar->profile_id);
unlink($tmpName);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use App\Status;
use Illuminate\Support\Str;
class StatusLabelService
{
const CACHE_KEY = 'pf:services:status_label:_v0:';
public static function get(Status $status)
{
if(config('instance.label.covid.enabled') == false || !$status) {
return [
'covid' => false
];
}
return Cache::remember(self::CACHE_KEY . $status->id, now()->addDays(7), function() use($status) {
return [
'covid' => Str::of(strtolower($status->caption))->contains(['covid','corona', 'coronavirus', 'vaccine', 'vaxx', 'vaccination'])
];
});
}
}

View File

@ -9,7 +9,7 @@ class MediaTransformer extends Fractal\TransformerAbstract
{
public function transform(Media $media)
{
return [
$res = [
'id' => (string) $media->id,
'type' => lcfirst($media->activityVerb()),
'url' => $media->url(),
@ -17,7 +17,25 @@ class MediaTransformer extends Fractal\TransformerAbstract
'preview_url' => $media->thumbnailUrl(),
'text_url' => null,
'meta' => null,
'description' => $media->caption
'description' => $media->caption,
'blurhash' => $media->blurhash
];
if($media->width && $media->height) {
$res['meta'] = [
'focus' => [
'x' => 0,
'y' => 0
],
'original' => [
'width' => $media->width,
'height' => $media->height,
'size' => "{$media->width}x{$media->height}",
'aspect' => $media->width / $media->height
]
];
}
return $res;
}
}

View File

@ -9,7 +9,7 @@ class MediaTransformer extends Fractal\TransformerAbstract
{
public function transform(Media $media)
{
return [
$res = [
'id' => (string) $media->id,
'type' => $media->activityVerb(),
'url' => $media->url(),
@ -24,6 +24,24 @@ class MediaTransformer extends Fractal\TransformerAbstract
'filter_name' => $media->filter_name,
'filter_class' => $media->version == 1 ? $media->filter_class : null,
'mime' => $media->mime,
'blurhash' => $media->blurhash
];
if($media->width && $media->height) {
$res['meta'] = [
'focus' => [
'x' => 0,
'y' => 0
],
'original' => [
'width' => $media->width,
'height' => $media->height,
'size' => "{$media->width}x{$media->height}",
'aspect' => $media->width / $media->height
]
];
}
return $res;
}
}

View File

@ -7,6 +7,8 @@ use League\Fractal;
use Cache;
use App\Services\HashidService;
use App\Services\MediaTagService;
use App\Services\StatusLabelService;
use Illuminate\Support\Str;
class StatusTransformer extends Fractal\TransformerAbstract
{
@ -55,7 +57,8 @@ class StatusTransformer extends Fractal\TransformerAbstract
'parent' => [],
'place' => $status->place,
'local' => (bool) $status->local,
'taggedPeople' => $taggedPeople
'taggedPeople' => $taggedPeople,
'label' => StatusLabelService::get($status)
];
}

View File

@ -14,7 +14,7 @@ class StoryItemTransformer extends Fractal\TransformerAbstract
return [
'id' => (string) $item->id,
'type' => $item->type,
'length' => 5,
'length' => 10,
'src' => $item->url(),
'preview' => null,
'link' => null,

View File

@ -23,9 +23,12 @@ use App\Jobs\ImageOptimizePipeline\{ImageOptimize,ImageThumbnail};
use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Util\ActivityPub\HttpSignature;
use Illuminate\Support\Str;
use App\Services\ActivityPubFetchService;
use App\Services\ActivityPubDeliveryService;
use App\Services\MediaPathService;
use App\Services\MediaStorageService;
use App\Jobs\MediaPipeline\MediaStoragePipeline;
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
class Helpers {
@ -214,8 +217,8 @@ class Helpers {
$ttl = now()->addMinutes(5);
return Cache::remember($key, $ttl, function() use($url) {
$res = Zttp::withoutVerifying()->withHeaders(self::zttpUserAgent())->get($url);
$res = json_decode($res->body(), true, 8);
$res = ActivityPubFetchService::get($url);
$res = json_decode($res, true, 8);
if(json_last_error() == JSON_ERROR_NONE) {
return $res;
} else {
@ -242,129 +245,132 @@ class Helpers {
if($local) {
$id = (int) last(explode('/', $url));
return Status::whereNotIn('scope', ['draft','archived'])->findOrFail($id);
}
$cached = Status::whereNotIn('scope', ['draft','archived'])
->whereUri($url)
->orWhere('object_url', $url)
->first();
if($cached) {
return $cached;
}
$res = self::fetchFromUrl($url);
if(!$res || empty($res)) {
return;
}
if(isset($res['object'])) {
$activity = $res;
} else {
$cached = Status::whereNotIn('scope', ['draft','archived'])
->whereUri($url)
->orWhere('object_url', $url)
->first();
$activity = ['object' => $res];
}
if($cached) {
return $cached;
$scope = 'private';
$cw = isset($res['sensitive']) ? (bool) $res['sensitive'] : false;
if(isset($res['to']) == true) {
if(is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
$scope = 'public';
}
$res = self::fetchFromUrl($url);
if(!$res || empty($res)) {
return;
if(is_string($res['to']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['to']) {
$scope = 'public';
}
}
if(isset($res['object'])) {
$activity = $res;
} else {
$activity = ['object' => $res];
if(isset($res['cc']) == true) {
if(is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
$scope = 'unlisted';
}
$scope = 'private';
$cw = isset($res['sensitive']) ? (bool) $res['sensitive'] : false;
if(isset($res['to']) == true) {
if(is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
$scope = 'public';
}
if(is_string($res['to']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['to']) {
$scope = 'public';
}
if(is_string($res['cc']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['cc']) {
$scope = 'unlisted';
}
}
if(isset($res['cc']) == true) {
if(is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
$scope = 'unlisted';
}
if(is_string($res['cc']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['cc']) {
$scope = 'unlisted';
}
}
if(config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
if($blockedKeywords !== null) {
$keywords = config('costar.keyword.block');
foreach($keywords as $kw) {
if(Str::contains($res['content'], $kw) == true) {
abort(400, 'Invalid object');
}
if(config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
if($blockedKeywords !== null) {
$keywords = config('costar.keyword.block');
foreach($keywords as $kw) {
if(Str::contains($res['content'], $kw) == true) {
return;
}
}
$unlisted = config('costar.domain.unlisted');
if(in_array(parse_url($url, PHP_URL_HOST), $unlisted) == true) {
$unlisted = true;
$scope = 'unlisted';
} else {
$unlisted = false;
}
$cwDomains = config('costar.domain.cw');
if(in_array(parse_url($url, PHP_URL_HOST), $cwDomains) == true) {
$cw = true;
}
}
$id = isset($res['id']) ? $res['id'] : $url;
if(!self::validateUrl($id) ||
!self::validateUrl($activity['object']['attributedTo'])
) {
return;
}
$idDomain = parse_url($id, PHP_URL_HOST);
$urlDomain = parse_url($url, PHP_URL_HOST);
$actorDomain = parse_url($activity['object']['attributedTo'], PHP_URL_HOST);
if(
$idDomain !== $urlDomain ||
$actorDomain !== $urlDomain ||
$idDomain !== $actorDomain
) {
return;
}
$profile = self::profileFirstOrNew($activity['object']['attributedTo']);
if(isset($activity['object']['inReplyTo']) && !empty($activity['object']['inReplyTo']) && $replyTo == true) {
$reply_to = self::statusFirstOrFetch($activity['object']['inReplyTo'], false);
$reply_to = optional($reply_to)->id;
$unlisted = config('costar.domain.unlisted');
if(in_array(parse_url($url, PHP_URL_HOST), $unlisted) == true) {
$unlisted = true;
$scope = 'unlisted';
} else {
$reply_to = null;
$unlisted = false;
}
$ts = is_array($res['published']) ? $res['published'][0] : $res['published'];
$status = DB::transaction(function() use($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id) {
$status = new Status;
$status->profile_id = $profile->id;
$status->url = isset($res['url']) ? $res['url'] : $url;
$status->uri = isset($res['url']) ? $res['url'] : $url;
$status->object_url = $id;
$status->caption = strip_tags($res['content']);
$status->rendered = Purify::clean($res['content']);
$status->created_at = Carbon::parse($ts);
$status->in_reply_to_id = $reply_to;
$status->local = false;
$status->is_nsfw = $cw;
$status->scope = $scope;
$status->visibility = $scope;
$status->cw_summary = $cw == true && isset($res['summary']) ?
Purify::clean(strip_tags($res['summary'])) : null;
$status->save();
if($reply_to == null) {
self::importNoteAttachment($res, $status);
}
return $status;
});
return $status;
$cwDomains = config('costar.domain.cw');
if(in_array(parse_url($url, PHP_URL_HOST), $cwDomains) == true) {
$cw = true;
}
}
$id = isset($res['id']) ? $res['id'] : $url;
$idDomain = parse_url($id, PHP_URL_HOST);
$urlDomain = parse_url($url, PHP_URL_HOST);
if(!self::validateUrl($id)) {
return;
}
if(isset($activity['object']['attributedTo'])) {
$actorDomain = parse_url($activity['object']['attributedTo'], PHP_URL_HOST);
if(!self::validateUrl($activity['object']['attributedTo']) ||
$idDomain !== $actorDomain)
{
return;
}
}
if(
$idDomain !== $urlDomain ||
$actorDomain !== $urlDomain
) {
return;
}
$profile = self::profileFirstOrNew($activity['object']['attributedTo']);
if(isset($activity['object']['inReplyTo']) && !empty($activity['object']['inReplyTo']) && $replyTo == true) {
$reply_to = self::statusFirstOrFetch($activity['object']['inReplyTo'], false);
$reply_to = optional($reply_to)->id;
} else {
$reply_to = null;
}
$ts = is_array($res['published']) ? $res['published'][0] : $res['published'];
$status = DB::transaction(function() use($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id) {
$status = new Status;
$status->profile_id = $profile->id;
$status->url = isset($res['url']) ? $res['url'] : $url;
$status->uri = isset($res['url']) ? $res['url'] : $url;
$status->object_url = $id;
$status->caption = strip_tags($res['content']);
$status->rendered = Purify::clean($res['content']);
$status->created_at = Carbon::parse($ts);
$status->in_reply_to_id = $reply_to;
$status->local = false;
$status->is_nsfw = $cw;
$status->scope = $scope;
$status->visibility = $scope;
$status->cw_summary = $cw == true && isset($res['summary']) ?
Purify::clean(strip_tags($res['summary'])) : null;
$status->save();
if($reply_to == null) {
self::importNoteAttachment($res, $status);
}
return $status;
});
return $status;
}
public static function statusFetch($url)
@ -385,12 +391,14 @@ class Helpers {
foreach($attachments as $media) {
$type = $media['mediaType'];
$url = $media['url'];
$blurhash = isset($media['blurhash']) ? $media['blurhash'] : null;
$valid = self::validateUrl($url);
if(in_array($type, $allowed) == false || $valid == false) {
continue;
}
$media = new Media();
$media->blurhash = $blurhash;
$media->remote_media = true;
$media->status_id = $status->id;
$media->profile_id = $status->profile_id;
@ -398,7 +406,12 @@ class Helpers {
$media->media_path = $url;
$media->remote_url = $url;
$media->mime = $type;
$media->version = 3;
$media->save();
if(config('pixelfed.cloud_storage') == true) {
MediaStoragePipeline::dispatch($media);
}
}
$status->viewType();
@ -425,6 +438,7 @@ class Helpers {
->whereUsername($id)
->firstOrFail();
}
$res = self::fetchProfileFromUrl($url);
if(isset($res['id']) == false) {
return;
@ -460,10 +474,7 @@ class Helpers {
$profile->webfinger = strtolower(Purify::clean($webfinger));
$profile->last_fetched_at = now();
$profile->save();
if($runJobs == true) {
// RemoteFollowImportRecent::dispatch($res, $profile);
CreateAvatar::dispatch($profile);
}
RemoteAvatarFetch::dispatch($profile);
return $profile;
});
} else {
@ -477,6 +488,7 @@ class Helpers {
$profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) && Helpers::validateUrl($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null;
$profile->save();
}
RemoteAvatarFetch::dispatch($profile);
}
return $profile;
});

View File

@ -43,7 +43,7 @@ class HttpSignature {
$digest = self::_digest($body);
}
$headers = self::_headersToSign($url, $body ? $digest : false);
$headers = array_merge($headers, $addlHeaders);
$headers = array_unique(array_merge($headers, $addlHeaders));
$stringToSign = self::_headersToSigningString($headers);
$signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
$key = openssl_pkey_get_private($privateKey);
@ -53,7 +53,7 @@ class HttpSignature {
unset($headers['(request-target)']);
$headers['Signature'] = $signatureHeader;
return self::_headersToCurlArray($headers);
return $headers;
}
public static function parseSignatureHeader($signature) {

View File

@ -87,6 +87,14 @@ class RestrictedNames
'assets',
'public',
'storage',
'htaccess',
'.htaccess',
'favicon.ico',
'embed.js',
'index.php',
'manifest.json',
'mix-manifest.json',
'robots.txt',
// Laravel Horizon
'horizon',
@ -147,7 +155,6 @@ class RestrictedNames
'driver',
'e',
'embed',
'embed.js',
'email',
'emails',
'error',
@ -191,7 +198,6 @@ class RestrictedNames
'invites',
'import',
'imports',
'index.php',
'j',
'js',
'k',
@ -329,6 +335,7 @@ class RestrictedNames
$reserved = self::$reserved;
$res = array_merge($additional, $reserved, $banned);
$res = array_unique($res);
sort($res);
return $res;

View File

@ -4,7 +4,7 @@ namespace App\Util\Media;
use App\Media;
use Image as Intervention;
use Cache, Storage;
use Cache, Log, Storage;
class Image
{
@ -165,30 +165,32 @@ class Image
$quality = config('pixelfed.image_quality');
$img->save($newPath, $quality);
$media->width = $img->width();
$media->height = $img->height();
$img->destroy();
if (!$thumbnail) {
$media->orientation = $orientation;
}
if ($thumbnail == true) {
$media->thumbnail_path = $converted['path'];
$media->thumbnail_url = url(Storage::url($converted['path']));
} else {
$media->width = $img->width();
$media->height = $img->height();
$media->orientation = $orientation;
$media->media_path = $converted['path'];
$media->mime = $img->mime;
}
$img->destroy();
$media->save();
if($thumbnail) {
$this->generateBlurhash($media);
}
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
Cache::forget('status:thumb:'.$media->status_id);
} catch (Exception $e) {
$media->processed_at = now();
$media->save();
Log::info('MediaResizeException: Could not process media id: ' . $media->id);
}
}

View File

@ -15,12 +15,50 @@ class Bouncer {
return;
}
$recentKey = 'pf:bouncer:recent_by_pid:' . $status->profile_id;
$recentTtl = now()->addMinutes(5);
$recent = Cache::remember($recentKey, $recentTtl, function() use($status) {
return $status->profile->created_at->gt(now()->subMonths(2)) || $status->profile->statuses()->count() == 0;
$exemptionKey = 'pf:bouncer_v0:exemption_by_pid:' . $status->profile_id;
$exemptionTtl = now()->addDays(12);
$exemption = Cache::remember($exemptionKey, $exemptionTtl, function() use($status) {
$uid = $status->profile->user_id;
$ids = AccountInterstitial::whereUserId($uid)
->whereType('post.autospam')
->whereItemType('App\Status')
->whereNotNull('appeal_handled_at')
->latest()
->take(5)
->pluck('item_id');
if($ids->count() == 0) {
return false;
}
$count = Status::select('id', 'scope')
->whereScope('public')
->find($ids)
->count();
return $count >= 1 ? true : false;
});
if($exemption == true) {
return;
}
$recentKey = 'pf:bouncer_v0:recent_by_pid:' . $status->profile_id;
$recentTtl = now()->addHours(28);
$recent = Cache::remember($recentKey, $recentTtl, function() use($status) {
return $status
->profile
->created_at
->gt(now()->subMonths(6)) ||
$status
->profile
->statuses()
->whereScope('public')
->count() == 0;
});
if(!$recent) {
return;
}
@ -29,7 +67,16 @@ class Bouncer {
return;
}
if(!Str::contains($status->caption, ['https://', 'http://', 'hxxps://', 'hxxp://', 'www.', '.com', '.net', '.org'])) {
if(!Str::contains($status->caption, [
'https://',
'http://',
'hxxps://',
'hxxp://',
'www.',
'.com',
'.net',
'.org'
])) {
return;
}
@ -74,6 +121,8 @@ class Bouncer {
$status->is_nsfw = true;
$status->save();
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $status->profile_id);
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $status->profile_id);
}
}

View File

@ -8,7 +8,7 @@ use Illuminate\Support\Str;
class Config {
public static function get() {
return Cache::remember('api:site:configuration:_v0.1', now()->addHours(30), function() {
return Cache::remember('api:site:configuration:_v0.2', now()->addHours(30), function() {
return [
'open_registration' => config('pixelfed.open_registration'),
'uploader' => [
@ -62,6 +62,13 @@ class Config {
'instagram' => config('pixelfed.import.instagram.enabled'),
'mastodon' => false,
'pixelfed' => false
],
'label' => [
'covid' => [
'enabled' => config('instance.label.covid.enabled'),
'org' => config('instance.label.covid.org'),
'url' => config('instance.label.covid.url'),
]
]
]
];

View File

@ -44,6 +44,7 @@
"symfony/http-kernel": "5.1.5"
},
"require-dev": {
"brianium/paratest": "^6.1",
"facade/ignition": "^2.3.6",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",

296
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b4d25a7ba9e07f08e9ddacc2ddf5cfc1",
"content-hash": "eab416feda81875b20d5df2399f9ed86",
"packages": [
{
"name": "alchemy/binary-driver",
@ -130,16 +130,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.171.16",
"version": "3.172.0",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "216ff33ce238c30cf793973262ea727f2ce41224"
"reference": "5a5e66c4d54c392042820703eeb8a6bd3d222924"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/216ff33ce238c30cf793973262ea727f2ce41224",
"reference": "216ff33ce238c30cf793973262ea727f2ce41224",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5a5e66c4d54c392042820703eeb8a6bd3d222924",
"reference": "5a5e66c4d54c392042820703eeb8a6bd3d222924",
"shasum": ""
},
"require": {
@ -214,9 +214,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.171.16"
"source": "https://github.com/aws/aws-sdk-php/tree/3.172.0"
},
"time": "2021-01-12T19:12:49+00:00"
"time": "2021-01-22T19:21:38+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -1971,23 +1971,23 @@
},
{
"name": "jaybizzle/crawler-detect",
"version": "v1.2.103",
"version": "v1.2.104",
"source": {
"type": "git",
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
"reference": "3efa2860959cc971f17624b40bf0699823f9d0f3"
"reference": "a581e89a9212c4e9d18049666dc735718c29de9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/3efa2860959cc971f17624b40bf0699823f9d0f3",
"reference": "3efa2860959cc971f17624b40bf0699823f9d0f3",
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/a581e89a9212c4e9d18049666dc735718c29de9c",
"reference": "a581e89a9212c4e9d18049666dc735718c29de9c",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.5|^6.5"
"phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4"
},
"type": "library",
"autoload": {
@ -2017,9 +2017,9 @@
],
"support": {
"issues": "https://github.com/JayBizzle/Crawler-Detect/issues",
"source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.103"
"source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.104"
},
"time": "2020-11-23T19:49:25+00:00"
"time": "2021-01-13T15:25:20+00:00"
},
{
"name": "jenssegers/agent",
@ -2106,16 +2106,16 @@
},
{
"name": "laravel/framework",
"version": "v8.22.1",
"version": "v8.25.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "5c70991b96c5722afed541a996479b5112654c8b"
"reference": "05da44d6823c2923597519ac10151f5827a24f80"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/5c70991b96c5722afed541a996479b5112654c8b",
"reference": "5c70991b96c5722afed541a996479b5112654c8b",
"url": "https://api.github.com/repos/laravel/framework/zipball/05da44d6823c2923597519ac10151f5827a24f80",
"reference": "05da44d6823c2923597519ac10151f5827a24f80",
"shasum": ""
},
"require": {
@ -2202,6 +2202,7 @@
},
"suggest": {
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).",
"brianium/paratest": "Required to run tests in parallel (^6.0).",
"doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).",
"ext-ftp": "Required to use the Flysystem FTP driver.",
"ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
@ -2269,7 +2270,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2021-01-13T13:37:56+00:00"
"time": "2021-01-26T14:40:21+00:00"
},
{
"name": "laravel/helpers",
@ -3097,16 +3098,16 @@
},
{
"name": "league/mime-type-detection",
"version": "1.5.1",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
"reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa"
"reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/353f66d7555d8a90781f6f5e7091932f9a4250aa",
"reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa",
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
"reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
"shasum": ""
},
"require": {
@ -3114,8 +3115,9 @@
"php": "^7.2 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.36",
"phpunit/phpunit": "^8.5.8"
"friendsofphp/php-cs-fixer": "^2.18",
"phpstan/phpstan": "^0.12.68",
"phpunit/phpunit": "^8.5.8 || ^9.3"
},
"type": "library",
"autoload": {
@ -3136,7 +3138,7 @@
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.5.1"
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0"
},
"funding": [
{
@ -3148,7 +3150,7 @@
"type": "tidelift"
}
],
"time": "2020-10-18T11:50:25+00:00"
"time": "2021-01-18T20:58:21+00:00"
},
{
"name": "league/oauth2-server",
@ -3239,16 +3241,16 @@
},
{
"name": "mobiledetect/mobiledetectlib",
"version": "2.8.34",
"version": "2.8.35",
"source": {
"type": "git",
"url": "https://github.com/serbanghita/Mobile-Detect.git",
"reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b"
"reference": "68a35170fdf36e7b35f9c125e5102338dbc3ff65"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
"reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b",
"url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/68a35170fdf36e7b35f9c125e5102338dbc3ff65",
"reference": "68a35170fdf36e7b35f9c125e5102338dbc3ff65",
"shasum": ""
},
"require": {
@ -3289,9 +3291,9 @@
],
"support": {
"issues": "https://github.com/serbanghita/Mobile-Detect/issues",
"source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.34"
"source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.35"
},
"time": "2019-09-18T18:44:20+00:00"
"time": "2021-01-25T19:09:34+00:00"
},
{
"name": "monolog/monolog",
@ -3904,16 +3906,16 @@
},
{
"name": "pbmedia/laravel-ffmpeg",
"version": "7.5.4",
"version": "7.5.5",
"source": {
"type": "git",
"url": "https://github.com/protonemedia/laravel-ffmpeg.git",
"reference": "72bb005b4be13710663e7de9077d32c7a76158a3"
"reference": "460b879f7b1b6333ee02fe1fa35d6ff5bc4c0ea0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/72bb005b4be13710663e7de9077d32c7a76158a3",
"reference": "72bb005b4be13710663e7de9077d32c7a76158a3",
"url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/460b879f7b1b6333ee02fe1fa35d6ff5bc4c0ea0",
"reference": "460b879f7b1b6333ee02fe1fa35d6ff5bc4c0ea0",
"shasum": ""
},
"require": {
@ -3977,7 +3979,7 @@
],
"support": {
"issues": "https://github.com/protonemedia/laravel-ffmpeg/issues",
"source": "https://github.com/protonemedia/laravel-ffmpeg/tree/7.5.4"
"source": "https://github.com/protonemedia/laravel-ffmpeg/tree/7.5.5"
},
"funding": [
{
@ -3985,7 +3987,7 @@
"type": "github"
}
],
"time": "2021-01-07T08:06:09+00:00"
"time": "2021-01-18T14:48:50+00:00"
},
{
"name": "php-ffmpeg/php-ffmpeg",
@ -4976,16 +4978,16 @@
},
{
"name": "psy/psysh",
"version": "v0.10.5",
"version": "v0.10.6",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
"reference": "7c710551d4a2653afa259c544508dc18a9098956"
"reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/7c710551d4a2653afa259c544508dc18a9098956",
"reference": "7c710551d4a2653afa259c544508dc18a9098956",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/6f990c19f91729de8b31e639d6e204ea59f19cf3",
"reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3",
"shasum": ""
},
"require": {
@ -5014,7 +5016,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.10.x-dev"
"dev-main": "0.10.x-dev"
}
},
"autoload": {
@ -5046,9 +5048,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
"source": "https://github.com/bobthecow/psysh/tree/v0.10.5"
"source": "https://github.com/bobthecow/psysh/tree/v0.10.6"
},
"time": "2020-12-04T02:51:30+00:00"
"time": "2021-01-18T15:53:43+00:00"
},
{
"name": "ralouphie/getallheaders",
@ -5096,16 +5098,16 @@
},
{
"name": "ramsey/collection",
"version": "1.1.1",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "24d93aefb2cd786b7edd9f45b554aea20b28b9b1"
"reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/24d93aefb2cd786b7edd9f45b554aea20b28b9b1",
"reference": "24d93aefb2cd786b7edd9f45b554aea20b28b9b1",
"url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1",
"reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1",
"shasum": ""
},
"require": {
@ -5115,19 +5117,19 @@
"captainhook/captainhook": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"ergebnis/composer-normalize": "^2.6",
"fzaninotto/faker": "^1.5",
"fakerphp/faker": "^1.5",
"hamcrest/hamcrest-php": "^2",
"jangregor/phpstan-prophecy": "^0.6",
"jangregor/phpstan-prophecy": "^0.8",
"mockery/mockery": "^1.3",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^0.12.32",
"phpstan/phpstan-mockery": "^0.12.5",
"phpstan/phpstan-phpunit": "^0.12.11",
"phpunit/phpunit": "^8.5",
"phpunit/phpunit": "^8.5 || ^9",
"psy/psysh": "^0.10.4",
"slevomat/coding-standard": "^6.3",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^3.12.2"
"vimeo/psalm": "^4.4"
},
"type": "library",
"autoload": {
@ -5157,15 +5159,19 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/1.1.1"
"source": "https://github.com/ramsey/collection/tree/1.1.3"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
"type": "tidelift"
}
],
"time": "2020-09-10T20:58:17+00:00"
"time": "2021-01-21T17:40:04+00:00"
},
{
"name": "ramsey/uuid",
@ -5261,16 +5267,16 @@
},
{
"name": "spatie/db-dumper",
"version": "2.18.0",
"version": "2.20.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/db-dumper.git",
"reference": "eddb2b7c6877817d97bbdc1c60d1a800bf5a267a"
"reference": "6a9004885b6de8417c2a5e1aa9e3712b49c1c59d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/db-dumper/zipball/eddb2b7c6877817d97bbdc1c60d1a800bf5a267a",
"reference": "eddb2b7c6877817d97bbdc1c60d1a800bf5a267a",
"url": "https://api.github.com/repos/spatie/db-dumper/zipball/6a9004885b6de8417c2a5e1aa9e3712b49c1c59d",
"reference": "6a9004885b6de8417c2a5e1aa9e3712b49c1c59d",
"shasum": ""
},
"require": {
@ -5309,7 +5315,7 @@
],
"support": {
"issues": "https://github.com/spatie/db-dumper/issues",
"source": "https://github.com/spatie/db-dumper/tree/2.18.0"
"source": "https://github.com/spatie/db-dumper/tree/2.20.0"
},
"funding": [
{
@ -5317,7 +5323,7 @@
"type": "github"
}
],
"time": "2020-11-10T09:20:18+00:00"
"time": "2021-01-26T07:44:13+00:00"
},
{
"name": "spatie/image-optimizer",
@ -5375,16 +5381,16 @@
},
{
"name": "spatie/laravel-backup",
"version": "6.14.2",
"version": "6.14.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-backup.git",
"reference": "3374e1eeb09ef32c6bfd495ae1f2f4de4b594922"
"reference": "8a4c95bffffde831edaca64bdef55aac213d0eef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-backup/zipball/3374e1eeb09ef32c6bfd495ae1f2f4de4b594922",
"reference": "3374e1eeb09ef32c6bfd495ae1f2f4de4b594922",
"url": "https://api.github.com/repos/spatie/laravel-backup/zipball/8a4c95bffffde831edaca64bdef55aac213d0eef",
"reference": "8a4c95bffffde831edaca64bdef55aac213d0eef",
"shasum": ""
},
"require": {
@ -5448,7 +5454,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-backup/issues",
"source": "https://github.com/spatie/laravel-backup/tree/6.14.2"
"source": "https://github.com/spatie/laravel-backup/tree/6.14.3"
},
"funding": [
{
@ -5460,7 +5466,7 @@
"type": "other"
}
],
"time": "2020-12-23T10:13:12+00:00"
"time": "2021-01-15T13:25:43+00:00"
},
{
"name": "spatie/laravel-image-optimizer",
@ -8069,16 +8075,16 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v5.2.0",
"version": "v5.3.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "fba64139db67123c7a57072e5f8d3db10d160b66"
"reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/fba64139db67123c7a57072e5f8d3db10d160b66",
"reference": "fba64139db67123c7a57072e5f8d3db10d160b66",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56",
"reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56",
"shasum": ""
},
"require": {
@ -8093,7 +8099,7 @@
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"ext-filter": "*",
"phpunit/phpunit": "^7.5.20 || ^8.5.2 || ^9.0"
"phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1"
},
"suggest": {
"ext-filter": "Required to use the boolean validator."
@ -8101,7 +8107,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.2-dev"
"dev-master": "5.3-dev"
}
},
"autoload": {
@ -8133,7 +8139,7 @@
],
"support": {
"issues": "https://github.com/vlucas/phpdotenv/issues",
"source": "https://github.com/vlucas/phpdotenv/tree/v5.2.0"
"source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0"
},
"funding": [
{
@ -8145,7 +8151,7 @@
"type": "tidelift"
}
],
"time": "2020-09-14T15:57:31+00:00"
"time": "2021-01-20T15:23:13+00:00"
},
{
"name": "voku/portable-ascii",
@ -8226,12 +8232,12 @@
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
"shasum": ""
},
@ -8269,13 +8275,93 @@
"validate"
],
"support": {
"issues": "https://github.com/webmozart/assert/issues",
"source": "https://github.com/webmozart/assert/tree/master"
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/1.9.1"
},
"time": "2020-07-08T17:02:28+00:00"
}
],
"packages-dev": [
{
"name": "brianium/paratest",
"version": "v6.1.2",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
"reference": "235db99a43401d68fdc4495b20b49291ea2e767d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/235db99a43401d68fdc4495b20b49291ea2e767d",
"reference": "235db99a43401d68fdc4495b20b49291ea2e767d",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-simplexml": "*",
"php": "^7.3 || ^8.0",
"phpunit/php-code-coverage": "^9.2.5",
"phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-timer": "^5.0.3",
"phpunit/phpunit": "^9.5.0",
"sebastian/environment": "^5.1.3",
"symfony/console": "^4.4 || ^5.2",
"symfony/process": "^4.4 || ^5.2"
},
"require-dev": {
"doctrine/coding-standard": "^8.2.0",
"ekino/phpstan-banned-code": "^0.3.1",
"ergebnis/phpstan-rules": "^0.15.3",
"ext-posix": "*",
"infection/infection": "^0.18.2",
"phpstan/phpstan": "^0.12.58",
"phpstan/phpstan-deprecation-rules": "^0.12.5",
"phpstan/phpstan-phpunit": "^0.12.16",
"phpstan/phpstan-strict-rules": "^0.12.5",
"squizlabs/php_codesniffer": "^3.5.8",
"symfony/filesystem": "^5.2.0",
"thecodingmachine/phpstan-strict-rules": "^0.12.1",
"vimeo/psalm": "^4.3.1"
},
"bin": [
"bin/paratest"
],
"type": "library",
"autoload": {
"psr-4": {
"ParaTest\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Scaturro",
"email": "scaturrob@gmail.com",
"homepage": "http://brianscaturro.com",
"role": "Lead"
}
],
"description": "Parallel testing for PHP",
"homepage": "https://github.com/paratestphp/paratest",
"keywords": [
"concurrent",
"parallel",
"phpunit",
"testing"
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
"source": "https://github.com/paratestphp/paratest/tree/v6.1.2"
},
"time": "2020-12-15T11:41:54+00:00"
},
{
"name": "doctrine/instantiator",
"version": "1.4.0",
@ -8412,16 +8498,16 @@
},
{
"name": "facade/ignition",
"version": "2.5.8",
"version": "2.5.9",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition.git",
"reference": "8e907d81244649c5ea746e2ec30c32c5f59df472"
"reference": "66b3138ecce38024723fb3bfc66ef8852a779ea9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition/zipball/8e907d81244649c5ea746e2ec30c32c5f59df472",
"reference": "8e907d81244649c5ea746e2ec30c32c5f59df472",
"url": "https://api.github.com/repos/facade/ignition/zipball/66b3138ecce38024723fb3bfc66ef8852a779ea9",
"reference": "66b3138ecce38024723fb3bfc66ef8852a779ea9",
"shasum": ""
},
"require": {
@ -8485,7 +8571,7 @@
"issues": "https://github.com/facade/ignition/issues",
"source": "https://github.com/facade/ignition"
},
"time": "2020-12-29T09:12:55+00:00"
"time": "2021-01-26T14:45:19+00:00"
},
{
"name": "facade/ignition-contracts",
@ -8542,16 +8628,16 @@
},
{
"name": "filp/whoops",
"version": "2.9.1",
"version": "2.9.2",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "307fb34a5ab697461ec4c9db865b20ff2fd40771"
"reference": "df7933820090489623ce0be5e85c7e693638e536"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/307fb34a5ab697461ec4c9db865b20ff2fd40771",
"reference": "307fb34a5ab697461ec4c9db865b20ff2fd40771",
"url": "https://api.github.com/repos/filp/whoops/zipball/df7933820090489623ce0be5e85c7e693638e536",
"reference": "df7933820090489623ce0be5e85c7e693638e536",
"shasum": ""
},
"require": {
@ -8601,9 +8687,15 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.9.1"
"source": "https://github.com/filp/whoops/tree/2.9.2"
},
"time": "2020-11-01T12:00:00+00:00"
"funding": [
{
"url": "https://github.com/denis-sokolov",
"type": "github"
}
],
"time": "2021-01-24T12:00:00+00:00"
},
{
"name": "fzaninotto/faker",
@ -8843,16 +8935,16 @@
},
{
"name": "nunomaduro/collision",
"version": "v5.2.0",
"version": "v5.3.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "aca954fd03414ba0dd85d7d8e42ba9b251893d1f"
"reference": "aca63581f380f63a492b1e3114604e411e39133a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/aca954fd03414ba0dd85d7d8e42ba9b251893d1f",
"reference": "aca954fd03414ba0dd85d7d8e42ba9b251893d1f",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/aca63581f380f63a492b1e3114604e411e39133a",
"reference": "aca63581f380f63a492b1e3114604e411e39133a",
"shasum": ""
},
"require": {
@ -8927,7 +9019,7 @@
"type": "patreon"
}
],
"time": "2021-01-13T10:00:08+00:00"
"time": "2021-01-25T15:34:13+00:00"
},
{
"name": "phar-io/manifest",
@ -9585,16 +9677,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.0",
"version": "9.5.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "8e16c225d57c3d6808014df6b1dd7598d0a5bbbe"
"reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e16c225d57c3d6808014df6b1dd7598d0a5bbbe",
"reference": "8e16c225d57c3d6808014df6b1dd7598d0a5bbbe",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7bdf4085de85a825f4424eae52c99a1cec2f360",
"reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360",
"shasum": ""
},
"require": {
@ -9672,7 +9764,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.0"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.1"
},
"funding": [
{
@ -9684,7 +9776,7 @@
"type": "github"
}
],
"time": "2020-12-04T05:05:53+00:00"
"time": "2021-01-17T07:42:25+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@ -37,6 +37,13 @@ return [
'followLinks' => false,
],
'mysql' => [
'dump' => [
'useSingleTransaction' => true,
'useQuick' => true,
],
],
/*
* The names of the connections to the databases that should be backed up
* MySQL, PostgreSQL, SQLite and Mongo databases are supported.
@ -49,7 +56,7 @@ return [
/*
* The database dump can be gzipped to decrease diskspace usage.
*/
'gzip_database_dump' => false,
'gzip_database_dump' => true,
'destination' => [
@ -62,7 +69,7 @@ return [
* The disk names on which the backups will be stored.
*/
'disks' => [
'local',
'local'
],
],
],

View File

@ -70,7 +70,7 @@ return [
'redis' => [
'driver' => 'redis',
'client' => env('REDIS_CLIENT', 'predis'),
'client' => env('REDIS_CLIENT', 'phpredis'),
'default' => [
'scheme' => env('REDIS_SCHEME', 'tcp'),

View File

@ -36,6 +36,7 @@ return [
'body' => env('PAGE_503_BODY', 'Our service is in maintenance mode, please try again later.')
]
],
'username' => [
'banned' => env('BANNED_USERNAMES'),
'remote' => [
@ -61,5 +62,13 @@ return [
'enabled' => env('OAUTH_PAT_ENABLED', false),
'id' => env('OAUTH_PAT_ID'),
]
]
],
'label' => [
'covid' => [
'enabled' => env('ENABLE_COVID_LABEL', true),
'url' => env('COVID_LABEL_URL', 'https://www.who.int/emergencies/diseases/novel-coronavirus-2019/advice-for-public'),
'org' => env('COVID_LABEL_ORG', 'visit the WHO website')
]
],
];

View File

@ -23,7 +23,7 @@ return [
| This value is the version of your Pixelfed instance.
|
*/
'version' => '0.10.9',
'version' => '0.10.10',
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddCdnUrlToAvatarsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('avatars', function (Blueprint $table) {
$table->string('cdn_url')->unique()->index()->nullable()->after('remote_url');
$table->unsignedInteger('size')->nullable()->after('cdn_url');
$table->boolean('is_remote')->nullable()->index()->after('cdn_url');
$table->dropColumn('thumb_path');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('avatars', function (Blueprint $table) {
$table->dropColumn('cdn_url');
$table->dropColumn('size');
$table->dropColumn('is_remote');
$table->string('thumb_path')->nullable();
});
}
}

12255
package-lock.json generated

File diff suppressed because it is too large Load Diff

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/my2020.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[18],{19:function(t,e,s){t.exports=s("ETg6")},"7wkd":function(t,e,s){"use strict";s.r(e);var o={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(e){if(0==e.data.length)return t.showLoadMore=!1,void(t.loaded=!0);t.profiles=e.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}}},a=s("KHd+"),n=Object(a.a)(o,(function(){var t=this,e=t.$createElement,s=t._self._c||e;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,o){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,o){return s("span",{key:"profile_posts_"+o,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(e){return t.loadMore()}}},[t._v("Load More")])])]):t._e()],2)]):s("div",[t._m(0)])])])}),[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"row"},[e("div",{staticClass:"col-12 d-flex justify-content-center align-items-center"},[e("div",{staticClass:"spinner-border",attrs:{role:"status"}},[e("span",{staticClass:"sr-only"},[this._v("Loading...")])])])])}],!1,null,"7b3eea1c",null);e.default=n.exports},ETg6:function(t,e,s){Vue.component("profile-directory",s("7wkd").default)},"KHd+":function(t,e,s){"use strict";function o(t,e,s,o,a,n,r,i){var c,d="function"==typeof t?t.options:t;if(e&&(d.render=e,d.staticRenderFns=s,d._compiled=!0),o&&(d.functional=!0),n&&(d._scopeId="data-v-"+n),r?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),a&&a.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(r)},d._ssrRegister=c):a&&(c=i?function(){a.call(this,this.$root.$options.shadowRoot)}:a),c)if(d.functional){d._injectStyles=c;var l=d.render;d.render=function(t,e){return c.call(e),l(t,e)}}else{var u=d.beforeCreate;d.beforeCreate=u?[].concat(u,c):[c]}return{exports:t,options:d}}s.d(e,"a",(function(){return o}))}},[[19,0]]]);
(window.webpackJsonp=window.webpackJsonp||[]).push([[19],{19:function(t,e,s){t.exports=s("ETg6")},"7wkd":function(t,e,s){"use strict";s.r(e);var o={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(e){if(0==e.data.length)return t.showLoadMore=!1,void(t.loaded=!0);t.profiles=e.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}}},a=s("KHd+"),n=Object(a.a)(o,(function(){var t=this,e=t.$createElement,s=t._self._c||e;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,o){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,o){return s("span",{key:"profile_posts_"+o,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(e){return t.loadMore()}}},[t._v("Load More")])])]):t._e()],2)]):s("div",[t._m(0)])])])}),[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"row"},[e("div",{staticClass:"col-12 d-flex justify-content-center align-items-center"},[e("div",{staticClass:"spinner-border",attrs:{role:"status"}},[e("span",{staticClass:"sr-only"},[this._v("Loading...")])])])])}],!1,null,"7b3eea1c",null);e.default=n.exports},ETg6:function(t,e,s){Vue.component("profile-directory",s("7wkd").default)},"KHd+":function(t,e,s){"use strict";function o(t,e,s,o,a,n,r,i){var c,d="function"==typeof t?t.options:t;if(e&&(d.render=e,d.staticRenderFns=s,d._compiled=!0),o&&(d.functional=!0),n&&(d._scopeId="data-v-"+n),r?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),a&&a.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(r)},d._ssrRegister=c):a&&(c=i?function(){a.call(this,this.$root.$options.shadowRoot)}:a),c)if(d.functional){d._injectStyles=c;var l=d.render;d.render=function(t,e){return c.call(e),l(t,e)}}else{var u=d.beforeCreate;d.beforeCreate=u?[].concat(u,c):[c]}return{exports:t,options:d}}s.d(e,"a",(function(){return o}))}},[[19,0]]]);

File diff suppressed because one or more lines are too long

2
public/js/quill.js vendored

File diff suppressed because one or more lines are too long

2
public/js/rempos.js vendored

File diff suppressed because one or more lines are too long

2
public/js/rempro.js vendored

File diff suppressed because one or more lines are too long

2
public/js/search.js vendored

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[25],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],(function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)})),ace.require(["ace/theme/monokai"],(function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)}))}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);
(window.webpackJsonp=window.webpackJsonp||[]).push([[26],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],(function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)})),ace.require(["ace/theme/monokai"],(function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)}))}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);

File diff suppressed because one or more lines are too long

2
public/js/vendor.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,17 +1,17 @@
{
"/js/manifest.js": "/js/manifest.js?id=7db827d654313dce4250",
"/js/vendor.js": "/js/vendor.js?id=b9760eb405c3c32b09cf",
"/js/vendor.js": "/js/vendor.js?id=92f80b74af583bf54d69",
"/js/ace.js": "/js/ace.js?id=11e5550a450fece75c33",
"/js/activity.js": "/js/activity.js?id=85479715e399b3489d25",
"/js/app.js": "/js/app.js?id=30b60018fc7e78141292",
"/js/app.js": "/js/app.js?id=fdbdd51482b98e1324e8",
"/css/app.css": "/css/app.css?id=77729cabd5c8a0ad09b8",
"/css/appdark.css": "/css/appdark.css?id=995ec87dd4aff426cd1c",
"/css/landing.css": "/css/landing.css?id=3092e86721fa8b922c06",
"/css/quill.css": "/css/quill.css?id=e3741782d15a3031f785",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=c6a07cb79dd7d6c7b8a0",
"/js/collections.js": "/js/collections.js?id=6f64a9032085ebac28b3",
"/js/components.js": "/js/components.js?id=88296701f1382d285031",
"/js/compose.js": "/js/compose.js?id=1848b13369edc4e791ec",
"/js/components.js": "/js/components.js?id=51f4666393c2eee7a284",
"/js/compose.js": "/js/compose.js?id=995965cdcd1265bda2af",
"/js/compose-classic.js": "/js/compose-classic.js?id=283f19c895f4118a2a8b",
"/js/developers.js": "/js/developers.js?id=f75deca5ccf47d43eb07",
"/js/direct.js": "/js/direct.js?id=e1e4a830bfedc1870db1",
@ -20,14 +20,15 @@
"/js/loops.js": "/js/loops.js?id=1dcb3790eb9ea4ea5848",
"/js/memoryprofile.js": "/js/memoryprofile.js?id=75ea0503eca4f7ad3642",
"/js/mode-dot.js": "/js/mode-dot.js?id=dd9c87024fbaa8e75ac4",
"/js/profile.js": "/js/profile.js?id=889a57bbb59e82d2d436",
"/js/profile-directory.js": "/js/profile-directory.js?id=855a548efdf56b8594bf",
"/js/quill.js": "/js/quill.js?id=866b31b9b9540305751d",
"/js/rempos.js": "/js/rempos.js?id=fe15468206b24bb81ae0",
"/js/rempro.js": "/js/rempro.js?id=6e7fc68a65bf82d5aec8",
"/js/search.js": "/js/search.js?id=5467143312ebae07695b",
"/js/status.js": "/js/status.js?id=a2e24add6e24284a5cb9",
"/js/story-compose.js": "/js/story-compose.js?id=13f9606a3c4ce7acf17b",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=8842103833ba4861bcfa",
"/js/timeline.js": "/js/timeline.js?id=f8859af911cef06e3d8e"
"/js/my2020.js": "/js/my2020.js?id=31aeb1c22e0a5a99b0a8",
"/js/profile.js": "/js/profile.js?id=2e251656c84e1c283582",
"/js/profile-directory.js": "/js/profile-directory.js?id=2386392b464e9088a859",
"/js/quill.js": "/js/quill.js?id=4769f11fc9a6c32dde50",
"/js/rempos.js": "/js/rempos.js?id=f4325d9c7ee4b5165a00",
"/js/rempro.js": "/js/rempro.js?id=38ad09b48b084cd82995",
"/js/search.js": "/js/search.js?id=edd612b83049bf276b19",
"/js/status.js": "/js/status.js?id=5fc1e46961585fd79519",
"/js/story-compose.js": "/js/story-compose.js?id=b31904f8fd5b397f8c54",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=85f0af57479412548223",
"/js/timeline.js": "/js/timeline.js?id=9696ba2673a38b912fc3"
}

View File

@ -96,6 +96,24 @@ window.App.util = {
return interval + "m";
}
return Math.floor(seconds) + "s";
}),
rewriteLinks: (function(i) {
let tag = i.innerText;
if(i.href.startsWith(window.location.origin)) {
return i.href;
}
if(tag.startsWith('#') == true) {
tag = '/discover/tags/' + tag.substr(1) +'?src=rph';
} else if(tag.startsWith('@') == true) {
tag = '/' + i.innerText + '?src=rpp';
} else {
tag = '/i/redirect?url=' + encodeURIComponent(tag);
}
return tag;
})
},
filters: [

View File

@ -4,7 +4,10 @@ import InfiniteLoading from 'vue-infinite-loading';
import Loading from 'vue-loading-overlay';
import VueTimeago from 'vue-timeago';
import VueCarousel from 'vue-carousel';
import VueBlurHash from 'vue-blurhash'
import 'vue-blurhash/dist/vue-blurhash.css'
Vue.use(VueBlurHash);
Vue.use(VueCarousel);
Vue.use(BootstrapVue);
Vue.use(InfiniteLoading);

View File

@ -86,10 +86,46 @@
<span v-else>
<a v-if="!pageLoading && (page > 1 && page <= 2) || (page == 1 && ids.length != 0) || page == 'cropPhoto'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a>
<a v-if="!pageLoading && page == 3" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
<a v-if="!pageLoading && page == 'addText'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="composeTextPost()">Post</a>
</span>
</div>
</div>
<div class="card-body p-0 border-top">
<div v-if="page == 'textOptions'" class="w-100 h-100" style="min-height: 280px;">
test
</div>
<div v-if="page == 'addText'" class="w-100 h-100" style="min-height: 280px;">
<div class="mt-2">
<div class="media px-3">
<div class="media-body">
<div class="form-group">
<label class="font-weight-bold text-muted small d-none">Body</label>
<textarea class="form-control border-0 rounded-0 no-focus" rows="7" placeholder="What's happening?" style="font-size:18px;resize:none" v-model="composeText" v-on:keyup="composeTextLength = composeText.length"></textarea>
<div class="border-bottom"></div>
<p class="help-text small text-right text-muted mb-0 font-weight-bold">{{composeTextLength}}/{{config.uploader.max_caption_length}}</p>
<p class="mb-0 mt-2">
<a class="btn btn-primary rounded-pill mr-2" href="#" style="height: 37px;" @click.prevent="showTextOptions()">
<i class="fas fa-palette px-3 text-white"></i>
</a>
<!-- <a class="btn btn-outline-lighter rounded-pill ml-3" href="#" @click.prevent="showLocationCard()">
<i class="fas fa-map-marker-alt px-3"></i>
</a>
<a class="btn btn-outline-lighter rounded-pill mx-3" href="#" @click.prevent="showTagCard()">
<i class="fas fa-user-plus px-3"></i>
</a> -->
<a class="btn rounded-pill mx-3 d-inline-flex align-items-center" href="#" :class="[nsfw ? 'btn-danger' : 'btn-outline-lighter']" style="height: 37px;" @click.prevent="nsfw = !nsfw" title="Mark as sensitive/not safe for work">
<i class="far fa-flag px-3"></i> <span class="text-muted small font-weight-bold"></span>
</a>
<a class="btn btn-outline-lighter rounded-pill d-inline-flex align-items-center" href="#" style="height: 37px;" @click.prevent="showVisibilityCard()">
<i class="fas fa-eye mr-2"></i> <span class="text-muted small font-weight-bold">{{visibilityTag}}</span>
</a>
</p>
</div>
</div>
</div>
</div>
</div>
<div v-if="page == 1" class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: 400px;">
<div class="text-center">
<div v-if="media.length == 0" class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark">
@ -107,6 +143,26 @@
</div>
</div>
</div>
<div v-if="config.ab.top == true && media.length == 0" class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark">
<div @click.prevent="addText" class="card-body">
<div class="media">
<div class="mr-3 align-items-center justify-content-center" style="display:inline-flex;width:40px;height:40px;border-radius: 100%;border: 2px solid #008DF5">
<i class="far fa-edit text-primary fa-lg"></i>
</div>
<div class="media-body text-left">
<p class="mb-0">
<span class="h5 mt-0 font-weight-bold text-primary">New Text Post</span>
<sup class="float-right mt-2">
<span class="btn btn-outline-lighter p-1 btn-sm font-weight-bold py-0" style="font-size:10px;line-height: 0.6">BETA</span>
</sup>
</p>
<p class="mb-0 text-muted">Share a text only post</p>
</div>
</div>
</div>
</div>
<a v-if="config.features.stories == true" class="card mx-md-5 my-md-3 shadow-none border compose-action text-decoration-none text-dark" href="/i/stories/new">
<div class="card-body">
<div class="media">
@ -349,6 +405,19 @@
<div v-if="page == 'advancedSettings'" class="w-100 h-100">
<div class="list-group list-group-flush">
<!-- <div class="d-none list-group-item d-flex justify-content-between">
<div>
<div class="text-dark ">Optimize Media</div>
<p v-if="mediaCropped" class="text-muted small mb-0">Media was cropped or filtered, it must be optimized.</p>
<p v-else class="text-muted small mb-0">Compress media for smaller file size.</p>
</div>
<div>
<div class="custom-control custom-switch" style="z-index: 9999;">
<input type="checkbox" class="custom-control-input" id="asoptimizemedia" v-model="optimizeMedia" :disabled="mediaCropped">
<label class="custom-control-label" for="asoptimizemedia"></label>
</div>
</div>
</div> -->
<div class="list-group-item d-flex justify-content-between">
<div>
<div class="text-dark ">Turn off commenting</div>
@ -591,6 +660,8 @@ export default {
nsfw: false,
place: false,
commentsDisabled: false,
optimizeMedia: true,
mediaCropped: false,
pageTitle: '',
cropper: {
@ -613,11 +684,13 @@ export default {
'addToStory',
'editMedia',
'cameraRoll',
'tagPeopleHelp'
'tagPeopleHelp',
'textOptions'
],
cameraRollMedia: [],
taggedUsernames: [],
taggedPeopleSearch: null
taggedPeopleSearch: null,
textMode: false
}
},
@ -664,6 +737,12 @@ export default {
el.removeAttr('disabled');
},
addText(event) {
this.pageTitle = 'New Text Post';
this.page = 'addText';
this.textMode = true;
},
mediaWatcher() {
let self = this;
$(document).on('change', '#pf-dz', function(e) {
@ -705,7 +784,7 @@ export default {
}
};
axios.post('/api/pixelfed/v1/media', form, xhrConfig)
axios.post('/api/compose/v0/media/upload', form, xhrConfig)
.then(function(e) {
self.uploadProgress = 100;
self.ids.push(e.data.id);
@ -747,7 +826,7 @@ export default {
}
let id = this.media[this.carouselCursor].id;
axios.delete('/api/pixelfed/v1/media', {
axios.delete('/api/compose/v0/media/delete', {
params: {
id: id
}
@ -794,9 +873,51 @@ export default {
cw: this.nsfw,
comments_disabled: this.commentsDisabled,
place: this.place,
tagged: this.taggedUsernames
tagged: this.taggedUsernames,
optimize_media: this.optimizeMedia
};
axios.post('/api/local/status/compose', data)
axios.post('/api/compose/v0/publish', data)
.then(res => {
let data = res.data;
window.location.href = data;
}).catch(err => {
let msg = err.response.data.message ? err.response.data.message : 'An unexpected error occured.'
swal('Oops, something went wrong!', msg, 'error');
});
return;
break;
case 'delete' :
this.ids = [];
this.media = [];
this.carouselCursor = 0;
this.composeText = '';
this.composeTextLength = 0;
$('#composeModal').modal('hide');
return;
break;
}
},
composeTextPost() {
let state = this.composeState;
if(this.composeText.length > this.config.uploader.max_caption_length) {
swal('Error', 'Caption is too long', 'error');
return;
}
switch(state) {
case 'publish' :
let data = {
caption: this.composeText,
visibility: this.visibility,
cw: this.nsfw,
comments_disabled: this.commentsDisabled,
place: this.place,
tagged: this.taggedUsernames,
};
axios.post('/api/compose/v0/publish/text', data)
.then(res => {
let data = res.data;
window.location.href = data;
@ -828,6 +949,14 @@ export default {
this.pageTitle = '';
switch(this.page) {
case 'addText':
this.page = 1;
break;
case 'textOptions':
this.page = 'addText';
break;
case 'cropPhoto':
case 'editMedia':
this.page = 2;
@ -838,7 +967,9 @@ export default {
break;
default:
this.namedPages.indexOf(this.page) != -1 ? this.page = 3 : this.page--;
this.namedPages.indexOf(this.page) != -1 ?
this.page = (this.textMode ? 'addText' : 3) :
(this.textMode ? 'addText' : this.page--);
break;
}
},
@ -860,10 +991,11 @@ export default {
imageSmoothingEnabled: false,
imageSmoothingQuality: 'high',
}).toBlob(function(blob) {
self.mediaCropped = true;
let data = new FormData();
data.append('file', blob);
let url = '/api/local/compose/media/update/' + self.ids[self.carouselCursor];
data.append('id', self.ids[self.carouselCursor]);
let url = '/api/compose/v0/media/update';
axios.post(url, data).then(res => {
self.media[self.carouselCursor].url = res.data.url;
self.pageLoading = false;
@ -921,7 +1053,7 @@ export default {
locationSearch(input) {
if (input.length < 1) { return []; };
let results = [];
return axios.get('/api/local/compose/location/search', {
return axios.get('/api/compose/v0/search/location', {
params: {
q: input
}
@ -936,8 +1068,8 @@ export default {
onSubmitLocation(result) {
this.place = result;
this.pageTitle = '';
this.page = 3;
this.pageTitle = this.textMode ? 'New Text Post' : '';
this.page = (this.textMode ? 'addText' : 3);
return;
},
@ -965,7 +1097,7 @@ export default {
this.visibility = state;
this.visibilityTag = tags[state];
this.pageTitle = '';
this.page = 3;
this.page = this.textMode ? 'addText' : 3;
},
showMediaDescriptionsCard() {
@ -1024,7 +1156,8 @@ export default {
canvas.toBlob(function(blob) {
data = new FormData();
data.append('file', blob);
axios.post('/api/local/compose/media/update/'+media.id, data).then(res => {
data.append('id', media.id);
axios.post('/api/compose/v0/media/update', data).then(res => {
}).catch(err => {
});
});
@ -1039,7 +1172,7 @@ export default {
if (input.length < 1) { return []; };
let self = this;
let results = [];
return axios.get('/api/local/compose/tag/search', {
return axios.get('/api/compose/v0/search/tag', {
params: {
q: input
}
@ -1070,6 +1203,11 @@ export default {
untagUsername(index) {
this.taggedUsernames.splice(index, 1);
},
showTextOptions() {
this.page = 'textOptions';
this.pageTitle = 'Text Post Options';
}
}
}

View File

@ -0,0 +1,239 @@
<template>
<div class="bg-dark text-white">
<div v-if="!loaded" style="height: 100vh;" class="d-flex justify-content-center align-items-center">
<div class="text-center">
<div class="spinner-border text-light" role="status">
<span class="sr-only">Loading...</span>
</div>
<p class="mb-0 lead mt-2">Loading</p>
</div>
</div>
<div v-if="loaded && notEnoughData" style="height: 100vh;" class="d-flex justify-content-center align-items-center">
<div class="text-center">
<p class="display-4">Oops!</p>
<p class="h3 font-weight-light py-3">We don't have enough data to display your <span class="font-weight-bold">#my2020</span>.</p>
<p class="mb-0 h5 font-weight-light">We hope to see you next year!</p>
</div>
</div>
<div v-if="loaded && !notEnoughData" class="d-flex justify-content-center align-items-center" style="width:100%;height:100vh;min-height:500px; padding: 0 15px;">
<div v-if="page == 1" class="text-center">
<p class="h1 font-weight-light">Hello {{user.username}}!</p>
<p class="h1 py-4">Your 2020 on Pixelfed.</p>
<p class="h4 font-weight-light mb-0 animate__animated animate__bounceInDown">Use the buttons below to navigate.</p>
</div>
<div v-if="page == 2" class="text-center mw-500">
<p class="display-4">User #<span class="font-weight-bold">{{stats.account.user_id}}</span></p>
<p class="h3 font-weight-light mb-0">You joined Pixelfed on {{stats.account.created_at}}</p>
</div>
<div v-if="page == 3" class="text-center mw-500">
<p class="display-4">You created <span class="font-weight-bold">{{stats.account.posts_count}}</span> posts</p>
<p class="h3 font-weight-light mb-0">The average user created <span class="font-weight-bold">{{stats.average.posts}}</span> posts this year.</p>
</div>
<div v-if="page == 4" class="text-center mw-500">
<p class="display-4">You liked <span class="font-weight-bold">{{stats.account.likes_count}}</span> posts</p>
<p class="h3 font-weight-light mb-0">The average user liked <span class="font-weight-bold">{{stats.average.likes}}</span> posts this year.</p>
</div>
<div v-if="page == 5" class="text-center mw-500">
<div v-if="stats.account.most_popular">
<p class="h1 font-weight-light mb-0 text-break md-line-height">Your most popular post of 2020 was created on <span class="font-weight-bold">{{stats.account.most_popular.created_at}}</span> with <span class="font-weight-bold">{{stats.account.most_popular.likes_count}}</span> likes.</p>
<p class="mt-4 mb-0">
<a class="btn btn-outline-light btn-lg btn-block rounded-pill" :href="stats.account.most_popular.url">View Post</a>
</p>
</div>
<div v-else>
<p class="h1 font-weight-light mb-0 text-break md-line-height">The most popular post of 2020 was created by <span class="font-weight-bold">{{stats.popular.post.username}}</span> on <span class="font-weight-bold">{{stats.popular.post.created_at}}</span> with <span class="font-weight-bold">{{stats.popular.post.likes_count}}</span> likes.</p>
<p class="mt-4 mb-0">
<a class="btn btn-outline-light btn-lg btn-block rounded-pill" :href="stats.popular.post.url">View Post</a>
</p>
</div>
</div>
<div v-if="page == 6" class="text-center mw-500">
<p class="display-4"><span class="font-weight-bold">{{stats.account.followers_this_year}}</span> New Followers</p>
<p class="h3 font-weight-light mb-0">You followed <span class="font-weight-bold">{{stats.account.followed_this_year}}</span> accounts this year!</p>
</div>
<div v-if="page == 7" class="text-center mw-500">
<div v-if="stats.account.hashtag">
<p class="h1 text-break">Your favourite hashtag was <span class="font-weight-bold">#{{stats.account.hashtag.name}}</span>.</p>
<p class="h3 font-weight-light mb-0">You used it <span class="font-weight-bold">{{stats.account.hashtag.count}}</span> times!</p>
</div>
<div v-else>
<p class="h1 text-break">The most popular hashtag was <span class="font-weight-bold">#{{stats.popular.hashtag.name}}</span></p>
<p class="h3 font-weight-light mb-0">It was used <span class="font-weight-bold">{{stats.popular.hashtag.count}}</span> times!</p>
</div>
</div>
<div v-if="page == 8" class="text-center mw-500">
<p class="display-4">You tagged <span class="font-weight-bold">{{stats.account.places_total}}</span> places.</p>
<p v-if="stats.account.places_total" class="h3 font-weight-light mb-0">You tagged <span class="font-weight-bold">{{stats.account.places.name}}</span> a total of <span class="font-weight-bold">{{stats.account.places.count}}</span> times!</p>
<p v-else class="h3 font-weight-light mb-0">The most tagged place was <span class="font-weight-bold">{{stats.popular.places.name}}</span> that was tagged a total of <span class="font-weight-bold">{{stats.popular.places.count}}</span> times!</p>
</div>
<div v-if="page == 9" class="text-center">
<p class="display-4">Happy 2021!</p>
<p class="h3 font-weight-light mb-0">We wish you the best in the new year.</p>
</div>
</div>
<div v-if="loaded" class="fixed-top">
<p class="text-center mt-3 d-flex justify-content-center align-items-center mb-0">
<img src="/img/pixelfed-icon-grey.svg" width="60" height="60">
<span class="text-light font-weight-bold ml-3" style="font-size: 22px;">#my2020</span>
</p>
</div>
<div v-if="loaded" class="fixed-bottom">
<p class="text-center">
<a v-if="!notEnoughData" :class="prevClass()" href="#" @click.prevent="prevPage()" :disabled="page == 1"><i class="fas fa-chevron-left"></i> Back</a>
<a class="btn btn-outline-light rounded-pill mx-3" href="/">Back to Pixelfed</a>
<a v-if="!notEnoughData" :class="nextClass()" href="#" @click.prevent="nextPage()">Next <i class="fas fa-chevron-right"></i></a>
</p>
</div>
</div>
</template>
<style type="text/css" scoped>
.md-line-height {
line-height: 1.65 !important;
}
.mw-500 {
max-width: 500px;
}
</style>
<script type="text/javascript">
export default {
data() {
return {
config: window.App.config,
user: {},
loggedIn: false,
loaded: false,
page: 1,
stats: [],
notEnoughData: false,
reportedView: false
}
},
mounted() {
let u = new URLSearchParams(window.location.search);
if( u.has('v') &&
u.has('ned') &&
u.has('sl') &&
u.get('v') == 20 &&
u.get('sl') >= 1 &&
u.get('sl') <= 9
) {
if(u.get('ned') == 0) {
this.page = u.get('sl');
} else {
this.notEnoughData = true;
}
}
axios.get('/api/pixelfed/v1/accounts/verify_credentials')
.then(res => {
this.user = res.data;
window._sharedData.curUser = res.data;
});
this.fetchData();
},
updated() {
},
methods: {
fetchData() {
axios.get('/api/pixelfed/v2/seasonal/yir')
.then(res => {
this.stats = res.data;
this.loaded = true;
this.shortcuts();
})
},
nextPage() {
if(this.page == 9) {
return;
}
if(this.page == 7 && this.stats.popular.places == null) {
this.page = 9;
window.history.pushState({}, {}, '/i/my2020?v=20&ned=0&sl=9');
return;
}
if(this.page == 8) {
axios.post('/api/pixelfed/v2/seasonal/yir', {
'profile_id' : this.user.profile_id
})
}
++this.page;
window.history.pushState({}, {}, '/i/my2020?v=20&ned=0&sl=' + this.page);
},
prevPage() {
if(this.page == 1) {
return;
}
if(this.page == 9 && this.stats.popular.places == null) {
this.page = 7;
window.history.pushState({}, {}, '/i/my2020?v=20&ned=0&sl=7');
return;
}
--this.page;
if(this.page == 1) {
window.history.pushState({}, {}, '/i/my2020');
} else {
window.history.pushState({}, {}, '/i/my2020?v=20&ned=0&sl=' + this.page);
}
},
prevClass() {
return this.page == 1
? 'btn btn-outline-muted rounded-pill'
: 'btn btn-outline-light rounded-pill';
},
nextClass() {
return this.page == 9
? 'btn btn-outline-muted rounded-pill'
: 'btn btn-outline-light rounded-pill';
},
dateFormat(d) {
},
shortcuts() {
let self = this;
window.addEventListener("keydown", function(event) {
if (event.defaultPrevented) {
return;
}
switch(event.code) {
case "KeyA":
case "ArrowLeft":
self.prevPage();
break;
case "KeyD":
case "ArrowRight":
self.nextPage();
break;
}
event.preventDefault();
}, true);
}
}
}
</script>

View File

@ -45,7 +45,14 @@
</div>
<div class="col-12 col-md-8 px-0 mx-0">
<div class="postPresenterContainer d-none d-flex justify-content-center align-items-center" style="background: #000;">
<div v-if="status.pf_type === 'photo'" class="w-100">
<div v-if="status.pf_type === 'text'" class="w-100">
<div class="w-100 card-img-top border-bottom rounded-0" style="background-image: url(/storage/textimg/bg_1.jpg);background-size: cover;width: 100%;height: 540px;">
<div class="w-100 h-100 d-flex justify-content-center align-items-center">
<p class="text-center text-break h3 px-5 font-weight-bold" v-html="status.content"></p>
</div>
</div>
</div>
<div v-else-if="status.pf_type === 'photo'" class="w-100">
<photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter>
</div>
@ -104,7 +111,7 @@
</div>
<div class="d-flex flex-md-column flex-column-reverse h-100" style="overflow-y: auto;">
<div class="card-body status-comments pt-0">
<div class="status-comment">
<div v-if="status.pf_type != 'text'" class="status-comment">
<div v-if="status.content.length" class="pt-3">
<div v-if="showCaption != true">
<span class="py-3">
@ -839,12 +846,13 @@ export default {
beforeMount() {
let u = new URLSearchParams(window.location.search);
let forceMetro = localStorage.getItem('pf_metro_ui.exp.forceMetro') == 'true';
if(this.statusTemplate == 'text') {
this.layout = 'metro';
return;
}
if(forceMetro == true || u.has('ui') && u.get('ui') == 'metro' && this.layout != 'metro') {
this.layout = 'metro';
}
if(u.has('ui') && u.get('ui') == 'moment' && this.layout != 'moment') {
this.layout = 'moment';
}
},
mounted() {
@ -897,15 +905,8 @@ export default {
}, 3000);
setTimeout(function() {
self.fetchState();
document.querySelectorAll('.status-comment .comment-text a').forEach(function(i, e) {
if(i.href.startsWith(window.location.origin)) {
return;
}
let tag = i.innerText;
if(tag.startsWith('#')) {
tag = tag.substr(1);
}
i.href = '/discover/tags/'+tag+'?src=rph';
document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) {
i.href = App.util.format.rewriteLinks(i);
});
}, 500);
}).catch(error => {
@ -1252,15 +1253,8 @@ export default {
$('.postCommentsLoader').addClass('d-none');
$('.postCommentsContainer').removeClass('d-none');
setTimeout(function() {
document.querySelectorAll('.comments .comment-body a').forEach(function(i, e) {
if(i.href.startsWith(window.location.origin)) {
return;
}
let tag = i.innerText;
if(tag.startsWith('#')) {
tag = tag.substr(1);
}
i.href = '/discover/tags/'+tag+'?src=rph';
document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) {
i.href = App.util.format.rewriteLinks(i);
});
}, 500);
}).catch(error => {

View File

@ -1,5 +1,12 @@
<template>
<div class="w-100 h-100">
<div v-if="owner && layout == 'moment'">
<div class="bg-primary shadow">
<p class="text-center text-white mb-0 py-3 font-weight-bold border-bottom border-info">
<i class="fas fa-exclamation-triangle fa-lg mr-2"></i> The Moment UI layout has been deprecated and will be removed in a future release.
</p>
</div>
</div>
<div v-if="isMobile" class="bg-white p-3 border-bottom">
<div class="d-flex justify-content-between align-items-center">
<div @click="goBack" class="cursor-pointer">
@ -679,10 +686,7 @@
if(forceMetro == true || u.has('ui') && u.get('ui') == 'metro' && this.layout != 'metro') {
this.layout = 'metro';
}
if(u.has('ui') && u.get('ui') == 'moment' && this.layout != 'moment') {
Vue.use(VueMasonry);
this.layout = 'moment';
}
if(this.layout == 'metro' && u.has('t')) {
if(this.modes.indexOf(u.get('t')) != -1) {
if(u.get('t') == 'bookmarks') {

View File

@ -627,15 +627,8 @@ export default {
}, 3000);
setTimeout(function() {
self.fetchState();
document.querySelectorAll('.status-comment .comment-text a').forEach(function(i, e) {
if(i.href.startsWith(window.location.origin)) {
return;
}
let tag = i.innerText;
if(tag.startsWith('#')) {
tag = tag.substr(1);
}
i.href = '/discover/tags/'+tag+'?src=rph';
document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) {
i.href = App.util.format.rewriteLinks(i);
});
}, 500);
}).catch(error => {
@ -977,15 +970,8 @@ export default {
$('.postCommentsLoader').addClass('d-none');
$('.postCommentsContainer').removeClass('d-none');
setTimeout(function() {
document.querySelectorAll('.comments .comment-body a').forEach(function(i, e) {
if(i.href.startsWith(window.location.origin)) {
return;
}
let tag = i.innerText;
if(tag.startsWith('#')) {
tag = tag.substr(1);
}
i.href = '/discover/tags/'+tag+'?src=rph';
document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) {
i.href = App.util.format.rewriteLinks(i);
});
}, 500);
}).catch(error => {

View File

@ -253,7 +253,7 @@
shares: status.reblogs_count,
comments: status.reply_count
},
thumb: status.media_attachments[0].preview_url,
thumb: status.media_attachments[0].url,
media: status.media_attachments,
timestamp: status.created_at,
type: status.pf_type,

View File

@ -33,7 +33,7 @@
<div class="pb-2">
<div class="media align-items-center py-2">
<div class="media-body text-truncate">
<p class="mb-0 text-truncate text-dark font-weight-bold" data-toggle="tooltip" :title="hashtag.value">
<p class="mb-0 text-break text-dark font-weight-bold" data-toggle="tooltip" :title="hashtag.value">
<i class="fas fa-map-marker-alt text-lighter mr-2"></i> {{hashtag.value}}
</p>
</div>
@ -74,7 +74,7 @@
<i class="fas fa-hashtag text-muted"></i>
</span>
<div class="media-body text-truncate">
<p class="mb-0 text-truncate text-dark font-weight-bold" data-toggle="tooltip" :title="hashtag.value">
<p class="mb-0 text-break text-dark font-weight-bold" data-toggle="tooltip" :title="hashtag.value">
#{{hashtag.value}}
</p>
<p v-if="hashtag.count > 2" class="mb-0 small font-weight-bold text-muted text-uppercase">
@ -99,7 +99,7 @@
<div class="media align-items-center py-2 pr-3">
<img class="mr-3 rounded-circle border" :src="profile.avatar" width="50px" height="50px">
<div class="media-body">
<p class="mb-0 text-truncate text-dark font-weight-bold" data-toggle="tooltip" :title="profile.value">
<p class="mb-0 text-break text-dark font-weight-bold" data-toggle="tooltip" :title="profile.value">
{{profile.value}}
</p>
<p class="mb-0 small font-weight-bold text-muted text-uppercase">

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,26 @@
<template>
<div v-if="status.sensitive == true">
<details class="details-animated">
<summary>
<p class="mb-0 lead font-weight-bold">{{ status.spoiler_text ? status.spoiler_text : 'CW / NSFW / Hidden Media'}}</p>
<p class="font-weight-light">(click to show)</p>
</summary>
<div class="max-hide-overflow" :title="status.media_attachments[0].description">
<img :class="status.media_attachments[0].filter_class + ' card-img-top'" :src="status.media_attachments[0].url" loading="lazy" :alt="altText(status)" onerror="this.onerror=null;this.src='/storage/no-preview.png'">
</div>
</details>
<div class="text-light content-label">
<p class="text-center">
<i class="far fa-eye-slash fa-2x"></i>
</p>
<p class="h4 font-weight-bold text-center">
Sensitive Content
</p>
<p class="text-center py-2">
This photo contains sensitive content which <br/>
some people may find offsensive or disturbing.
</p>
<p class="mb-0">
<button @click="status.sensitive = false" class="btn btn-outline-light btn-block btn-sm font-weight-bold">See Photo</button>
</p>
</div>
<blur-hash-image
width="32"
height="32"
punch="1"
:hash="status.media_attachments[0].blurhash"
:alt="altText(status)"/>
</div>
<div v-else>
<div :title="status.media_attachments[0].description">
@ -22,6 +34,14 @@
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
}
.content-label {
margin: 0;
position: absolute;
top:45%;
left:50%;
z-index: 999;
transform: translate(-50%, -50%);
}
</style>
<script type="text/javascript">

4
resources/assets/js/my2020.js vendored Normal file
View File

@ -0,0 +1,4 @@
Vue.component(
'my-yearreview',
require('./components/My2020.vue').default
);

View File

@ -0,0 +1,10 @@
@extends('layouts.blank')
@section('content')
<my-yearreview></my-yearreview>
@endsection
@push('scripts')
<script type="text/javascript" src="{{mix('js/my2020.js')}}"></script>
<script type="text/javascript">App.boot();</script>
@endpush

View File

@ -118,7 +118,6 @@
value: "unlisted",
},
cw: {
text: autocw == 0 ? "CW Media" : "Remove AutoCW",
text: autocw == 0 ? "CW Media" : "Remove AutoCW",
className: "bg-warning",
value: "autocw",

View File

@ -56,26 +56,24 @@
@stack('scripts')
<div class="d-block d-sm-none mt-5"></div>
<div class="d-block d-sm-none fixed-bottom">
<div class="card card-body rounded-0 py-2 d-flex align-items-middle box-shadow" style="border-top:1px solid #F1F5F8">
<ul class="nav nav-pills nav-fill">
<div class="card card-body rounded-0 py-2 box-shadow" style="border-top:1px solid #F1F5F8">
<ul class="nav nav-pills nav-fill d-flex align-items-middle">
<li class="nav-item">
<a class="nav-link {{request()->is('/')?'text-dark':'text-lighter'}}" href="/"><i class="fas fa-home fa-lg"></i></a>
<a class="nav-link text-dark" href="/"><i class="fas fa-home fa-lg"></i></a>
</li>
<li class="nav-item">
<a class="nav-link {{request()->is('discover')?'text-dark':'text-lighter'}}" href="/discover"><i class="fas fa-search fa-lg"></i></a>
<a class="nav-link text-dark" href="/discover"><i class="fas fa-search fa-lg"></i></a>
</li>
<li class="nav-item">
<div class="nav-link text-primary cursor-pointer" onclick="App.util.compose.post()">
<span class="border border-primary rounded p-2 bg-primary">
<i class="fas fa-camera fa-lg text-white" style="color:#fff !important;"></i>
</span>
<div class="nav-link cursor-pointer text-dark" onclick="App.util.compose.post()">
<i class="far fa-plus-square fa-2x"></i>
</div>
</li>
<li class="nav-item">
<a class="nav-link {{request()->is('account/activity')?'text-dark':'text-lighter'}}" href="/account/activity"><i class="far fa-heart fa-lg"></i></a>
<a class="nav-link text-dark" href="/account/activity"><i class="far fa-bell fa-lg"></i></a>
</li>
<li class="nav-item">
<a class="nav-link text-lighter" href="/i/me"><i class="far fa-user fa-lg"></i></a>
<a class="nav-link text-dark" href="/i/me"><i class="far fa-user fa-lg"></i></a>
</li>
</ul>
</div>

View File

@ -39,10 +39,10 @@
</a>
</li>
<li class="nav-item px-md-2 d-none d-md-block">
<a class="nav-link font-weight-bold text-dark" href="/?a=co" title="Compose" data-toggle="tooltip" data-placement="bottom">
<div class="nav-link font-weight-bold text-dark cursor-pointer" title="Compose" data-toggle="tooltip" data-placement="bottom" onclick="App.util.compose.post()">
<i class="far fa-plus-square fa-lg"></i>
<span class="sr-only">Compose</span>
</a>
</div>
</li>
<li class="nav-item px-md-2">
<a class="nav-link font-weight-bold text-dark" href="/account/direct" title="Direct" data-toggle="tooltip" data-placement="bottom">
@ -52,7 +52,7 @@
</li>
<li class="nav-item px-md-2 d-none d-md-block">
<a class="nav-link font-weight-bold text-dark" href="/account/activity" title="Notifications" data-toggle="tooltip" data-placement="bottom">
<i class="far fa-bell fa-lg"></i>
<i class="far fa-bell fa-lg" style="vertical-align: middle;"></i>
<span class="sr-only">Notifications</span>
</a>
</li>
@ -64,10 +64,6 @@
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="d-block d-md-none dropdown-item font-weight-bold" href="/">
<span class="fas fa-home pr-2 text-lighter"></span>
Home
</a>
<a class="dropdown-item font-weight-bold" href="{{route('discover')}}">
<span class="far fa-compass pr-2 text-lighter"></span>
{{__('navmenu.discover')}}

View File

@ -0,0 +1,26 @@
@extends('site.help.partial.template', ['breadcrumb'=>'Instance Actor'])
@section('section')
<div class="title">
<h3 class="font-weight-bold">Instance Actor</h3>
</div>
<hr>
<p class="lead">We use a special account type known as an Instance Actor to fetch content securely with other servers in the fediverse.</p>
<div class="py-4">
<p class="font-weight-bold h5 pb-3">For Instance Admins</p>
<p class="mb-0">If you are an instance admin that found this URL in a request or profile, this account is used to fetch content from remote instances using signed requests (HTTP Signatures) to enforce domain block compatibility with other instances.</p>
</div>
<hr>
<div class="card bg-primary border-primary" style="box-shadow: none !important;border: 3px solid #08d!important;">
<div class="card-header text-light font-weight-bold h4 p-4 bg-primary">Instance Actor Tips</div>
<div class="card-body bg-white p-3">
<ul class="pt-3">
<li class="lead mb-4">The Instance Actor will not appear in search results.</li>
<li class="lead mb-4">You cannot follow an Instance Actor.</li>
<li class="lead mb-4">The Instance Actor does not follow accounts.</li>
<li class="lead">The Instance Actor account does not post or share content from users.</li>
</ul>
</div>
</div>
@endsection

View File

@ -4,7 +4,7 @@
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
<div class="col-12 px-0">
<div class="card mt-md-5 px-0 mx-md-3">
<div class="card mt-md-5 px-0 mx-md-3 shadow-none border">
<div class="card-header font-weight-bold text-muted bg-white py-4">
<a href="{{route('site.help')}}" class="text-muted">{{__('helpcenter.helpcenter')}}</a>
<span class="px-2 font-weight-light">&mdash;</span>

View File

@ -2,12 +2,28 @@
@section('content')
<div class="alert alert-info text-center rounded-0">
<div class="container">
<span class="font-weight-bold">ComposeUI v3 is deprecated</span>
<br>
Please use the <a href="#" onclick="event.preventDefault();window.App.util.compose.post()" class="font-weight-bold">new UI</a> to compose a post.
</div>
<div class="row">
<div class="col-12 col-md-6 offset-md-3 mt-md-3 px-0">
<compose-modal></compose-modal>
</div>
</div>
</div>
@endsection
@endsection
@push('styles')
<style type="text/css">
.card {
box-shadow: none;
border: 1px solid #ddd;
}
.card .card-header .fas.fa-times {
color: #fff;
}
</style>
@endpush
@push('scripts')
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
<script type="text/javascript">window.App.boot()</script>
@endpush

View File

@ -2,10 +2,13 @@
use Illuminate\Http\Request;
$middleware = ['auth:api','twofactor','validemail','localization', 'throttle:60,1'];
$middleware = ['auth:api','twofactor','validemail','throttle:60,1','interstitial'];
Route::post('/f/inbox', 'FederationController@sharedInbox');
Route::post('/users/{username}/inbox', 'FederationController@userInbox');
Route::get('i/actor', 'InstanceActorController@profile');
Route::post('i/actor/inbox', 'InstanceActorController@inbox');
Route::get('i/actor/outbox', 'InstanceActorController@outbox');
Route::group(['prefix' => 'api'], function() use($middleware) {

View File

@ -98,11 +98,28 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('discover/loops', 'DiscoverController@showLoops');
Route::get('discover/profiles', 'DiscoverController@profilesDirectory')->name('discover.profiles');
Route::group(['prefix' => 'api'], function () {
Route::get('search', 'SearchController@searchAPI');
Route::get('nodeinfo/2.0.json', 'FederationController@nodeinfo');
Route::group(['prefix' => 'compose'], function() {
Route::group(['prefix' => 'v0'], function() {
Route::post('/media/upload', 'ComposeController@mediaUpload');
Route::post('/media/update', 'ComposeController@mediaUpdate')
->middleware('throttle:maxComposeMediaUpdatesPerHour,60')
->middleware('throttle:maxComposeMediaUpdatesPerDay,1440')
->middleware('throttle:maxComposeMediaUpdatesPerMonth,43800');
Route::delete('/media/delete', 'ComposeController@mediaDelete');
Route::get('/search/tag', 'ComposeController@searchTag');
Route::get('/search/location', 'ComposeController@searchLocation');
Route::post('/publish', 'ComposeController@store')
->middleware('throttle:maxPostsPerHour,60')
->middleware('throttle:maxPostsPerDay,1440');
Route::post('/publish/text', 'ComposeController@storeText');
});
});
Route::group(['prefix' => 'direct'], function () {
Route::get('browse', 'DirectMessageController@browse');
Route::post('create', 'DirectMessageController@create');
@ -130,7 +147,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('loops', 'DiscoverController@loopsApi');
Route::post('loops/watch', 'DiscoverController@loopWatch');
Route::get('discover/tag', 'DiscoverController@getHashtags');
Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
});
Route::group(['prefix' => 'pixelfed'], function() {
@ -176,25 +192,13 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('discover/posts/trending', 'DiscoverController@trendingApi');
Route::get('discover/posts/hashtags', 'DiscoverController@trendingHashtags');
Route::get('discover/posts/places', 'DiscoverController@trendingPlaces');
Route::get('seasonal/yir', 'SeasonalController@getData');
Route::post('seasonal/yir', 'SeasonalController@store');
});
});
Route::group(['prefix' => 'local'], function () {
// Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials');
// Route::get('accounts/relationships', 'PublicApiController@relationships');
// Route::get('accounts/{id}/statuses', 'PublicApiController@accountStatuses');
// Route::get('accounts/{id}/following', 'PublicApiController@accountFollowing');
// Route::get('accounts/{id}/followers', 'PublicApiController@accountFollowers');
// Route::get('accounts/{id}', 'PublicApiController@account');
// Route::post('avatar/update', 'ApiController@avatarUpdate');
// Route::get('likes', 'ApiController@hydrateLikes');
// Route::post('media', 'ApiController@uploadMedia');
// Route::delete('media', 'ApiController@deleteMedia');
// Route::get('notifications', 'ApiController@notifications');
// Route::get('timelines/public', 'PublicApiController@publicTimelineApi');
// Route::get('timelines/home', 'PublicApiController@homeTimelineApi');
Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
// Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
Route::get('exp/rec', 'ApiController@userRecommendations');
Route::post('discover/tag/subscribe', 'HashtagFollowController@store')->middleware('throttle:maxHashtagFollowsPerHour,60')->middleware('throttle:maxHashtagFollowsPerDay,1440');
Route::get('discover/tag/list', 'HashtagFollowController@getTags');
@ -209,9 +213,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('collection/{id}/publish', 'CollectionController@publish')->middleware('throttle:maxCollectionsPerHour,60')->middleware('throttle:maxCollectionsPerDay,1440')->middleware('throttle:maxCollectionsPerMonth,43800');
Route::get('profile/collections/{id}', 'CollectionController@getUserCollections');
Route::post('compose/media/update/{id}', 'MediaController@composeUpdate')->middleware('throttle:maxComposeMediaUpdatesPerHour,60')->middleware('throttle:maxComposeMediaUpdatesPerDay,1440')->middleware('throttle:maxComposeMediaUpdatesPerMonth,43800');
Route::get('compose/location/search', 'ApiController@composeLocationSearch');
Route::get('compose/tag/search', 'MediaTagController@usernameLookup');
Route::post('compose/tag/untagme', 'MediaTagController@untagProfile');
});
Route::group(['prefix' => 'admin'], function () {
@ -308,6 +310,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('warning', 'AccountInterstitialController@get');
Route::post('warning', 'AccountInterstitialController@read');
Route::get('my2020', 'SeasonalController@yearInReview');
});
Route::group(['prefix' => 'account'], function () {
@ -440,6 +443,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::view('stories', 'site.help.stories')->name('help.stories');
Route::view('embed', 'site.help.embed')->name('help.embed');
Route::view('hashtags', 'site.help.hashtags')->name('help.hashtags');
Route::view('instance-actor', 'site.help.instance-actor')->name('help.instance-actor');
Route::view('discover', 'site.help.discover')->name('help.discover');
Route::view('direct-messages', 'site.help.dm')->name('help.dm');
Route::view('timelines', 'site.help.timelines')->name('help.timelines');

View File

@ -1,4 +1,5 @@
*
!public/
!remcache/
!cities.json
!.gitignore

View File

@ -1,4 +1,5 @@
*
!.gitignore
!no-preview.png
!m/
!m/
!textimg/

3
storage/app/public/textimg/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*
!.gitignore
!bg_1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

2
storage/app/remcache/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore