mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-18 19:13:17 +00:00
commit
32d4b63a23
25 changed files with 1340 additions and 441 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -6,6 +6,12 @@
|
|||
- Implement Admin Domain Blocks API (Mastodon API Compatible) [ThisIsMissEm](https://github.com/ThisIsMissEm) ([#5021](https://github.com/pixelfed/pixelfed/pull/5021))
|
||||
- Authorize Interaction support (for handling remote interactions) ([4ca7c6c3](https://github.com/pixelfed/pixelfed/commit/4ca7c6c3))
|
||||
|
||||
### Federation
|
||||
- Add ActiveSharedInboxService, for efficient sharedInbox caching ([1a6a3397](https://github.com/pixelfed/pixelfed/commit/1a6a3397))
|
||||
- Add MovePipeline queue jobs ([9904d05f](https://github.com/pixelfed/pixelfed/commit/9904d05f))
|
||||
- Add ActivityPub Move validator ([909a6c72](https://github.com/pixelfed/pixelfed/commit/909a6c72))
|
||||
- Add delay to move handler to allow for remote cache invalidation ([8a362c12](https://github.com/pixelfed/pixelfed/commit/8a362c12))
|
||||
|
||||
### Updates
|
||||
- Update ApiV1Controller, add support for notification filter types ([f61159a1](https://github.com/pixelfed/pixelfed/commit/f61159a1))
|
||||
- Update ApiV1Dot1Controller, fix mutual api ([a8bb97b2](https://github.com/pixelfed/pixelfed/commit/a8bb97b2))
|
||||
|
@ -17,6 +23,11 @@
|
|||
- Update AdminSettings component, add link to Custom CSS settings ([958daac4](https://github.com/pixelfed/pixelfed/commit/958daac4))
|
||||
- Update ApiV1Controller, fix v1/instance stats, force cast to int ([dcd95d68](https://github.com/pixelfed/pixelfed/commit/dcd95d68))
|
||||
- Update BeagleService, disable discovery if AP is disabled ([6cd1cbb4](https://github.com/pixelfed/pixelfed/commit/6cd1cbb4))
|
||||
- Update NodeinfoService, fix typo ([edad436d](https://github.com/pixelfed/pixelfed/commit/edad436d))
|
||||
- Update ActivityPubFetchService, reduce cache ttl from 1 hour to 7.5 mins and add uncached fetchRequest method ([21da2b64](https://github.com/pixelfed/pixelfed/commit/21da2b64))
|
||||
- Update UserAccountDelete command, increase sharedInbox ttl from 12h to 14d ([be02f48a](https://github.com/pixelfed/pixelfed/commit/be02f48a))
|
||||
- Update HttpSignature, add signRaw method and improve error checking ([d4cf9181](https://github.com/pixelfed/pixelfed/commit/d4cf9181))
|
||||
- Update AP helpers, add forceBanCheck param to validateUrl method ([42424028](https://github.com/pixelfed/pixelfed/commit/42424028))
|
||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||
|
||||
## [v0.12.3 (2024-07-01)](https://github.com/pixelfed/pixelfed/compare/v0.12.2...v0.12.3)
|
||||
|
|
|
@ -74,14 +74,14 @@ class UserAccountDelete extends Command
|
|||
$activity = $fractal->createData($resource)->toArray();
|
||||
|
||||
$audience = Instance::whereNotNull(['shared_inbox', 'nodeinfo_last_fetched'])
|
||||
->where('nodeinfo_last_fetched', '>', now()->subHours(12))
|
||||
->where('nodeinfo_last_fetched', '>', now()->subDays(14))
|
||||
->distinct()
|
||||
->pluck('shared_inbox');
|
||||
|
||||
$payload = json_encode($activity);
|
||||
|
||||
$client = new Client([
|
||||
'timeout' => 10,
|
||||
'timeout' => 5,
|
||||
]);
|
||||
|
||||
$version = config('pixelfed.version');
|
||||
|
|
|
@ -67,7 +67,9 @@ trait AdminDirectoryController
|
|||
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||
$res['curated_onboarding'] = (bool) config_cache('instance.curated_registration.enabled');
|
||||
$res['open_registration'] = (bool) config_cache('pixelfed.open_registration');
|
||||
$res['oauth_enabled'] = (bool) config_cache('pixelfed.oauth_enabled') && file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
||||
$res['oauth_enabled'] = (bool) config_cache('pixelfed.oauth_enabled') &&
|
||||
(file_exists(storage_path('oauth-public.key')) || config_cache('passport.public_key')) &&
|
||||
(file_exists(storage_path('oauth-private.key')) || config_cache('passport.private_key'));
|
||||
|
||||
$res['activitypub_enabled'] = (bool) config_cache('federation.activitypub.enabled');
|
||||
|
||||
|
|
|
@ -195,7 +195,9 @@ trait AdminSettingsController
|
|||
if ($key == 'mobile_apis' &&
|
||||
$active &&
|
||||
! file_exists(storage_path('oauth-public.key')) &&
|
||||
! file_exists(storage_path('oauth-private.key'))
|
||||
! config_cache('passport.public_key') &&
|
||||
! file_exists(storage_path('oauth-private.key')) &&
|
||||
! config_cache('passport.private_key')
|
||||
) {
|
||||
Artisan::call('passport:keys');
|
||||
Artisan::call('route:cache');
|
||||
|
|
|
@ -67,9 +67,7 @@ use App\Transformer\Api\Mastodon\v1\AccountTransformer;
|
|||
use App\Transformer\Api\Mastodon\v1\MediaTransformer;
|
||||
use App\Transformer\Api\Mastodon\v1\NotificationTransformer;
|
||||
use App\Transformer\Api\Mastodon\v1\StatusTransformer;
|
||||
use App\Transformer\Api\{
|
||||
RelationshipTransformer,
|
||||
};
|
||||
use App\Transformer\Api\RelationshipTransformer;
|
||||
use App\User;
|
||||
use App\UserFilter;
|
||||
use App\UserSetting;
|
||||
|
@ -98,8 +96,8 @@ class ApiV1Controller extends Controller
|
|||
|
||||
public function __construct()
|
||||
{
|
||||
$this->fractal = new Fractal\Manager();
|
||||
$this->fractal->setSerializer(new ArraySerializer());
|
||||
$this->fractal = new Fractal\Manager;
|
||||
$this->fractal->setSerializer(new ArraySerializer);
|
||||
}
|
||||
|
||||
public function json($res, $code = 200, $headers = [])
|
||||
|
@ -490,6 +488,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$account = AccountService::get($id);
|
||||
abort_if(! $account, 404);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||
$pid = $request->user()->profile_id;
|
||||
$this->validate($request, [
|
||||
'limit' => 'sometimes|integer|min:1',
|
||||
|
@ -591,6 +590,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$account = AccountService::get($id);
|
||||
abort_if(! $account, 404);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||
$pid = $request->user()->profile_id;
|
||||
$this->validate($request, [
|
||||
'limit' => 'sometimes|integer|min:1',
|
||||
|
@ -818,6 +818,8 @@ class ApiV1Controller extends Controller
|
|||
->whereNull('status')
|
||||
->findOrFail($id);
|
||||
|
||||
abort_if($target && $target->moved_to_profile_id, 400, 'Cannot follow an account that has moved!');
|
||||
|
||||
if ($target && $target->domain) {
|
||||
$domain = $target->domain;
|
||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||
|
@ -857,7 +859,7 @@ class ApiV1Controller extends Controller
|
|||
'following_id' => $target->id,
|
||||
]);
|
||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||
(new FollowerController())->sendFollow($user->profile, $target);
|
||||
(new FollowerController)->sendFollow($user->profile, $target);
|
||||
}
|
||||
} else {
|
||||
$follower = Follower::firstOrCreate([
|
||||
|
@ -866,7 +868,7 @@ class ApiV1Controller extends Controller
|
|||
]);
|
||||
|
||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||
(new FollowerController())->sendFollow($user->profile, $target);
|
||||
(new FollowerController)->sendFollow($user->profile, $target);
|
||||
}
|
||||
FollowPipeline::dispatch($follower)->onQueue('high');
|
||||
}
|
||||
|
@ -925,7 +927,7 @@ class ApiV1Controller extends Controller
|
|||
$followRequest->delete();
|
||||
RelationshipService::refresh($target->id, $user->profile_id);
|
||||
}
|
||||
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
|
||||
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return $this->json($res);
|
||||
|
@ -938,7 +940,7 @@ class ApiV1Controller extends Controller
|
|||
UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high');
|
||||
|
||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||
(new FollowerController())->sendUndoFollow($user->profile, $target);
|
||||
(new FollowerController)->sendUndoFollow($user->profile, $target);
|
||||
}
|
||||
|
||||
RelationshipService::refresh($user->profile_id, $target->id);
|
||||
|
@ -1132,6 +1134,8 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$profile = Profile::findOrFail($id);
|
||||
|
||||
abort_if($profile->moved_to_profile_id, 422, 'Cannot block an account that has migrated!');
|
||||
|
||||
if ($profile->user && $profile->user->is_admin == true) {
|
||||
abort(400, 'You cannot block an admin');
|
||||
}
|
||||
|
@ -1198,7 +1202,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
UserFilterService::block($pid, $id);
|
||||
RelationshipService::refresh($pid, $id);
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return $this->json($res);
|
||||
|
@ -1225,6 +1229,8 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$profile = Profile::findOrFail($id);
|
||||
|
||||
abort_if($profile->moved_to_profile_id, 422, 'Cannot unblock an account that has migrated!');
|
||||
|
||||
$filter = UserFilter::whereUserId($pid)
|
||||
->whereFilterableId($profile->id)
|
||||
->whereFilterableType('App\Profile')
|
||||
|
@ -1237,7 +1243,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
RelationshipService::refresh($pid, $id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return $this->json($res);
|
||||
|
@ -1372,6 +1378,8 @@ class ApiV1Controller extends Controller
|
|||
|
||||
abort_unless($status, 404);
|
||||
|
||||
abort_if(isset($status['moved'], $status['moved']['id']), 422, 'Cannot like a post from an account that has migrated');
|
||||
|
||||
if ($status && isset($status['account'], $status['account']['acct']) && strpos($status['account']['acct'], '@') != -1) {
|
||||
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||
|
@ -1440,6 +1448,7 @@ class ApiV1Controller extends Controller
|
|||
$status = $napi ? StatusService::get($id, false) : StatusService::getMastodon($id, false);
|
||||
|
||||
abort_unless($status && isset($status['account']), 404);
|
||||
abort_if(isset($status['moved'], $status['moved']['id']), 422, 'Cannot unlike a post from an account that has migrated');
|
||||
|
||||
if ($status && isset($status['account'], $status['account']['acct']) && strpos($status['account']['acct'], '@') != -1) {
|
||||
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
||||
|
@ -1540,6 +1549,8 @@ class ApiV1Controller extends Controller
|
|||
$pid = $request->user()->profile_id;
|
||||
$target = AccountService::getMastodon($id);
|
||||
|
||||
abort_if(isset($target['moved'], $target['moved']['id']), 422, 'Cannot accept a request from an account that has migrated!');
|
||||
|
||||
if (! $target) {
|
||||
return response()->json(['error' => 'Record not found'], 404);
|
||||
}
|
||||
|
@ -1556,7 +1567,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
$follower = $followRequest->follower;
|
||||
$follow = new Follower();
|
||||
$follow = new Follower;
|
||||
$follow->profile_id = $follower->id;
|
||||
$follow->following_id = $pid;
|
||||
$follow->save();
|
||||
|
@ -1603,6 +1614,8 @@ class ApiV1Controller extends Controller
|
|||
return response()->json(['error' => 'Record not found'], 404);
|
||||
}
|
||||
|
||||
abort_if(isset($target['moved'], $target['moved']['id']), 422, 'Cannot reject a request from an account that has migrated!');
|
||||
|
||||
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
|
||||
|
||||
if (! $followRequest) {
|
||||
|
@ -1661,7 +1674,7 @@ class ApiV1Controller extends Controller
|
|||
null;
|
||||
});
|
||||
|
||||
$stats = Cache::remember('api:v1:instance-data:stats', 43200, function () {
|
||||
$stats = Cache::remember('api:v1:instance-data:stats:v0', 43200, function () {
|
||||
return [
|
||||
'user_count' => (int) User::count(),
|
||||
'status_count' => (int) StatusService::totalLocalStatuses(),
|
||||
|
@ -1857,7 +1870,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
||||
|
||||
$media = new Media();
|
||||
$media = new Media;
|
||||
$media->status_id = null;
|
||||
$media->profile_id = $profile->id;
|
||||
$media->user_id = $user->id;
|
||||
|
@ -1891,7 +1904,7 @@ class ApiV1Controller extends Controller
|
|||
$user->save();
|
||||
|
||||
Cache::forget($limitKey);
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
$res['preview_url'] = $media->url().'?v='.time();
|
||||
$res['url'] = $media->url().'?v='.time();
|
||||
|
@ -1946,9 +1959,9 @@ class ApiV1Controller extends Controller
|
|||
], 429);
|
||||
}
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$fractal = new Fractal\Manager;
|
||||
$fractal->setSerializer(new ArraySerializer);
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||
|
||||
return $this->json($fractal->createData($resource)->toArray());
|
||||
}
|
||||
|
@ -1972,7 +1985,7 @@ class ApiV1Controller extends Controller
|
|||
->whereNull('status_id')
|
||||
->findOrFail($id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return $this->json($res);
|
||||
|
@ -2085,7 +2098,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$media = new Media();
|
||||
$media = new Media;
|
||||
$media->status_id = null;
|
||||
$media->profile_id = $profile->id;
|
||||
$media->user_id = $user->id;
|
||||
|
@ -2119,7 +2132,7 @@ class ApiV1Controller extends Controller
|
|||
$user->save();
|
||||
|
||||
Cache::forget($limitKey);
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
$res['preview_url'] = $media->url().'?v='.time();
|
||||
$res['url'] = null;
|
||||
|
@ -2204,6 +2217,8 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$account = Profile::findOrFail($id);
|
||||
|
||||
abort_if($account->moved_to_profile_id, 422, 'Cannot mute an account that has migrated!');
|
||||
|
||||
if ($account && $account->domain) {
|
||||
$domain = $account->domain;
|
||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||
|
@ -2237,7 +2252,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
RelationshipService::refresh($pid, $id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($account, new RelationshipTransformer());
|
||||
$resource = new Fractal\Resource\Item($account, new RelationshipTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return $this->json($res);
|
||||
|
@ -2263,6 +2278,8 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$profile = Profile::findOrFail($id);
|
||||
|
||||
abort_if($profile->moved_to_profile_id, 422, 'Cannot unmute an account that has migrated!');
|
||||
|
||||
$filter = UserFilter::whereUserId($pid)
|
||||
->whereFilterableId($profile->id)
|
||||
->whereFilterableType('App\Profile')
|
||||
|
@ -2276,7 +2293,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
RelationshipService::refresh($pid, $id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return $this->json($res);
|
||||
|
@ -3209,6 +3226,7 @@ class ApiV1Controller extends Controller
|
|||
$status = Status::findOrFail($id);
|
||||
$account = AccountService::get($status->profile_id, true);
|
||||
abort_if(! $account, 404);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||
if ($account && strpos($account['acct'], '@') != -1) {
|
||||
$domain = parse_url($account['url'], PHP_URL_HOST);
|
||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||
|
@ -3308,11 +3326,12 @@ class ApiV1Controller extends Controller
|
|||
$pid = $user->profile_id;
|
||||
$status = Status::findOrFail($id);
|
||||
$account = AccountService::get($status->profile_id, true);
|
||||
abort_if(! $account, 404);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||
if ($account && strpos($account['acct'], '@') != -1) {
|
||||
$domain = parse_url($account['url'], PHP_URL_HOST);
|
||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||
}
|
||||
abort_if(! $account, 404);
|
||||
$author = intval($status->profile_id) === intval($pid) || $user->is_admin;
|
||||
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||
|
||||
|
@ -3617,7 +3636,7 @@ class ApiV1Controller extends Controller
|
|||
$status = Status::whereProfileId($request->user()->profile->id)
|
||||
->findOrFail($id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($status, new StatusTransformer());
|
||||
$resource = new Fractal\Resource\Item($status, new StatusTransformer);
|
||||
|
||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||
StatusDelete::dispatch($status);
|
||||
|
@ -3644,6 +3663,8 @@ class ApiV1Controller extends Controller
|
|||
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
||||
AccountService::setLastActive($user->id);
|
||||
$status = Status::whereScope('public')->findOrFail($id);
|
||||
$account = AccountService::get($status->profile_id);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot share a post from an account that has migrated');
|
||||
if ($status && ($status->uri || $status->url || $status->object_url)) {
|
||||
$url = $status->uri ?? $status->url ?? $status->object_url;
|
||||
$domain = parse_url($url, PHP_URL_HOST);
|
||||
|
@ -3696,6 +3717,8 @@ class ApiV1Controller extends Controller
|
|||
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
||||
AccountService::setLastActive($user->id);
|
||||
$status = Status::whereScope('public')->findOrFail($id);
|
||||
$account = AccountService::get($status->profile_id);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot unshare a post from an account that has migrated');
|
||||
|
||||
if (intval($status->profile_id) !== intval($user->profile_id)) {
|
||||
if ($status->scope == 'private') {
|
||||
|
@ -3929,7 +3952,8 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$status = Status::findOrFail($id);
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
$account = AccountService::get($status->profile_id);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark a post from an account that has migrated');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||
|
@ -4045,8 +4069,8 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
$pid = $request->user()->profile_id;
|
||||
$status = StatusService::getMastodon($id, false);
|
||||
|
||||
abort_if(! $status, 404);
|
||||
abort_if(isset($status['account'], $account['account']['moved']['id']), 404, 'Account moved');
|
||||
|
||||
if ($status['visibility'] == 'private') {
|
||||
if ($pid != $status['account']['id']) {
|
||||
|
@ -4135,11 +4159,11 @@ class ApiV1Controller extends Controller
|
|||
{
|
||||
abort_if(! $request->user(), 403);
|
||||
|
||||
$status = Status::findOrFail($id);
|
||||
$pid = $request->user()->profile_id;
|
||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||
$status = StatusService::get($id, false, true);
|
||||
abort_if(! $status, 404);
|
||||
abort_if(! in_array($status['visibility'], ['public', 'unlisted', 'private']), 404);
|
||||
|
||||
return $this->json(StatusService::getState($status->id, $pid));
|
||||
return $this->json(StatusService::getState($status['id'], $request->user()->profile_id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Bookmark;
|
||||
use App\Status;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\BookmarkService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\UserRoleService;
|
||||
use App\Status;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BookmarkController extends Controller
|
||||
{
|
||||
|
@ -25,7 +25,8 @@ class BookmarkController extends Controller
|
|||
|
||||
$user = $request->user();
|
||||
$status = Status::findOrFail($request->input('item'));
|
||||
|
||||
$account = AccountService::get($status->profile_id);
|
||||
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark or unbookmark a post from an account that has migrated');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||
|
|
|
@ -90,7 +90,8 @@ class PixelfedDirectoryController extends Controller
|
|||
|
||||
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
||||
if ($oauthEnabled) {
|
||||
$keys = file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
||||
$keys = (file_exists(storage_path('oauth-public.key')) || config_cache('passport.public_key')) &&
|
||||
(file_exists(storage_path('oauth-private.key')) || config_cache('passport.private_key'));
|
||||
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,8 @@ class StatusController extends Controller
|
|||
! $status ||
|
||||
! isset($status['account'], $status['account']['id'], $status['local']) ||
|
||||
! $status['local'] ||
|
||||
strtolower($status['account']['username']) !== strtolower($username)
|
||||
strtolower($status['account']['username']) !== strtolower($username) ||
|
||||
isset($status['account']['moved'], $status['account']['moved']['id'])
|
||||
) {
|
||||
$content = view('status.embed-removed');
|
||||
|
||||
|
@ -220,10 +221,7 @@ class StatusController extends Controller
|
|||
return view('status.compose');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
|
||||
}
|
||||
public function store(Request $request) {}
|
||||
|
||||
public function delete(Request $request)
|
||||
{
|
||||
|
@ -307,6 +305,8 @@ class StatusController extends Controller
|
|||
$profile = $user->profile;
|
||||
$status = Status::whereScope('public')
|
||||
->findOrFail($request->input('item'));
|
||||
$statusAccount = AccountService::get($status->profile_id);
|
||||
abort_if(! $statusAccount || isset($statusAccount['moved'], $statusAccount['moved']['id']), 422, 'Account moved');
|
||||
|
||||
$count = $status->reblogs_count;
|
||||
|
||||
|
@ -323,7 +323,7 @@ class StatusController extends Controller
|
|||
$count--;
|
||||
}
|
||||
} else {
|
||||
$share = new Status();
|
||||
$share = new Status;
|
||||
$share->profile_id = $profile->id;
|
||||
$share->reblog_of_id = $status->id;
|
||||
$share->in_reply_to_profile_id = $status->profile_id;
|
||||
|
@ -352,8 +352,8 @@ class StatusController extends Controller
|
|||
|
||||
return Cache::remember($key, 3600, function () use ($status) {
|
||||
$status = Status::findOrFail($status['id']);
|
||||
$object = $status->type == 'poll' ? new Question() : new Note();
|
||||
$fractal = new Fractal\Manager();
|
||||
$object = $status->type == 'poll' ? new Question : new Note;
|
||||
$fractal = new Fractal\Manager;
|
||||
$resource = new Fractal\Resource\Item($status, $object);
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
|
||||
|
|
103
app/Jobs/MovePipeline/CleanupLegacyAccountMovePipeline.php
Normal file
103
app/Jobs/MovePipeline/CleanupLegacyAccountMovePipeline.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\MovePipeline;
|
||||
|
||||
use App\Follower;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\UserFilter;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
class CleanupLegacyAccountMovePipeline implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $target;
|
||||
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 6;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxExceptions = 3;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($target, $activity)
|
||||
{
|
||||
$this->target = $target;
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [
|
||||
new WithoutOverlapping('process-move-cleanup-legacy-followers:'.$this->target),
|
||||
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*/
|
||||
public function retryUntil(): DateTime
|
||||
{
|
||||
return now()->addMinutes(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||
throw new Exception('Activitypub not enabled');
|
||||
}
|
||||
|
||||
$target = $this->target;
|
||||
$actor = $this->activity;
|
||||
|
||||
$targetAccount = Helpers::profileFetch($target);
|
||||
$actorAccount = Helpers::profileFetch($actor);
|
||||
|
||||
if (! $targetAccount || ! $actorAccount) {
|
||||
throw new Exception('Invalid move accounts');
|
||||
}
|
||||
|
||||
UserFilter::where('filterable_type', 'App\Profile')
|
||||
->where('filterable_id', $actorAccount['id'])
|
||||
->update(['filterable_id' => $targetAccount['id']]);
|
||||
|
||||
Follower::whereFollowingId($actorAccount['id'])->delete();
|
||||
|
||||
$oldProfile = Profile::find($actorAccount['id']);
|
||||
|
||||
if ($oldProfile) {
|
||||
$oldProfile->moved_to_profile_id = $targetAccount['id'];
|
||||
$oldProfile->save();
|
||||
AccountService::del($oldProfile->id);
|
||||
AccountService::del($targetAccount['id']);
|
||||
}
|
||||
}
|
||||
}
|
131
app/Jobs/MovePipeline/MoveMigrateFollowersPipeline.php
Normal file
131
app/Jobs/MovePipeline/MoveMigrateFollowersPipeline.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\MovePipeline;
|
||||
|
||||
use App\Follower;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use DateTime;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
class MoveMigrateFollowersPipeline implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $target;
|
||||
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 15;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxExceptions = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds the job can run before timing out.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 900;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($target, $activity)
|
||||
{
|
||||
$this->target = $target;
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [
|
||||
new WithoutOverlapping('process-move-migrate-followers:'.$this->target),
|
||||
(new ThrottlesExceptionsWithRedis(5, 2 * 60))->backoff(1),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*/
|
||||
public function retryUntil(): DateTime
|
||||
{
|
||||
return now()->addMinutes(15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||
throw new Exception('Activitypub not enabled');
|
||||
}
|
||||
|
||||
$target = $this->target;
|
||||
$actor = $this->activity;
|
||||
|
||||
$targetAccount = Helpers::profileFetch($target);
|
||||
$actorAccount = Helpers::profileFetch($actor);
|
||||
|
||||
if (! $targetAccount || ! $actorAccount) {
|
||||
throw new Exception('Invalid move accounts');
|
||||
}
|
||||
|
||||
$activity = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'type' => 'Follow',
|
||||
'actor' => null,
|
||||
'object' => $target,
|
||||
];
|
||||
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
$addlHeaders = [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
];
|
||||
$targetInbox = $targetAccount['sharedInbox'] ?? $targetAccount['inbox_url'];
|
||||
$targetPid = $targetAccount['id'];
|
||||
|
||||
DB::table('followers')
|
||||
->join('profiles', 'followers.profile_id', '=', 'profiles.id')
|
||||
->where('followers.following_id', $actorAccount['id'])
|
||||
->whereNotNull('profiles.user_id')
|
||||
->whereNull('profiles.deleted_at')
|
||||
->select('profiles.id', 'profiles.user_id', 'profiles.username', 'profiles.private_key', 'profiles.status')
|
||||
->chunkById(100, function ($followers) use ($targetInbox, $targetPid, $target) {
|
||||
foreach ($followers as $follower) {
|
||||
if (! $follower->private_key || ! $follower->username || ! $follower->user_id || $follower->status === 'delete') {
|
||||
continue;
|
||||
}
|
||||
|
||||
Follower::updateOrCreate([
|
||||
'profile_id' => $follower->id,
|
||||
'following_id' => $targetPid,
|
||||
]);
|
||||
|
||||
MoveSendFollowPipeline::dispatch($follower, $targetInbox, $targetPid, $target)->onQueue('follow');
|
||||
}
|
||||
}, 'id');
|
||||
}
|
||||
}
|
113
app/Jobs/MovePipeline/MoveSendFollowPipeline.php
Normal file
113
app/Jobs/MovePipeline/MoveSendFollowPipeline.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\MovePipeline;
|
||||
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
class MoveSendFollowPipeline implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $follower;
|
||||
|
||||
public $targetInbox;
|
||||
|
||||
public $targetPid;
|
||||
|
||||
public $target;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxExceptions = 3;
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [
|
||||
new WithoutOverlapping('move-send-follow:'.$this->follower->id.':target:'.$this->target),
|
||||
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($follower, $targetInbox, $targetPid, $target)
|
||||
{
|
||||
$this->follower = $follower;
|
||||
$this->targetInbox = $targetInbox;
|
||||
$this->targetPid = $targetPid;
|
||||
$this->target = $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$follower = $this->follower;
|
||||
$targetPid = $this->targetPid;
|
||||
$targetInbox = $this->targetInbox;
|
||||
$target = $this->target;
|
||||
|
||||
if (! $follower->username || ! $follower->private_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
$permalink = 'https://'.config('pixelfed.domain.app').'/users/'.$follower->username;
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
$addlHeaders = [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
];
|
||||
|
||||
$activity = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'type' => 'Follow',
|
||||
'actor' => $permalink,
|
||||
'object' => $target,
|
||||
];
|
||||
|
||||
$keyId = $permalink.'#main-key';
|
||||
$payload = json_encode($activity);
|
||||
$headers = HttpSignature::signRaw($follower->private_key, $keyId, $targetInbox, $activity, $addlHeaders);
|
||||
|
||||
$client = new Client([
|
||||
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||
]);
|
||||
|
||||
try {
|
||||
$client->post($targetInbox, [
|
||||
'curl' => [
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HEADER => true,
|
||||
],
|
||||
]);
|
||||
} catch (ClientException $e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
119
app/Jobs/MovePipeline/MoveSendUndoFollowPipeline.php
Normal file
119
app/Jobs/MovePipeline/MoveSendUndoFollowPipeline.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\MovePipeline;
|
||||
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
class MoveSendUndoFollowPipeline implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $follower;
|
||||
|
||||
public $targetInbox;
|
||||
|
||||
public $targetPid;
|
||||
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxExceptions = 3;
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [
|
||||
new WithoutOverlapping('move-send-unfollow:'.$this->follower->id.':actor:'.$this->actor),
|
||||
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($follower, $targetInbox, $targetPid, $actor)
|
||||
{
|
||||
$this->follower = $follower;
|
||||
$this->targetInbox = $targetInbox;
|
||||
$this->targetPid = $targetPid;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$follower = $this->follower;
|
||||
$targetPid = $this->targetPid;
|
||||
$targetInbox = $this->targetInbox;
|
||||
$actor = $this->actor;
|
||||
|
||||
if (! $follower->username || ! $follower->private_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
$permalink = 'https://'.config('pixelfed.domain.app').'/users/'.$follower->username;
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
$addlHeaders = [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
];
|
||||
|
||||
$activity = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'type' => 'Undo',
|
||||
'id' => $permalink.'#follow/'.$targetPid.'/undo',
|
||||
'actor' => $permalink,
|
||||
'object' => [
|
||||
'type' => 'Follow',
|
||||
'id' => $permalink.'#follows/'.$targetPid,
|
||||
'object' => $actor,
|
||||
'actor' => $permalink,
|
||||
],
|
||||
];
|
||||
|
||||
$keyId = $permalink.'#main-key';
|
||||
$payload = json_encode($activity);
|
||||
$headers = HttpSignature::signRaw($follower->private_key, $keyId, $targetInbox, $activity, $addlHeaders);
|
||||
|
||||
$client = new Client([
|
||||
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||
]);
|
||||
|
||||
try {
|
||||
$client->post($targetInbox, [
|
||||
'curl' => [
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HEADER => true,
|
||||
],
|
||||
]);
|
||||
} catch (ClientException $e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
156
app/Jobs/MovePipeline/ProcessMovePipeline.php
Normal file
156
app/Jobs/MovePipeline/ProcessMovePipeline.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\MovePipeline;
|
||||
|
||||
use App\Services\ActivityPubFetchService;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ProcessMovePipeline implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $target;
|
||||
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 15;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxExceptions = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds the job can run before timing out.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 120;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($target, $activity)
|
||||
{
|
||||
$this->target = $target;
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [
|
||||
new WithoutOverlapping('process-move:'.$this->target),
|
||||
(new ThrottlesExceptionsWithRedis(5, 2 * 60))->backoff(1),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*/
|
||||
public function retryUntil(): DateTime
|
||||
{
|
||||
return now()->addMinutes(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||
throw new Exception('Activitypub not enabled');
|
||||
}
|
||||
|
||||
$validTarget = $this->checkTarget();
|
||||
if (! $validTarget) {
|
||||
throw new Exception('Invalid target');
|
||||
}
|
||||
|
||||
$validActor = $this->checkActor();
|
||||
if (! $validActor) {
|
||||
throw new Exception('Invalid actor');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function checkTarget()
|
||||
{
|
||||
$fetchTargetUrl = $this->target.'?cb='.time();
|
||||
$res = ActivityPubFetchService::fetchRequest($fetchTargetUrl, true);
|
||||
|
||||
if (! $res || ! isset($res['alsoKnownAs'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$targetRes = Helpers::profileFetch($this->target);
|
||||
if (! $targetRes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_string($res['alsoKnownAs'])) {
|
||||
return $this->lowerTrim($res['alsoKnownAs']) === $this->lowerTrim($this->activity);
|
||||
}
|
||||
|
||||
if (is_array($res['alsoKnownAs'])) {
|
||||
$map = Arr::map($res['alsoKnownAs'], function ($value, $key) {
|
||||
return trim(strtolower($value));
|
||||
});
|
||||
|
||||
$res = in_array($this->activity, $map);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkActor()
|
||||
{
|
||||
$fetchActivityUrl = $this->activity.'?cb='.time();
|
||||
$res = ActivityPubFetchService::fetchRequest($fetchActivityUrl, true);
|
||||
|
||||
if (! $res || ! isset($res['movedTo']) || empty($res['movedTo'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actorRes = Helpers::profileFetch($this->activity);
|
||||
if (! $actorRes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_string($res['movedTo'])) {
|
||||
$match = $this->lowerTrim($res['movedTo']) === $this->lowerTrim($this->target);
|
||||
if (! $match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function lowerTrim($str)
|
||||
{
|
||||
return trim(strtolower($str));
|
||||
}
|
||||
}
|
111
app/Jobs/MovePipeline/UnfollowLegacyAccountMovePipeline.php
Normal file
111
app/Jobs/MovePipeline/UnfollowLegacyAccountMovePipeline.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\MovePipeline;
|
||||
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use DateTime;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
class UnfollowLegacyAccountMovePipeline implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $target;
|
||||
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 6;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxExceptions = 3;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($target, $activity)
|
||||
{
|
||||
$this->target = $target;
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [
|
||||
new WithoutOverlapping('process-move-undo-legacy-followers:'.$this->target),
|
||||
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*/
|
||||
public function retryUntil(): DateTime
|
||||
{
|
||||
return now()->addMinutes(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||
throw new Exception('Activitypub not enabled');
|
||||
}
|
||||
|
||||
$target = $this->target;
|
||||
$actor = $this->activity;
|
||||
|
||||
$targetAccount = Helpers::profileFetch($target);
|
||||
$actorAccount = Helpers::profileFetch($actor);
|
||||
|
||||
if (! $targetAccount || ! $actorAccount) {
|
||||
throw new Exception('Invalid move accounts');
|
||||
}
|
||||
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
$addlHeaders = [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
];
|
||||
$targetInbox = $actorAccount['sharedInbox'] ?? $actorAccount['inbox_url'];
|
||||
$targetPid = $actorAccount['id'];
|
||||
|
||||
DB::table('followers')
|
||||
->join('profiles', 'followers.profile_id', '=', 'profiles.id')
|
||||
->where('followers.following_id', $actorAccount['id'])
|
||||
->whereNotNull('profiles.user_id')
|
||||
->whereNull('profiles.deleted_at')
|
||||
->select('profiles.id', 'profiles.user_id', 'profiles.username', 'profiles.private_key', 'profiles.status')
|
||||
->chunkById(100, function ($followers) use ($actor, $targetInbox, $targetPid) {
|
||||
foreach ($followers as $follower) {
|
||||
if (! $follower->id || ! $follower->private_key || ! $follower->username || ! $follower->user_id || $follower->status === 'delete') {
|
||||
continue;
|
||||
}
|
||||
|
||||
MoveSendUndoFollowPipeline::dispatch($follower, $targetInbox, $targetPid, $actor)->onQueue('move');
|
||||
}
|
||||
}, 'id');
|
||||
}
|
||||
}
|
|
@ -24,13 +24,13 @@ class AccountService
|
|||
public static function get($id, $softFail = false)
|
||||
{
|
||||
$res = Cache::remember(self::CACHE_KEY.$id, 43200, function () use ($id) {
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$fractal = new Fractal\Manager;
|
||||
$fractal->setSerializer(new ArraySerializer);
|
||||
$profile = Profile::find($id);
|
||||
if (! $profile || $profile->status === 'delete') {
|
||||
return null;
|
||||
}
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||
|
||||
return $fractal->createData($resource)->toArray();
|
||||
});
|
||||
|
@ -291,7 +291,6 @@ class AccountService
|
|||
$user->save();
|
||||
Cache::put($key, 1, 14400);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function blocksDomain($pid, $domain = false)
|
||||
|
|
|
@ -25,59 +25,8 @@ class ActivityPubFetchService
|
|||
$urlKey = hash('sha256', $url);
|
||||
$key = self::CACHE_KEY.$domainKey.':'.$urlKey;
|
||||
|
||||
return Cache::remember($key, 3600, function () use ($url) {
|
||||
$baseHeaders = [
|
||||
'Accept' => 'application/activity+json',
|
||||
];
|
||||
|
||||
$headers = HttpSignature::instanceActorSign($url, false, $baseHeaders, 'get');
|
||||
$headers['Accept'] = 'application/activity+json';
|
||||
$headers['User-Agent'] = 'PixelFedBot/1.0.0 (Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')';
|
||||
|
||||
try {
|
||||
$res = Http::withOptions([
|
||||
'allow_redirects' => [
|
||||
'max' => 2,
|
||||
'protocols' => ['https'],
|
||||
]])
|
||||
->withHeaders($headers)
|
||||
->timeout(30)
|
||||
->connectTimeout(5)
|
||||
->retry(3, 500)
|
||||
->get($url);
|
||||
} catch (RequestException $e) {
|
||||
return;
|
||||
} catch (ConnectionException $e) {
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $res->ok()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $res->hasHeader('Content-Type')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$acceptedTypes = [
|
||||
'application/activity+json; charset=utf-8',
|
||||
'application/activity+json',
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
];
|
||||
|
||||
$contentType = $res->getHeader('Content-Type')[0];
|
||||
|
||||
if (! $contentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! in_array($contentType, $acceptedTypes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $res->body();
|
||||
return Cache::remember($key, 450, function () use ($url) {
|
||||
return self::fetchRequest($url);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -130,4 +79,60 @@ class ActivityPubFetchService
|
|||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function fetchRequest($url, $returnJsonFormat = false)
|
||||
{
|
||||
$baseHeaders = [
|
||||
'Accept' => 'application/activity+json',
|
||||
];
|
||||
|
||||
$headers = HttpSignature::instanceActorSign($url, false, $baseHeaders, 'get');
|
||||
$headers['Accept'] = 'application/activity+json';
|
||||
$headers['User-Agent'] = 'PixelFedBot/1.0.0 (Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')';
|
||||
|
||||
try {
|
||||
$res = Http::withOptions([
|
||||
'allow_redirects' => [
|
||||
'max' => 2,
|
||||
'protocols' => ['https'],
|
||||
]])
|
||||
->withHeaders($headers)
|
||||
->timeout(30)
|
||||
->connectTimeout(5)
|
||||
->retry(3, 500)
|
||||
->get($url);
|
||||
} catch (RequestException $e) {
|
||||
return;
|
||||
} catch (ConnectionException $e) {
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $res->ok()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $res->hasHeader('Content-Type')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$acceptedTypes = [
|
||||
'application/activity+json; charset=utf-8',
|
||||
'application/activity+json',
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
];
|
||||
|
||||
$contentType = $res->getHeader('Content-Type')[0];
|
||||
|
||||
if (! $contentType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! in_array($contentType, $acceptedTypes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $returnJsonFormat ? $res->json() : $res->body();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class FollowerService
|
|||
const FOLLOWING_SYNC_KEY = 'pf:services:followers:sync-following:';
|
||||
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
|
||||
const FOLLOWERS_KEY = 'pf:services:follow:followers:id:';
|
||||
const FOLLOWERS_LOCAL_KEY = 'pf:services:follow:local-follower-ids:';
|
||||
const FOLLOWERS_LOCAL_KEY = 'pf:services:follow:local-follower-ids:v1:';
|
||||
const FOLLOWERS_INTER_KEY = 'pf:services:follow:followers:inter:id:';
|
||||
|
||||
public static function add($actor, $target, $refresh = true)
|
||||
|
@ -33,12 +33,16 @@ class FollowerService
|
|||
Redis::zadd(self::FOLLOWING_KEY . $actor, $ts, $target);
|
||||
Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor);
|
||||
Cache::forget('profile:following:' . $actor);
|
||||
Cache::forget(self::FOLLOWERS_LOCAL_KEY . $actor);
|
||||
Cache::forget(self::FOLLOWERS_LOCAL_KEY . $target);
|
||||
}
|
||||
|
||||
public static function remove($actor, $target, $silent = false)
|
||||
{
|
||||
Redis::zrem(self::FOLLOWING_KEY . $actor, $target);
|
||||
Redis::zrem(self::FOLLOWERS_KEY . $target, $actor);
|
||||
Cache::forget(self::FOLLOWERS_LOCAL_KEY . $actor);
|
||||
Cache::forget(self::FOLLOWERS_LOCAL_KEY . $target);
|
||||
if($silent !== true) {
|
||||
AccountService::del($actor);
|
||||
AccountService::del($target);
|
||||
|
@ -151,18 +155,26 @@ class FollowerService
|
|||
protected function getAudienceInboxes($pid, $scope = null)
|
||||
{
|
||||
$key = 'pf:services:follower:audience:' . $pid;
|
||||
$domains = Cache::remember($key, 432000, function() use($pid) {
|
||||
$bannedDomains = InstanceService::getBannedDomains();
|
||||
$domains = Cache::remember($key, 432000, function() use($pid, $bannedDomains) {
|
||||
$profile = Profile::whereNull(['status', 'domain'])->find($pid);
|
||||
if(!$profile) {
|
||||
return [];
|
||||
}
|
||||
return $profile
|
||||
->followers()
|
||||
return DB::table('followers')
|
||||
->join('profiles', 'followers.profile_id', '=', 'profiles.id')
|
||||
->where('followers.following_id', $pid)
|
||||
->whereNotNull('profiles.inbox_url')
|
||||
->whereNull('profiles.deleted_at')
|
||||
->select('followers.profile_id', 'followers.following_id', 'profiles.id', 'profiles.user_id', 'profiles.deleted_at', 'profiles.sharedInbox', 'profiles.inbox_url')
|
||||
->get()
|
||||
->map(function($follow) {
|
||||
return $follow->sharedInbox ?? $follow->inbox_url;
|
||||
->map(function($r) {
|
||||
return $r->sharedInbox ?? $r->inbox_url;
|
||||
})
|
||||
->filter(function($r) use($bannedDomains) {
|
||||
$domain = parse_url($r, PHP_URL_HOST);
|
||||
return $r && !in_array($domain, $bannedDomains);
|
||||
})
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
});
|
||||
|
@ -241,7 +253,13 @@ class FollowerService
|
|||
{
|
||||
$key = self::FOLLOWERS_LOCAL_KEY . $pid;
|
||||
$res = Cache::remember($key, 7200, function() use($pid) {
|
||||
return DB::table('followers')->whereFollowingId($pid)->whereLocalProfile(true)->pluck('profile_id')->sort();
|
||||
return DB::table('followers')
|
||||
->join('profiles', 'followers.profile_id', '=', 'profiles.id')
|
||||
->where('followers.following_id', $pid)
|
||||
->whereNotNull('profiles.user_id')
|
||||
->whereNull('profiles.deleted_at')
|
||||
->select('followers.profile_id', 'followers.following_id', 'profiles.id', 'profiles.user_id', 'profiles.deleted_at')
|
||||
->pluck('followers.profile_id');
|
||||
});
|
||||
return $limit ?
|
||||
$res->take($limit)->values()->toArray() :
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class NodeinfoService
|
||||
{
|
||||
|
@ -60,7 +59,7 @@ class NodeinfoService
|
|||
$hrefDomain = parse_url($href, PHP_URL_HOST);
|
||||
|
||||
if ($domain !== $hrefDomain) {
|
||||
return 60;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -77,6 +76,7 @@ class NodeinfoService
|
|||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $res->json();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ use Illuminate\Support\Str;
|
|||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
|
||||
|
||||
class SearchApiV2Service
|
||||
{
|
||||
private $query;
|
||||
|
@ -119,7 +118,7 @@ class SearchApiV2Service
|
|||
AccountService::get($res['id']);
|
||||
})
|
||||
->filter(function ($account) {
|
||||
return $account && isset($account['id']);
|
||||
return $account && isset($account['id']) && ! isset($account['moved'], $account['moved']['id']);
|
||||
})
|
||||
->values();
|
||||
|
||||
|
@ -248,7 +247,7 @@ class SearchApiV2Service
|
|||
|
||||
if ($sid = Status::whereUri($query)->first()) {
|
||||
$s = StatusService::get($sid->id, false);
|
||||
if (! $s) {
|
||||
if (! $s || isset($s['account']['moved'], $s['account']['moved']['id'])) {
|
||||
return $default;
|
||||
}
|
||||
if (in_array($s['visibility'], ['public', 'unlisted'])) {
|
||||
|
@ -359,7 +358,7 @@ class SearchApiV2Service
|
|||
->whereUsername($query)
|
||||
->first();
|
||||
|
||||
if (! $profile) {
|
||||
if (! $profile || $profile->moved_to_profile_id) {
|
||||
return [
|
||||
'accounts' => [],
|
||||
'hashtags' => [],
|
||||
|
@ -367,9 +366,9 @@ class SearchApiV2Service
|
|||
];
|
||||
}
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||
$fractal = new Fractal\Manager;
|
||||
$fractal->setSerializer(new ArraySerializer);
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||
|
||||
return [
|
||||
'accounts' => [$fractal->createData($resource)->toArray()],
|
||||
|
@ -393,9 +392,9 @@ class SearchApiV2Service
|
|||
];
|
||||
}
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||
$fractal = new Fractal\Manager;
|
||||
$fractal->setSerializer(new ArraySerializer);
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||
|
||||
return [
|
||||
'accounts' => [$fractal->createData($resource)->toArray()],
|
||||
|
|
|
@ -30,9 +30,9 @@ class StatusService
|
|||
if (! $status) {
|
||||
return null;
|
||||
}
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
||||
$fractal = new Fractal\Manager;
|
||||
$fractal->setSerializer(new ArraySerializer);
|
||||
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer);
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
$res['_pid'] = isset($res['account']) && isset($res['account']['id']) ? $res['account']['id'] : null;
|
||||
if (isset($res['_pid'])) {
|
||||
|
@ -112,7 +112,7 @@ class StatusService
|
|||
{
|
||||
$status = self::get($id, false);
|
||||
|
||||
if (! $status) {
|
||||
if (! $status || ! $pid) {
|
||||
return [
|
||||
'liked' => false,
|
||||
'shared' => false,
|
||||
|
@ -146,9 +146,9 @@ class StatusService
|
|||
return null;
|
||||
}
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
||||
$fractal = new Fractal\Manager;
|
||||
$fractal->setSerializer(new ArraySerializer);
|
||||
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer);
|
||||
|
||||
return $fractal->createData($resource)->toArray();
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ class Helpers
|
|||
return in_array($url, $audience['to']) || in_array($url, $audience['cc']);
|
||||
}
|
||||
|
||||
public static function validateUrl($url = null, $disableDNSCheck = false)
|
||||
public static function validateUrl($url = null, $disableDNSCheck = false, $forceBanCheck = false)
|
||||
{
|
||||
if (is_array($url) && ! empty($url)) {
|
||||
$url = $url[0];
|
||||
|
@ -212,7 +212,7 @@ class Helpers
|
|||
}
|
||||
}
|
||||
|
||||
if ($disableDNSCheck !== true && app()->environment() === 'production') {
|
||||
if ($forceBanCheck || $disableDNSCheck !== true && app()->environment() === 'production') {
|
||||
$bannedInstances = InstanceService::getBannedDomains();
|
||||
if (in_array($host, $bannedInstances)) {
|
||||
return false;
|
||||
|
@ -739,7 +739,7 @@ class Helpers
|
|||
$width = isset($media['width']) ? $media['width'] : false;
|
||||
$height = isset($media['height']) ? $media['height'] : false;
|
||||
|
||||
$media = new Media();
|
||||
$media = new Media;
|
||||
$media->blurhash = $blurhash;
|
||||
$media->remote_media = true;
|
||||
$media->status_id = $status->id;
|
||||
|
@ -801,12 +801,19 @@ class Helpers
|
|||
return self::profileUpdateOrCreate($url);
|
||||
}
|
||||
|
||||
public static function profileUpdateOrCreate($url)
|
||||
public static function profileUpdateOrCreate($url, $movedToCheck = false)
|
||||
{
|
||||
$movedToPid = null;
|
||||
$res = self::fetchProfileFromUrl($url);
|
||||
if (! $res || isset($res['id']) == false) {
|
||||
return;
|
||||
}
|
||||
if (! self::validateUrl($res['inbox'])) {
|
||||
return;
|
||||
}
|
||||
if (! self::validateUrl($res['id'])) {
|
||||
return;
|
||||
}
|
||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||
$domain = parse_url($res['id'], PHP_URL_HOST);
|
||||
if (strtolower($urlDomain) !== strtolower($domain)) {
|
||||
|
@ -829,13 +836,6 @@ class Helpers
|
|||
$remoteUsername = $username;
|
||||
$webfinger = "@{$username}@{$domain}";
|
||||
|
||||
if (! self::validateUrl($res['inbox'])) {
|
||||
return;
|
||||
}
|
||||
if (! self::validateUrl($res['id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = Instance::updateOrCreate([
|
||||
'domain' => $domain,
|
||||
]);
|
||||
|
@ -843,6 +843,13 @@ class Helpers
|
|||
\App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
|
||||
}
|
||||
|
||||
if (! $movedToCheck && isset($res['movedTo']) && Helpers::validateUrl($res['movedTo'])) {
|
||||
$movedTo = self::profileUpdateOrCreate($res['movedTo'], true);
|
||||
if ($movedTo) {
|
||||
$movedToPid = $movedTo->id;
|
||||
}
|
||||
}
|
||||
|
||||
$profile = Profile::updateOrCreate(
|
||||
[
|
||||
'domain' => strtolower($domain),
|
||||
|
@ -859,6 +866,7 @@ class Helpers
|
|||
'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null,
|
||||
'public_key' => $res['publicKey']['publicKeyPem'],
|
||||
'indexable' => isset($res['indexable']) && is_bool($res['indexable']) ? $res['indexable'] : false,
|
||||
'moved_to_profile_id' => $movedToPid,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -25,7 +25,13 @@ class HttpSignature
|
|||
$stringToSign = self::_headersToSigningString($headers);
|
||||
$signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
|
||||
$key = openssl_pkey_get_private($user->private_key);
|
||||
if (empty($key)) {
|
||||
return [];
|
||||
}
|
||||
openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
|
||||
if (empty($signature)) {
|
||||
return [];
|
||||
}
|
||||
$signature = base64_encode($signature);
|
||||
$signatureHeader = 'keyId="'.$user->keyId().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
|
||||
unset($headers['(request-target)']);
|
||||
|
@ -34,6 +40,34 @@ class HttpSignature
|
|||
return self::_headersToCurlArray($headers);
|
||||
}
|
||||
|
||||
public static function signRaw($privateKey, $keyId, $url, $body = false, $addlHeaders = [])
|
||||
{
|
||||
if (empty($privateKey) || empty($keyId)) {
|
||||
return [];
|
||||
}
|
||||
if ($body) {
|
||||
$digest = self::_digest($body);
|
||||
}
|
||||
$headers = self::_headersToSign($url, $body ? $digest : false);
|
||||
$headers = array_merge($headers, $addlHeaders);
|
||||
$stringToSign = self::_headersToSigningString($headers);
|
||||
$signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
|
||||
$key = openssl_pkey_get_private($privateKey);
|
||||
if (empty($key)) {
|
||||
return [];
|
||||
}
|
||||
openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
|
||||
if (empty($signature)) {
|
||||
return [];
|
||||
}
|
||||
$signature = base64_encode($signature);
|
||||
$signatureHeader = 'keyId="'.$keyId.'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
|
||||
unset($headers['(request-target)']);
|
||||
$headers['Signature'] = $signatureHeader;
|
||||
|
||||
return self::_headersToCurlArray($headers);
|
||||
}
|
||||
|
||||
public static function instanceActorSign($url, $body = false, $addlHeaders = [], $method = 'post')
|
||||
{
|
||||
$keyId = config('app.url').'/i/actor#main-key';
|
||||
|
|
|
@ -2,59 +2,60 @@
|
|||
|
||||
namespace App\Util\ActivityPub;
|
||||
|
||||
use Cache, DB, Log, Purify, Redis, Storage, Validator;
|
||||
use App\{
|
||||
Activity,
|
||||
DirectMessage,
|
||||
Follower,
|
||||
FollowRequest,
|
||||
Instance,
|
||||
Like,
|
||||
Notification,
|
||||
Media,
|
||||
Profile,
|
||||
Status,
|
||||
StatusHashtag,
|
||||
Story,
|
||||
StoryView,
|
||||
UserFilter
|
||||
};
|
||||
use Carbon\Carbon;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Jobs\LikePipeline\LikePipeline;
|
||||
use App\Jobs\FollowPipeline\FollowPipeline;
|
||||
use App\DirectMessage;
|
||||
use App\Follower;
|
||||
use App\FollowRequest;
|
||||
use App\Instance;
|
||||
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||
use App\Jobs\FollowPipeline\FollowPipeline;
|
||||
use App\Jobs\HomeFeedPipeline\FeedRemoveRemotePipeline;
|
||||
use App\Jobs\LikePipeline\LikePipeline;
|
||||
use App\Jobs\MovePipeline\CleanupLegacyAccountMovePipeline;
|
||||
use App\Jobs\MovePipeline\MoveMigrateFollowersPipeline;
|
||||
use App\Jobs\MovePipeline\ProcessMovePipeline;
|
||||
use App\Jobs\MovePipeline\UnfollowLegacyAccountMovePipeline;
|
||||
use App\Jobs\ProfilePipeline\HandleUpdateActivity;
|
||||
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||
use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline;
|
||||
use App\Jobs\StoryPipeline\StoryExpire;
|
||||
use App\Jobs\StoryPipeline\StoryFetch;
|
||||
use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline;
|
||||
use App\Jobs\ProfilePipeline\HandleUpdateActivity;
|
||||
|
||||
use App\Like;
|
||||
use App\Media;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\RemoteReport;
|
||||
use App\Notification;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\PollService;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\UserFilterService;
|
||||
use App\Status;
|
||||
use App\Story;
|
||||
use App\StoryView;
|
||||
use App\UserFilter;
|
||||
use App\Util\ActivityPub\Validator\Accept as AcceptValidator;
|
||||
use App\Util\ActivityPub\Validator\Add as AddValidator;
|
||||
use App\Util\ActivityPub\Validator\Announce as AnnounceValidator;
|
||||
use App\Util\ActivityPub\Validator\Follow as FollowValidator;
|
||||
use App\Util\ActivityPub\Validator\Like as LikeValidator;
|
||||
use App\Util\ActivityPub\Validator\UndoFollow as UndoFollowValidator;
|
||||
use App\Util\ActivityPub\Validator\MoveValidator;
|
||||
use App\Util\ActivityPub\Validator\UpdatePersonValidator;
|
||||
|
||||
use App\Services\AccountService;
|
||||
use App\Services\PollService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\UserFilterService;
|
||||
use App\Services\NetworkTimelineService;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\RemoteReport;
|
||||
use App\Jobs\HomeFeedPipeline\FeedRemoveRemotePipeline;
|
||||
use Cache;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Purify;
|
||||
use Storage;
|
||||
use Throwable;
|
||||
|
||||
class Inbox
|
||||
{
|
||||
protected $headers;
|
||||
|
||||
protected $profile;
|
||||
|
||||
protected $payload;
|
||||
|
||||
protected $logger;
|
||||
|
||||
public function __construct($headers, $profile, $payload)
|
||||
|
@ -67,7 +68,7 @@ class Inbox
|
|||
public function handle()
|
||||
{
|
||||
$this->handleVerb();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function handleVerb()
|
||||
|
@ -84,17 +85,23 @@ class Inbox
|
|||
break;
|
||||
|
||||
case 'Follow':
|
||||
if(FollowValidator::validate($this->payload) == false) { return; }
|
||||
if (FollowValidator::validate($this->payload) == false) {
|
||||
return;
|
||||
}
|
||||
$this->handleFollowActivity();
|
||||
break;
|
||||
|
||||
case 'Announce':
|
||||
if(AnnounceValidator::validate($this->payload) == false) { return; }
|
||||
if (AnnounceValidator::validate($this->payload) == false) {
|
||||
return;
|
||||
}
|
||||
$this->handleAnnounceActivity();
|
||||
break;
|
||||
|
||||
case 'Accept':
|
||||
if(AcceptValidator::validate($this->payload) == false) { return; }
|
||||
if (AcceptValidator::validate($this->payload) == false) {
|
||||
return;
|
||||
}
|
||||
$this->handleAcceptActivity();
|
||||
break;
|
||||
|
||||
|
@ -103,7 +110,9 @@ class Inbox
|
|||
break;
|
||||
|
||||
case 'Like':
|
||||
if(LikeValidator::validate($this->payload) == false) { return; }
|
||||
if (LikeValidator::validate($this->payload) == false) {
|
||||
return;
|
||||
}
|
||||
$this->handleLikeActivity();
|
||||
break;
|
||||
|
||||
|
@ -135,6 +144,15 @@ class Inbox
|
|||
$this->handleUpdateActivity();
|
||||
break;
|
||||
|
||||
case 'Move':
|
||||
if (MoveValidator::validate($this->payload) == false) {
|
||||
Log::info('[AP][INBOX][MOVE] VALIDATE_FAILURE '.json_encode($this->payload));
|
||||
|
||||
return;
|
||||
}
|
||||
$this->handleMoveActivity();
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO: decide how to handle invalid verbs.
|
||||
break;
|
||||
|
@ -191,7 +209,6 @@ class Inbox
|
|||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleCreateActivity()
|
||||
|
@ -226,6 +243,7 @@ class Inbox
|
|||
|
||||
if ($activity['type'] == 'Question') {
|
||||
$this->handlePollCreate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -236,6 +254,7 @@ class Inbox
|
|||
parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app')
|
||||
) {
|
||||
$this->handleDirectMessage();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -248,7 +267,7 @@ class Inbox
|
|||
}
|
||||
$this->handleNoteCreate();
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function handleNoteReply()
|
||||
|
@ -263,7 +282,7 @@ class Inbox
|
|||
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
||||
|
||||
Helpers::statusFirstOrFetch($url, true);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function handlePollCreate()
|
||||
|
@ -275,7 +294,7 @@ class Inbox
|
|||
}
|
||||
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
||||
Helpers::statusFirstOrFetch($url);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function handleNoteCreate()
|
||||
|
@ -293,6 +312,7 @@ class Inbox
|
|||
Helpers::validateLocalUrl($activity['inReplyTo'])
|
||||
) {
|
||||
$this->handlePollVote();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -321,7 +341,7 @@ class Inbox
|
|||
$actor,
|
||||
$activity
|
||||
);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function handlePollVote()
|
||||
|
@ -376,7 +396,6 @@ class Inbox
|
|||
|
||||
PollService::del($status->id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleDirectMessage()
|
||||
|
@ -436,13 +455,13 @@ class Inbox
|
|||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $profile->id,
|
||||
'from_id' => $actor->id
|
||||
'from_id' => $actor->id,
|
||||
],
|
||||
[
|
||||
'type' => 'text',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => $hidden
|
||||
'is_hidden' => $hidden,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -459,7 +478,7 @@ class Inbox
|
|||
continue;
|
||||
}
|
||||
|
||||
$media = new Media();
|
||||
$media = new Media;
|
||||
$media->remote_media = true;
|
||||
$media->status_id = $status->id;
|
||||
$media->profile_id = $status->profile_id;
|
||||
|
@ -492,7 +511,7 @@ class Inbox
|
|||
$dm->meta = [
|
||||
'domain' => parse_url($msgText, PHP_URL_HOST),
|
||||
'local' => parse_url($msgText, PHP_URL_HOST) ==
|
||||
parse_url(config('app.url'), PHP_URL_HOST)
|
||||
parse_url(config('app.url'), PHP_URL_HOST),
|
||||
];
|
||||
$dm->save();
|
||||
}
|
||||
|
@ -505,7 +524,7 @@ class Inbox
|
|||
->exists();
|
||||
|
||||
if ($profile->domain == null && $hidden == false && ! $nf) {
|
||||
$notification = new Notification();
|
||||
$notification = new Notification;
|
||||
$notification->profile_id = $profile->id;
|
||||
$notification->actor_id = $actor->id;
|
||||
$notification->action = 'dm';
|
||||
|
@ -514,7 +533,6 @@ class Inbox
|
|||
$notification->save();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleFollowActivity()
|
||||
|
@ -554,7 +572,7 @@ class Inbox
|
|||
'follower_id' => $actor->id,
|
||||
'following_id' => $target->id,
|
||||
], [
|
||||
'activity' => collect($this->payload)->only(['id','actor','object','type'])->toArray()
|
||||
'activity' => collect($this->payload)->only(['id', 'actor', 'object', 'type'])->toArray(),
|
||||
]);
|
||||
} else {
|
||||
$follower = new Follower;
|
||||
|
@ -576,8 +594,8 @@ class Inbox
|
|||
'id' => $this->payload['id'],
|
||||
'actor' => $actor->permalink(),
|
||||
'type' => 'Follow',
|
||||
'object' => $target->permalink()
|
||||
]
|
||||
'object' => $target->permalink(),
|
||||
],
|
||||
];
|
||||
Helpers::sendSignedObject($target, $actor->inbox_url, $accept);
|
||||
Cache::forget('profile:follower_count:'.$target->id);
|
||||
|
@ -586,7 +604,6 @@ class Inbox
|
|||
Cache::forget('profile:following_count:'.$actor->id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleAnnounceActivity()
|
||||
|
@ -616,7 +633,7 @@ class Inbox
|
|||
$status = Status::firstOrCreate([
|
||||
'profile_id' => $actor->id,
|
||||
'reblog_of_id' => $parent->id,
|
||||
'type' => 'share'
|
||||
'type' => 'share',
|
||||
]);
|
||||
|
||||
Notification::firstOrCreate(
|
||||
|
@ -634,7 +651,6 @@ class Inbox
|
|||
|
||||
ReblogService::addPostReblog($parent->profile_id, $status->id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleAcceptActivity()
|
||||
|
@ -682,7 +698,6 @@ class Inbox
|
|||
|
||||
$request->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleDeleteActivity()
|
||||
|
@ -701,6 +716,7 @@ class Inbox
|
|||
return;
|
||||
}
|
||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
|
||||
|
||||
return;
|
||||
} else {
|
||||
if (! isset(
|
||||
|
@ -727,6 +743,7 @@ class Inbox
|
|||
return;
|
||||
}
|
||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
|
||||
|
||||
return;
|
||||
break;
|
||||
|
||||
|
@ -752,6 +769,7 @@ class Inbox
|
|||
}
|
||||
}
|
||||
RemoteStatusDelete::dispatch($status)->onQueue('high');
|
||||
|
||||
return;
|
||||
break;
|
||||
|
||||
|
@ -761,6 +779,7 @@ class Inbox
|
|||
if ($story) {
|
||||
StoryExpire::dispatch($story)->onQueue('story');
|
||||
}
|
||||
|
||||
return;
|
||||
break;
|
||||
|
||||
|
@ -769,7 +788,7 @@ class Inbox
|
|||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function handleLikeActivity()
|
||||
|
@ -801,7 +820,7 @@ class Inbox
|
|||
|
||||
$like = Like::firstOrCreate([
|
||||
'profile_id' => $profile->id,
|
||||
'status_id' => $status->id
|
||||
'status_id' => $status->id,
|
||||
]);
|
||||
|
||||
if ($like->wasRecentlyCreated == true) {
|
||||
|
@ -810,12 +829,9 @@ class Inbox
|
|||
LikePipeline::dispatch($like);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleRejectActivity()
|
||||
{
|
||||
}
|
||||
public function handleRejectActivity() {}
|
||||
|
||||
public function handleUndoActivity()
|
||||
{
|
||||
|
@ -917,7 +933,7 @@ class Inbox
|
|||
->forceDelete();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function handleViewActivity()
|
||||
|
@ -969,7 +985,7 @@ class Inbox
|
|||
|
||||
$view = StoryView::firstOrCreate([
|
||||
'story_id' => $story->id,
|
||||
'profile_id' => $profile->id
|
||||
'profile_id' => $profile->id,
|
||||
]);
|
||||
|
||||
if ($view->wasRecentlyCreated == true) {
|
||||
|
@ -977,7 +993,6 @@ class Inbox
|
|||
$story->save();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleStoryReactionActivity()
|
||||
|
@ -1060,7 +1075,7 @@ class Inbox
|
|||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id,
|
||||
'reaction' => $text
|
||||
'reaction' => $text,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -1074,20 +1089,20 @@ class Inbox
|
|||
'story_actor_username' => $actorProfile->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'reaction' => $text
|
||||
'reaction' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $actorProfile->id
|
||||
'from_id' => $actorProfile->id,
|
||||
],
|
||||
[
|
||||
'type' => 'story:react',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -1099,7 +1114,6 @@ class Inbox
|
|||
$n->action = 'story:react';
|
||||
$n->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleStoryReplyActivity()
|
||||
|
@ -1155,7 +1169,6 @@ class Inbox
|
|||
|
||||
$actorProfile = Helpers::profileFetch($actor);
|
||||
|
||||
|
||||
if (AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) {
|
||||
return;
|
||||
}
|
||||
|
@ -1183,7 +1196,7 @@ class Inbox
|
|||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id,
|
||||
'caption' => $text
|
||||
'caption' => $text,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -1197,20 +1210,20 @@ class Inbox
|
|||
'story_actor_username' => $actorProfile->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'caption' => $text
|
||||
'caption' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $actorProfile->id
|
||||
'from_id' => $actorProfile->id,
|
||||
],
|
||||
[
|
||||
'type' => 'story:comment',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -1222,7 +1235,6 @@ class Inbox
|
|||
$n->action = 'story:comment';
|
||||
$n->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleFlagActivity()
|
||||
|
@ -1307,7 +1319,7 @@ class Inbox
|
|||
$instanceHost = parse_url($id, PHP_URL_HOST);
|
||||
|
||||
$instance = Instance::updateOrCreate([
|
||||
'domain' => $instanceHost
|
||||
'domain' => $instanceHost,
|
||||
]);
|
||||
|
||||
$report = new RemoteReport;
|
||||
|
@ -1318,11 +1330,10 @@ class Inbox
|
|||
$report->instance_id = $instance->id;
|
||||
$report->report_meta = [
|
||||
'actor' => $actor,
|
||||
'object' => $object
|
||||
'object' => $object,
|
||||
];
|
||||
$report->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public function handleUpdateActivity()
|
||||
|
@ -1347,4 +1358,31 @@ class Inbox
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function handleMoveActivity()
|
||||
{
|
||||
$actor = $this->payload['actor'];
|
||||
$activity = $this->payload['object'];
|
||||
$target = $this->payload['target'];
|
||||
if (
|
||||
! Helpers::validateUrl($actor) ||
|
||||
! Helpers::validateUrl($activity) ||
|
||||
! Helpers::validateUrl($target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bus::chain([
|
||||
new ProcessMovePipeline($target, $activity),
|
||||
new MoveMigrateFollowersPipeline($target, $activity),
|
||||
new UnfollowLegacyAccountMovePipeline($target, $activity),
|
||||
new CleanupLegacyAccountMovePipeline($target, $activity),
|
||||
])
|
||||
->catch(function (Throwable $e) {
|
||||
Log::error($e);
|
||||
})
|
||||
->onQueue('move')
|
||||
->delay(now()->addMinutes(random_int(1, 3)))
|
||||
->dispatch();
|
||||
}
|
||||
}
|
||||
|
|
23
app/Util/ActivityPub/Validator/MoveValidator.php
Normal file
23
app/Util/ActivityPub/Validator/MoveValidator.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\ActivityPub\Validator;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Validator;
|
||||
|
||||
class MoveValidator
|
||||
{
|
||||
public static function validate($payload)
|
||||
{
|
||||
return Validator::make($payload, [
|
||||
'@context' => 'required',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['Move']),
|
||||
],
|
||||
'actor' => 'required|url',
|
||||
'object' => 'required|url',
|
||||
'target' => 'required|url',
|
||||
])->passes();
|
||||
}
|
||||
}
|
|
@ -92,6 +92,7 @@ return [
|
|||
'redis:intbg' => 30,
|
||||
'redis:adelete' => 30,
|
||||
'redis:groups' => 30,
|
||||
'redis:move' => 30,
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -175,7 +176,7 @@ return [
|
|||
'production' => [
|
||||
'supervisor-1' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo', 'intbg', 'groups', 'adelete'],
|
||||
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo', 'intbg', 'groups', 'adelete', 'move'],
|
||||
'balance' => env('HORIZON_BALANCE_STRATEGY', 'auto'),
|
||||
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 20),
|
||||
|
@ -189,7 +190,7 @@ return [
|
|||
'local' => [
|
||||
'supervisor-1' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo', 'intbg', 'groups', 'adelete'],
|
||||
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo', 'intbg', 'groups', 'adelete', 'move'],
|
||||
'balance' => 'auto',
|
||||
'minProcesses' => 1,
|
||||
'maxProcesses' => 20,
|
||||
|
|
Loading…
Reference in a new issue