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))
|
- 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))
|
- 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
|
### Updates
|
||||||
- Update ApiV1Controller, add support for notification filter types ([f61159a1](https://github.com/pixelfed/pixelfed/commit/f61159a1))
|
- 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))
|
- 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 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 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 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/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.12.3 (2024-07-01)](https://github.com/pixelfed/pixelfed/compare/v0.12.2...v0.12.3)
|
## [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();
|
$activity = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
$audience = Instance::whereNotNull(['shared_inbox', 'nodeinfo_last_fetched'])
|
$audience = Instance::whereNotNull(['shared_inbox', 'nodeinfo_last_fetched'])
|
||||||
->where('nodeinfo_last_fetched', '>', now()->subHours(12))
|
->where('nodeinfo_last_fetched', '>', now()->subDays(14))
|
||||||
->distinct()
|
->distinct()
|
||||||
->pluck('shared_inbox');
|
->pluck('shared_inbox');
|
||||||
|
|
||||||
$payload = json_encode($activity);
|
$payload = json_encode($activity);
|
||||||
|
|
||||||
$client = new Client([
|
$client = new Client([
|
||||||
'timeout' => 10,
|
'timeout' => 5,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$version = config('pixelfed.version');
|
$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['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||||
$res['curated_onboarding'] = (bool) config_cache('instance.curated_registration.enabled');
|
$res['curated_onboarding'] = (bool) config_cache('instance.curated_registration.enabled');
|
||||||
$res['open_registration'] = (bool) config_cache('pixelfed.open_registration');
|
$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');
|
$res['activitypub_enabled'] = (bool) config_cache('federation.activitypub.enabled');
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,9 @@ trait AdminSettingsController
|
||||||
if ($key == 'mobile_apis' &&
|
if ($key == 'mobile_apis' &&
|
||||||
$active &&
|
$active &&
|
||||||
! file_exists(storage_path('oauth-public.key')) &&
|
! 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('passport:keys');
|
||||||
Artisan::call('route:cache');
|
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\MediaTransformer;
|
||||||
use App\Transformer\Api\Mastodon\v1\NotificationTransformer;
|
use App\Transformer\Api\Mastodon\v1\NotificationTransformer;
|
||||||
use App\Transformer\Api\Mastodon\v1\StatusTransformer;
|
use App\Transformer\Api\Mastodon\v1\StatusTransformer;
|
||||||
use App\Transformer\Api\{
|
use App\Transformer\Api\RelationshipTransformer;
|
||||||
RelationshipTransformer,
|
|
||||||
};
|
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
use App\UserSetting;
|
use App\UserSetting;
|
||||||
|
@ -98,8 +96,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->fractal = new Fractal\Manager();
|
$this->fractal = new Fractal\Manager;
|
||||||
$this->fractal->setSerializer(new ArraySerializer());
|
$this->fractal->setSerializer(new ArraySerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function json($res, $code = 200, $headers = [])
|
public function json($res, $code = 200, $headers = [])
|
||||||
|
@ -490,6 +488,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(! $account, 404);
|
abort_if(! $account, 404);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'limit' => 'sometimes|integer|min:1',
|
'limit' => 'sometimes|integer|min:1',
|
||||||
|
@ -591,6 +590,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(! $account, 404);
|
abort_if(! $account, 404);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'limit' => 'sometimes|integer|min:1',
|
'limit' => 'sometimes|integer|min:1',
|
||||||
|
@ -818,6 +818,8 @@ class ApiV1Controller extends Controller
|
||||||
->whereNull('status')
|
->whereNull('status')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($target && $target->moved_to_profile_id, 400, 'Cannot follow an account that has moved!');
|
||||||
|
|
||||||
if ($target && $target->domain) {
|
if ($target && $target->domain) {
|
||||||
$domain = $target->domain;
|
$domain = $target->domain;
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
@ -857,7 +859,7 @@ class ApiV1Controller extends Controller
|
||||||
'following_id' => $target->id,
|
'following_id' => $target->id,
|
||||||
]);
|
]);
|
||||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||||
(new FollowerController())->sendFollow($user->profile, $target);
|
(new FollowerController)->sendFollow($user->profile, $target);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$follower = Follower::firstOrCreate([
|
$follower = Follower::firstOrCreate([
|
||||||
|
@ -866,7 +868,7 @@ class ApiV1Controller extends Controller
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
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');
|
FollowPipeline::dispatch($follower)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
@ -925,7 +927,7 @@ class ApiV1Controller extends Controller
|
||||||
$followRequest->delete();
|
$followRequest->delete();
|
||||||
RelationshipService::refresh($target->id, $user->profile_id);
|
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();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -938,7 +940,7 @@ class ApiV1Controller extends Controller
|
||||||
UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high');
|
UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high');
|
||||||
|
|
||||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
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);
|
RelationshipService::refresh($user->profile_id, $target->id);
|
||||||
|
@ -1132,6 +1134,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = Profile::findOrFail($id);
|
$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) {
|
if ($profile->user && $profile->user->is_admin == true) {
|
||||||
abort(400, 'You cannot block an admin');
|
abort(400, 'You cannot block an admin');
|
||||||
}
|
}
|
||||||
|
@ -1198,7 +1202,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
UserFilterService::block($pid, $id);
|
UserFilterService::block($pid, $id);
|
||||||
RelationshipService::refresh($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();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -1225,6 +1229,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = Profile::findOrFail($id);
|
$profile = Profile::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($profile->moved_to_profile_id, 422, 'Cannot unblock an account that has migrated!');
|
||||||
|
|
||||||
$filter = UserFilter::whereUserId($pid)
|
$filter = UserFilter::whereUserId($pid)
|
||||||
->whereFilterableId($profile->id)
|
->whereFilterableId($profile->id)
|
||||||
->whereFilterableType('App\Profile')
|
->whereFilterableType('App\Profile')
|
||||||
|
@ -1237,7 +1243,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
RelationshipService::refresh($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();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -1372,6 +1378,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
abort_unless($status, 404);
|
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) {
|
if ($status && isset($status['account'], $status['account']['acct']) && strpos($status['account']['acct'], '@') != -1) {
|
||||||
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
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);
|
$status = $napi ? StatusService::get($id, false) : StatusService::getMastodon($id, false);
|
||||||
|
|
||||||
abort_unless($status && isset($status['account']), 404);
|
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) {
|
if ($status && isset($status['account'], $status['account']['acct']) && strpos($status['account']['acct'], '@') != -1) {
|
||||||
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
||||||
|
@ -1540,6 +1549,8 @@ class ApiV1Controller extends Controller
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$target = AccountService::getMastodon($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) {
|
if (! $target) {
|
||||||
return response()->json(['error' => 'Record not found'], 404);
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
}
|
}
|
||||||
|
@ -1556,7 +1567,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$follower = $followRequest->follower;
|
$follower = $followRequest->follower;
|
||||||
$follow = new Follower();
|
$follow = new Follower;
|
||||||
$follow->profile_id = $follower->id;
|
$follow->profile_id = $follower->id;
|
||||||
$follow->following_id = $pid;
|
$follow->following_id = $pid;
|
||||||
$follow->save();
|
$follow->save();
|
||||||
|
@ -1603,6 +1614,8 @@ class ApiV1Controller extends Controller
|
||||||
return response()->json(['error' => 'Record not found'], 404);
|
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();
|
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
|
||||||
|
|
||||||
if (! $followRequest) {
|
if (! $followRequest) {
|
||||||
|
@ -1661,7 +1674,7 @@ class ApiV1Controller extends Controller
|
||||||
null;
|
null;
|
||||||
});
|
});
|
||||||
|
|
||||||
$stats = Cache::remember('api:v1:instance-data:stats', 43200, function () {
|
$stats = Cache::remember('api:v1:instance-data:stats:v0', 43200, function () {
|
||||||
return [
|
return [
|
||||||
'user_count' => (int) User::count(),
|
'user_count' => (int) User::count(),
|
||||||
'status_count' => (int) StatusService::totalLocalStatuses(),
|
'status_count' => (int) StatusService::totalLocalStatuses(),
|
||||||
|
@ -1857,7 +1870,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->status_id = null;
|
$media->status_id = null;
|
||||||
$media->profile_id = $profile->id;
|
$media->profile_id = $profile->id;
|
||||||
$media->user_id = $user->id;
|
$media->user_id = $user->id;
|
||||||
|
@ -1891,7 +1904,7 @@ class ApiV1Controller extends Controller
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
Cache::forget($limitKey);
|
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 = $this->fractal->createData($resource)->toArray();
|
||||||
$res['preview_url'] = $media->url().'?v='.time();
|
$res['preview_url'] = $media->url().'?v='.time();
|
||||||
$res['url'] = $media->url().'?v='.time();
|
$res['url'] = $media->url().'?v='.time();
|
||||||
|
@ -1946,9 +1959,9 @@ class ApiV1Controller extends Controller
|
||||||
], 429);
|
], 429);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||||
|
|
||||||
return $this->json($fractal->createData($resource)->toArray());
|
return $this->json($fractal->createData($resource)->toArray());
|
||||||
}
|
}
|
||||||
|
@ -1972,7 +1985,7 @@ class ApiV1Controller extends Controller
|
||||||
->whereNull('status_id')
|
->whereNull('status_id')
|
||||||
->findOrFail($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();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -2085,7 +2098,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->status_id = null;
|
$media->status_id = null;
|
||||||
$media->profile_id = $profile->id;
|
$media->profile_id = $profile->id;
|
||||||
$media->user_id = $user->id;
|
$media->user_id = $user->id;
|
||||||
|
@ -2119,7 +2132,7 @@ class ApiV1Controller extends Controller
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
Cache::forget($limitKey);
|
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 = $this->fractal->createData($resource)->toArray();
|
||||||
$res['preview_url'] = $media->url().'?v='.time();
|
$res['preview_url'] = $media->url().'?v='.time();
|
||||||
$res['url'] = null;
|
$res['url'] = null;
|
||||||
|
@ -2204,6 +2217,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$account = Profile::findOrFail($id);
|
$account = Profile::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($account->moved_to_profile_id, 422, 'Cannot mute an account that has migrated!');
|
||||||
|
|
||||||
if ($account && $account->domain) {
|
if ($account && $account->domain) {
|
||||||
$domain = $account->domain;
|
$domain = $account->domain;
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
@ -2237,7 +2252,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
RelationshipService::refresh($pid, $id);
|
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();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -2263,6 +2278,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = Profile::findOrFail($id);
|
$profile = Profile::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($profile->moved_to_profile_id, 422, 'Cannot unmute an account that has migrated!');
|
||||||
|
|
||||||
$filter = UserFilter::whereUserId($pid)
|
$filter = UserFilter::whereUserId($pid)
|
||||||
->whereFilterableId($profile->id)
|
->whereFilterableId($profile->id)
|
||||||
->whereFilterableType('App\Profile')
|
->whereFilterableType('App\Profile')
|
||||||
|
@ -2276,7 +2293,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
RelationshipService::refresh($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();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -3209,6 +3226,7 @@ class ApiV1Controller extends Controller
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$account = AccountService::get($status->profile_id, true);
|
$account = AccountService::get($status->profile_id, true);
|
||||||
abort_if(! $account, 404);
|
abort_if(! $account, 404);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||||
if ($account && strpos($account['acct'], '@') != -1) {
|
if ($account && strpos($account['acct'], '@') != -1) {
|
||||||
$domain = parse_url($account['url'], PHP_URL_HOST);
|
$domain = parse_url($account['url'], PHP_URL_HOST);
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
@ -3308,11 +3326,12 @@ class ApiV1Controller extends Controller
|
||||||
$pid = $user->profile_id;
|
$pid = $user->profile_id;
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$account = AccountService::get($status->profile_id, true);
|
$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) {
|
if ($account && strpos($account['acct'], '@') != -1) {
|
||||||
$domain = parse_url($account['url'], PHP_URL_HOST);
|
$domain = parse_url($account['url'], PHP_URL_HOST);
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
}
|
}
|
||||||
abort_if(! $account, 404);
|
|
||||||
$author = intval($status->profile_id) === intval($pid) || $user->is_admin;
|
$author = intval($status->profile_id) === intval($pid) || $user->is_admin;
|
||||||
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
|
||||||
|
@ -3617,7 +3636,7 @@ class ApiV1Controller extends Controller
|
||||||
$status = Status::whereProfileId($request->user()->profile->id)
|
$status = Status::whereProfileId($request->user()->profile->id)
|
||||||
->findOrFail($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);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
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');
|
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
||||||
AccountService::setLastActive($user->id);
|
AccountService::setLastActive($user->id);
|
||||||
$status = Status::whereScope('public')->findOrFail($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)) {
|
if ($status && ($status->uri || $status->url || $status->object_url)) {
|
||||||
$url = $status->uri ?? $status->url ?? $status->object_url;
|
$url = $status->uri ?? $status->url ?? $status->object_url;
|
||||||
$domain = parse_url($url, PHP_URL_HOST);
|
$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');
|
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
||||||
AccountService::setLastActive($user->id);
|
AccountService::setLastActive($user->id);
|
||||||
$status = Status::whereScope('public')->findOrFail($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 (intval($status->profile_id) !== intval($user->profile_id)) {
|
||||||
if ($status->scope == 'private') {
|
if ($status->scope == 'private') {
|
||||||
|
@ -3929,7 +3952,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$pid = $request->user()->profile_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($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($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||||
|
@ -4045,8 +4069,8 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$status = StatusService::getMastodon($id, false);
|
$status = StatusService::getMastodon($id, false);
|
||||||
|
|
||||||
abort_if(! $status, 404);
|
abort_if(! $status, 404);
|
||||||
|
abort_if(isset($status['account'], $account['account']['moved']['id']), 404, 'Account moved');
|
||||||
|
|
||||||
if ($status['visibility'] == 'private') {
|
if ($status['visibility'] == 'private') {
|
||||||
if ($pid != $status['account']['id']) {
|
if ($pid != $status['account']['id']) {
|
||||||
|
@ -4135,11 +4159,11 @@ class ApiV1Controller extends Controller
|
||||||
{
|
{
|
||||||
abort_if(! $request->user(), 403);
|
abort_if(! $request->user(), 403);
|
||||||
|
|
||||||
$status = Status::findOrFail($id);
|
$status = StatusService::get($id, false, true);
|
||||||
$pid = $request->user()->profile_id;
|
abort_if(! $status, 404);
|
||||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 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;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Bookmark;
|
use App\Bookmark;
|
||||||
use App\Status;
|
use App\Services\AccountService;
|
||||||
use Auth;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Services\BookmarkService;
|
use App\Services\BookmarkService;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Services\UserRoleService;
|
use App\Services\UserRoleService;
|
||||||
|
use App\Status;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class BookmarkController extends Controller
|
class BookmarkController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,8 @@ class BookmarkController extends Controller
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$status = Status::findOrFail($request->input('item'));
|
$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($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($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||||
|
|
|
@ -90,7 +90,8 @@ class PixelfedDirectoryController extends Controller
|
||||||
|
|
||||||
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
||||||
if ($oauthEnabled) {
|
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;
|
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,8 @@ class StatusController extends Controller
|
||||||
! $status ||
|
! $status ||
|
||||||
! isset($status['account'], $status['account']['id'], $status['local']) ||
|
! isset($status['account'], $status['account']['id'], $status['local']) ||
|
||||||
! $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');
|
$content = view('status.embed-removed');
|
||||||
|
|
||||||
|
@ -220,10 +221,7 @@ class StatusController extends Controller
|
||||||
return view('status.compose');
|
return view('status.compose');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request) {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(Request $request)
|
public function delete(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -307,6 +305,8 @@ class StatusController extends Controller
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
$status = Status::whereScope('public')
|
$status = Status::whereScope('public')
|
||||||
->findOrFail($request->input('item'));
|
->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;
|
$count = $status->reblogs_count;
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ class StatusController extends Controller
|
||||||
$count--;
|
$count--;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$share = new Status();
|
$share = new Status;
|
||||||
$share->profile_id = $profile->id;
|
$share->profile_id = $profile->id;
|
||||||
$share->reblog_of_id = $status->id;
|
$share->reblog_of_id = $status->id;
|
||||||
$share->in_reply_to_profile_id = $status->profile_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) {
|
return Cache::remember($key, 3600, function () use ($status) {
|
||||||
$status = Status::findOrFail($status['id']);
|
$status = Status::findOrFail($status['id']);
|
||||||
$object = $status->type == 'poll' ? new Question() : new Note();
|
$object = $status->type == 'poll' ? new Question : new Note;
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$resource = new Fractal\Resource\Item($status, $object);
|
$resource = new Fractal\Resource\Item($status, $object);
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$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)
|
public static function get($id, $softFail = false)
|
||||||
{
|
{
|
||||||
$res = Cache::remember(self::CACHE_KEY.$id, 43200, function () use ($id) {
|
$res = Cache::remember(self::CACHE_KEY.$id, 43200, function () use ($id) {
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$profile = Profile::find($id);
|
$profile = Profile::find($id);
|
||||||
if (! $profile || $profile->status === 'delete') {
|
if (! $profile || $profile->status === 'delete') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||||
|
|
||||||
return $fractal->createData($resource)->toArray();
|
return $fractal->createData($resource)->toArray();
|
||||||
});
|
});
|
||||||
|
@ -291,7 +291,6 @@ class AccountService
|
||||||
$user->save();
|
$user->save();
|
||||||
Cache::put($key, 1, 14400);
|
Cache::put($key, 1, 14400);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function blocksDomain($pid, $domain = false)
|
public static function blocksDomain($pid, $domain = false)
|
||||||
|
|
|
@ -25,59 +25,8 @@ class ActivityPubFetchService
|
||||||
$urlKey = hash('sha256', $url);
|
$urlKey = hash('sha256', $url);
|
||||||
$key = self::CACHE_KEY.$domainKey.':'.$urlKey;
|
$key = self::CACHE_KEY.$domainKey.':'.$urlKey;
|
||||||
|
|
||||||
return Cache::remember($key, 3600, function () use ($url) {
|
return Cache::remember($key, 450, function () use ($url) {
|
||||||
$baseHeaders = [
|
return self::fetchRequest($url);
|
||||||
'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();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,4 +79,60 @@ class ActivityPubFetchService
|
||||||
|
|
||||||
return $url;
|
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_SYNC_KEY = 'pf:services:followers:sync-following:';
|
||||||
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
|
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
|
||||||
const FOLLOWERS_KEY = 'pf:services:follow:followers: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:';
|
const FOLLOWERS_INTER_KEY = 'pf:services:follow:followers:inter:id:';
|
||||||
|
|
||||||
public static function add($actor, $target, $refresh = true)
|
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::FOLLOWING_KEY . $actor, $ts, $target);
|
||||||
Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor);
|
Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor);
|
||||||
Cache::forget('profile:following:' . $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)
|
public static function remove($actor, $target, $silent = false)
|
||||||
{
|
{
|
||||||
Redis::zrem(self::FOLLOWING_KEY . $actor, $target);
|
Redis::zrem(self::FOLLOWING_KEY . $actor, $target);
|
||||||
Redis::zrem(self::FOLLOWERS_KEY . $target, $actor);
|
Redis::zrem(self::FOLLOWERS_KEY . $target, $actor);
|
||||||
|
Cache::forget(self::FOLLOWERS_LOCAL_KEY . $actor);
|
||||||
|
Cache::forget(self::FOLLOWERS_LOCAL_KEY . $target);
|
||||||
if($silent !== true) {
|
if($silent !== true) {
|
||||||
AccountService::del($actor);
|
AccountService::del($actor);
|
||||||
AccountService::del($target);
|
AccountService::del($target);
|
||||||
|
@ -151,18 +155,26 @@ class FollowerService
|
||||||
protected function getAudienceInboxes($pid, $scope = null)
|
protected function getAudienceInboxes($pid, $scope = null)
|
||||||
{
|
{
|
||||||
$key = 'pf:services:follower:audience:' . $pid;
|
$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);
|
$profile = Profile::whereNull(['status', 'domain'])->find($pid);
|
||||||
if(!$profile) {
|
if(!$profile) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return $profile
|
return DB::table('followers')
|
||||||
->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()
|
->get()
|
||||||
->map(function($follow) {
|
->map(function($r) {
|
||||||
return $follow->sharedInbox ?? $follow->inbox_url;
|
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()
|
->unique()
|
||||||
->values();
|
->values();
|
||||||
});
|
});
|
||||||
|
@ -241,7 +253,13 @@ class FollowerService
|
||||||
{
|
{
|
||||||
$key = self::FOLLOWERS_LOCAL_KEY . $pid;
|
$key = self::FOLLOWERS_LOCAL_KEY . $pid;
|
||||||
$res = Cache::remember($key, 7200, function() use($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 ?
|
return $limit ?
|
||||||
$res->take($limit)->values()->toArray() :
|
$res->take($limit)->values()->toArray() :
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
namespace App\Services;
|
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\ConnectionException;
|
||||||
|
use Illuminate\Http\Client\RequestException;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class NodeinfoService
|
class NodeinfoService
|
||||||
{
|
{
|
||||||
|
@ -60,7 +59,7 @@ class NodeinfoService
|
||||||
$hrefDomain = parse_url($href, PHP_URL_HOST);
|
$hrefDomain = parse_url($href, PHP_URL_HOST);
|
||||||
|
|
||||||
if ($domain !== $hrefDomain) {
|
if ($domain !== $hrefDomain) {
|
||||||
return 60;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -77,6 +76,7 @@ class NodeinfoService
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res->json();
|
return $res->json();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ use Illuminate\Support\Str;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
|
|
||||||
class SearchApiV2Service
|
class SearchApiV2Service
|
||||||
{
|
{
|
||||||
private $query;
|
private $query;
|
||||||
|
@ -119,7 +118,7 @@ class SearchApiV2Service
|
||||||
AccountService::get($res['id']);
|
AccountService::get($res['id']);
|
||||||
})
|
})
|
||||||
->filter(function ($account) {
|
->filter(function ($account) {
|
||||||
return $account && isset($account['id']);
|
return $account && isset($account['id']) && ! isset($account['moved'], $account['moved']['id']);
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
|
@ -248,7 +247,7 @@ class SearchApiV2Service
|
||||||
|
|
||||||
if ($sid = Status::whereUri($query)->first()) {
|
if ($sid = Status::whereUri($query)->first()) {
|
||||||
$s = StatusService::get($sid->id, false);
|
$s = StatusService::get($sid->id, false);
|
||||||
if (! $s) {
|
if (! $s || isset($s['account']['moved'], $s['account']['moved']['id'])) {
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
if (in_array($s['visibility'], ['public', 'unlisted'])) {
|
if (in_array($s['visibility'], ['public', 'unlisted'])) {
|
||||||
|
@ -359,7 +358,7 @@ class SearchApiV2Service
|
||||||
->whereUsername($query)
|
->whereUsername($query)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $profile) {
|
if (! $profile || $profile->moved_to_profile_id) {
|
||||||
return [
|
return [
|
||||||
'accounts' => [],
|
'accounts' => [],
|
||||||
'hashtags' => [],
|
'hashtags' => [],
|
||||||
|
@ -367,9 +366,9 @@ class SearchApiV2Service
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'accounts' => [$fractal->createData($resource)->toArray()],
|
'accounts' => [$fractal->createData($resource)->toArray()],
|
||||||
|
@ -393,9 +392,9 @@ class SearchApiV2Service
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'accounts' => [$fractal->createData($resource)->toArray()],
|
'accounts' => [$fractal->createData($resource)->toArray()],
|
||||||
|
|
|
@ -30,9 +30,9 @@ class StatusService
|
||||||
if (! $status) {
|
if (! $status) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer);
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
$res['_pid'] = isset($res['account']) && isset($res['account']['id']) ? $res['account']['id'] : null;
|
$res['_pid'] = isset($res['account']) && isset($res['account']['id']) ? $res['account']['id'] : null;
|
||||||
if (isset($res['_pid'])) {
|
if (isset($res['_pid'])) {
|
||||||
|
@ -112,7 +112,7 @@ class StatusService
|
||||||
{
|
{
|
||||||
$status = self::get($id, false);
|
$status = self::get($id, false);
|
||||||
|
|
||||||
if (! $status) {
|
if (! $status || ! $pid) {
|
||||||
return [
|
return [
|
||||||
'liked' => false,
|
'liked' => false,
|
||||||
'shared' => false,
|
'shared' => false,
|
||||||
|
@ -146,9 +146,9 @@ class StatusService
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
$resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer);
|
||||||
|
|
||||||
return $fractal->createData($resource)->toArray();
|
return $fractal->createData($resource)->toArray();
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,7 @@ class Helpers
|
||||||
return in_array($url, $audience['to']) || in_array($url, $audience['cc']);
|
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)) {
|
if (is_array($url) && ! empty($url)) {
|
||||||
$url = $url[0];
|
$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();
|
$bannedInstances = InstanceService::getBannedDomains();
|
||||||
if (in_array($host, $bannedInstances)) {
|
if (in_array($host, $bannedInstances)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -739,7 +739,7 @@ class Helpers
|
||||||
$width = isset($media['width']) ? $media['width'] : false;
|
$width = isset($media['width']) ? $media['width'] : false;
|
||||||
$height = isset($media['height']) ? $media['height'] : false;
|
$height = isset($media['height']) ? $media['height'] : false;
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->blurhash = $blurhash;
|
$media->blurhash = $blurhash;
|
||||||
$media->remote_media = true;
|
$media->remote_media = true;
|
||||||
$media->status_id = $status->id;
|
$media->status_id = $status->id;
|
||||||
|
@ -801,12 +801,19 @@ class Helpers
|
||||||
return self::profileUpdateOrCreate($url);
|
return self::profileUpdateOrCreate($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function profileUpdateOrCreate($url)
|
public static function profileUpdateOrCreate($url, $movedToCheck = false)
|
||||||
{
|
{
|
||||||
|
$movedToPid = null;
|
||||||
$res = self::fetchProfileFromUrl($url);
|
$res = self::fetchProfileFromUrl($url);
|
||||||
if (! $res || isset($res['id']) == false) {
|
if (! $res || isset($res['id']) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (! self::validateUrl($res['inbox'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (! self::validateUrl($res['id'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||||
$domain = parse_url($res['id'], PHP_URL_HOST);
|
$domain = parse_url($res['id'], PHP_URL_HOST);
|
||||||
if (strtolower($urlDomain) !== strtolower($domain)) {
|
if (strtolower($urlDomain) !== strtolower($domain)) {
|
||||||
|
@ -829,13 +836,6 @@ class Helpers
|
||||||
$remoteUsername = $username;
|
$remoteUsername = $username;
|
||||||
$webfinger = "@{$username}@{$domain}";
|
$webfinger = "@{$username}@{$domain}";
|
||||||
|
|
||||||
if (! self::validateUrl($res['inbox'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (! self::validateUrl($res['id'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$instance = Instance::updateOrCreate([
|
$instance = Instance::updateOrCreate([
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
]);
|
]);
|
||||||
|
@ -843,6 +843,13 @@ class Helpers
|
||||||
\App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
|
\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(
|
$profile = Profile::updateOrCreate(
|
||||||
[
|
[
|
||||||
'domain' => strtolower($domain),
|
'domain' => strtolower($domain),
|
||||||
|
@ -859,6 +866,7 @@ class Helpers
|
||||||
'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null,
|
'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null,
|
||||||
'public_key' => $res['publicKey']['publicKeyPem'],
|
'public_key' => $res['publicKey']['publicKeyPem'],
|
||||||
'indexable' => isset($res['indexable']) && is_bool($res['indexable']) ? $res['indexable'] : false,
|
'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);
|
$stringToSign = self::_headersToSigningString($headers);
|
||||||
$signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
|
$signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
|
||||||
$key = openssl_pkey_get_private($user->private_key);
|
$key = openssl_pkey_get_private($user->private_key);
|
||||||
|
if (empty($key)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
|
openssl_sign($stringToSign, $signature, $key, OPENSSL_ALGO_SHA256);
|
||||||
|
if (empty($signature)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
$signature = base64_encode($signature);
|
$signature = base64_encode($signature);
|
||||||
$signatureHeader = 'keyId="'.$user->keyId().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
|
$signatureHeader = 'keyId="'.$user->keyId().'",headers="'.$signedHeaders.'",algorithm="rsa-sha256",signature="'.$signature.'"';
|
||||||
unset($headers['(request-target)']);
|
unset($headers['(request-target)']);
|
||||||
|
@ -34,6 +40,34 @@ class HttpSignature
|
||||||
return self::_headersToCurlArray($headers);
|
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')
|
public static function instanceActorSign($url, $body = false, $addlHeaders = [], $method = 'post')
|
||||||
{
|
{
|
||||||
$keyId = config('app.url').'/i/actor#main-key';
|
$keyId = config('app.url').'/i/actor#main-key';
|
||||||
|
|
|
@ -2,59 +2,60 @@
|
||||||
|
|
||||||
namespace App\Util\ActivityPub;
|
namespace App\Util\ActivityPub;
|
||||||
|
|
||||||
use Cache, DB, Log, Purify, Redis, Storage, Validator;
|
use App\DirectMessage;
|
||||||
use App\{
|
use App\Follower;
|
||||||
Activity,
|
use App\FollowRequest;
|
||||||
DirectMessage,
|
use App\Instance;
|
||||||
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\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
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\RemoteStatusDelete;
|
||||||
|
use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline;
|
||||||
use App\Jobs\StoryPipeline\StoryExpire;
|
use App\Jobs\StoryPipeline\StoryExpire;
|
||||||
use App\Jobs\StoryPipeline\StoryFetch;
|
use App\Jobs\StoryPipeline\StoryFetch;
|
||||||
use App\Jobs\StatusPipeline\StatusRemoteUpdatePipeline;
|
use App\Like;
|
||||||
use App\Jobs\ProfilePipeline\HandleUpdateActivity;
|
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\Accept as AcceptValidator;
|
||||||
use App\Util\ActivityPub\Validator\Add as AddValidator;
|
|
||||||
use App\Util\ActivityPub\Validator\Announce as AnnounceValidator;
|
use App\Util\ActivityPub\Validator\Announce as AnnounceValidator;
|
||||||
use App\Util\ActivityPub\Validator\Follow as FollowValidator;
|
use App\Util\ActivityPub\Validator\Follow as FollowValidator;
|
||||||
use App\Util\ActivityPub\Validator\Like as LikeValidator;
|
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\Util\ActivityPub\Validator\UpdatePersonValidator;
|
||||||
|
use Cache;
|
||||||
use App\Services\AccountService;
|
use Illuminate\Support\Facades\Bus;
|
||||||
use App\Services\PollService;
|
use Illuminate\Support\Facades\Log;
|
||||||
use App\Services\FollowerService;
|
use Illuminate\Support\Str;
|
||||||
use App\Services\ReblogService;
|
use Purify;
|
||||||
use App\Services\StatusService;
|
use Storage;
|
||||||
use App\Services\UserFilterService;
|
use Throwable;
|
||||||
use App\Services\NetworkTimelineService;
|
|
||||||
use App\Models\Conversation;
|
|
||||||
use App\Models\RemoteReport;
|
|
||||||
use App\Jobs\HomeFeedPipeline\FeedRemoveRemotePipeline;
|
|
||||||
|
|
||||||
class Inbox
|
class Inbox
|
||||||
{
|
{
|
||||||
protected $headers;
|
protected $headers;
|
||||||
|
|
||||||
protected $profile;
|
protected $profile;
|
||||||
|
|
||||||
protected $payload;
|
protected $payload;
|
||||||
|
|
||||||
protected $logger;
|
protected $logger;
|
||||||
|
|
||||||
public function __construct($headers, $profile, $payload)
|
public function __construct($headers, $profile, $payload)
|
||||||
|
@ -67,7 +68,7 @@ class Inbox
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->handleVerb();
|
$this->handleVerb();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleVerb()
|
public function handleVerb()
|
||||||
|
@ -84,17 +85,23 @@ class Inbox
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Follow':
|
case 'Follow':
|
||||||
if(FollowValidator::validate($this->payload) == false) { return; }
|
if (FollowValidator::validate($this->payload) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->handleFollowActivity();
|
$this->handleFollowActivity();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Announce':
|
case 'Announce':
|
||||||
if(AnnounceValidator::validate($this->payload) == false) { return; }
|
if (AnnounceValidator::validate($this->payload) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->handleAnnounceActivity();
|
$this->handleAnnounceActivity();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Accept':
|
case 'Accept':
|
||||||
if(AcceptValidator::validate($this->payload) == false) { return; }
|
if (AcceptValidator::validate($this->payload) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->handleAcceptActivity();
|
$this->handleAcceptActivity();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -103,7 +110,9 @@ class Inbox
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Like':
|
case 'Like':
|
||||||
if(LikeValidator::validate($this->payload) == false) { return; }
|
if (LikeValidator::validate($this->payload) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->handleLikeActivity();
|
$this->handleLikeActivity();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -135,6 +144,15 @@ class Inbox
|
||||||
$this->handleUpdateActivity();
|
$this->handleUpdateActivity();
|
||||||
break;
|
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:
|
default:
|
||||||
// TODO: decide how to handle invalid verbs.
|
// TODO: decide how to handle invalid verbs.
|
||||||
break;
|
break;
|
||||||
|
@ -191,7 +209,6 @@ class Inbox
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleCreateActivity()
|
public function handleCreateActivity()
|
||||||
|
@ -226,6 +243,7 @@ class Inbox
|
||||||
|
|
||||||
if ($activity['type'] == 'Question') {
|
if ($activity['type'] == 'Question') {
|
||||||
$this->handlePollCreate();
|
$this->handlePollCreate();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,6 +254,7 @@ class Inbox
|
||||||
parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app')
|
parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app')
|
||||||
) {
|
) {
|
||||||
$this->handleDirectMessage();
|
$this->handleDirectMessage();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +267,7 @@ class Inbox
|
||||||
}
|
}
|
||||||
$this->handleNoteCreate();
|
$this->handleNoteCreate();
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleNoteReply()
|
public function handleNoteReply()
|
||||||
|
@ -263,7 +282,7 @@ class Inbox
|
||||||
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
||||||
|
|
||||||
Helpers::statusFirstOrFetch($url, true);
|
Helpers::statusFirstOrFetch($url, true);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handlePollCreate()
|
public function handlePollCreate()
|
||||||
|
@ -275,7 +294,7 @@ class Inbox
|
||||||
}
|
}
|
||||||
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
$url = isset($activity['url']) ? $activity['url'] : $activity['id'];
|
||||||
Helpers::statusFirstOrFetch($url);
|
Helpers::statusFirstOrFetch($url);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleNoteCreate()
|
public function handleNoteCreate()
|
||||||
|
@ -293,6 +312,7 @@ class Inbox
|
||||||
Helpers::validateLocalUrl($activity['inReplyTo'])
|
Helpers::validateLocalUrl($activity['inReplyTo'])
|
||||||
) {
|
) {
|
||||||
$this->handlePollVote();
|
$this->handlePollVote();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +341,7 @@ class Inbox
|
||||||
$actor,
|
$actor,
|
||||||
$activity
|
$activity
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handlePollVote()
|
public function handlePollVote()
|
||||||
|
@ -376,7 +396,6 @@ class Inbox
|
||||||
|
|
||||||
PollService::del($status->id);
|
PollService::del($status->id);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleDirectMessage()
|
public function handleDirectMessage()
|
||||||
|
@ -436,13 +455,13 @@ class Inbox
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $profile->id,
|
'to_id' => $profile->id,
|
||||||
'from_id' => $actor->id
|
'from_id' => $actor->id,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => $hidden
|
'is_hidden' => $hidden,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -459,7 +478,7 @@ class Inbox
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->remote_media = true;
|
$media->remote_media = true;
|
||||||
$media->status_id = $status->id;
|
$media->status_id = $status->id;
|
||||||
$media->profile_id = $status->profile_id;
|
$media->profile_id = $status->profile_id;
|
||||||
|
@ -492,7 +511,7 @@ class Inbox
|
||||||
$dm->meta = [
|
$dm->meta = [
|
||||||
'domain' => parse_url($msgText, PHP_URL_HOST),
|
'domain' => parse_url($msgText, PHP_URL_HOST),
|
||||||
'local' => 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();
|
$dm->save();
|
||||||
}
|
}
|
||||||
|
@ -505,7 +524,7 @@ class Inbox
|
||||||
->exists();
|
->exists();
|
||||||
|
|
||||||
if ($profile->domain == null && $hidden == false && ! $nf) {
|
if ($profile->domain == null && $hidden == false && ! $nf) {
|
||||||
$notification = new Notification();
|
$notification = new Notification;
|
||||||
$notification->profile_id = $profile->id;
|
$notification->profile_id = $profile->id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'dm';
|
$notification->action = 'dm';
|
||||||
|
@ -514,7 +533,6 @@ class Inbox
|
||||||
$notification->save();
|
$notification->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleFollowActivity()
|
public function handleFollowActivity()
|
||||||
|
@ -554,7 +572,7 @@ class Inbox
|
||||||
'follower_id' => $actor->id,
|
'follower_id' => $actor->id,
|
||||||
'following_id' => $target->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 {
|
} else {
|
||||||
$follower = new Follower;
|
$follower = new Follower;
|
||||||
|
@ -576,8 +594,8 @@ class Inbox
|
||||||
'id' => $this->payload['id'],
|
'id' => $this->payload['id'],
|
||||||
'actor' => $actor->permalink(),
|
'actor' => $actor->permalink(),
|
||||||
'type' => 'Follow',
|
'type' => 'Follow',
|
||||||
'object' => $target->permalink()
|
'object' => $target->permalink(),
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
Helpers::sendSignedObject($target, $actor->inbox_url, $accept);
|
Helpers::sendSignedObject($target, $actor->inbox_url, $accept);
|
||||||
Cache::forget('profile:follower_count:'.$target->id);
|
Cache::forget('profile:follower_count:'.$target->id);
|
||||||
|
@ -586,7 +604,6 @@ class Inbox
|
||||||
Cache::forget('profile:following_count:'.$actor->id);
|
Cache::forget('profile:following_count:'.$actor->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleAnnounceActivity()
|
public function handleAnnounceActivity()
|
||||||
|
@ -616,7 +633,7 @@ class Inbox
|
||||||
$status = Status::firstOrCreate([
|
$status = Status::firstOrCreate([
|
||||||
'profile_id' => $actor->id,
|
'profile_id' => $actor->id,
|
||||||
'reblog_of_id' => $parent->id,
|
'reblog_of_id' => $parent->id,
|
||||||
'type' => 'share'
|
'type' => 'share',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Notification::firstOrCreate(
|
Notification::firstOrCreate(
|
||||||
|
@ -634,7 +651,6 @@ class Inbox
|
||||||
|
|
||||||
ReblogService::addPostReblog($parent->profile_id, $status->id);
|
ReblogService::addPostReblog($parent->profile_id, $status->id);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleAcceptActivity()
|
public function handleAcceptActivity()
|
||||||
|
@ -682,7 +698,6 @@ class Inbox
|
||||||
|
|
||||||
$request->delete();
|
$request->delete();
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleDeleteActivity()
|
public function handleDeleteActivity()
|
||||||
|
@ -701,6 +716,7 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
|
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (! isset(
|
if (! isset(
|
||||||
|
@ -727,6 +743,7 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
|
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -752,6 +769,7 @@ class Inbox
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RemoteStatusDelete::dispatch($status)->onQueue('high');
|
RemoteStatusDelete::dispatch($status)->onQueue('high');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -761,6 +779,7 @@ class Inbox
|
||||||
if ($story) {
|
if ($story) {
|
||||||
StoryExpire::dispatch($story)->onQueue('story');
|
StoryExpire::dispatch($story)->onQueue('story');
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -769,7 +788,7 @@ class Inbox
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleLikeActivity()
|
public function handleLikeActivity()
|
||||||
|
@ -801,7 +820,7 @@ class Inbox
|
||||||
|
|
||||||
$like = Like::firstOrCreate([
|
$like = Like::firstOrCreate([
|
||||||
'profile_id' => $profile->id,
|
'profile_id' => $profile->id,
|
||||||
'status_id' => $status->id
|
'status_id' => $status->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($like->wasRecentlyCreated == true) {
|
if ($like->wasRecentlyCreated == true) {
|
||||||
|
@ -810,12 +829,9 @@ class Inbox
|
||||||
LikePipeline::dispatch($like);
|
LikePipeline::dispatch($like);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleRejectActivity()
|
public function handleRejectActivity() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handleUndoActivity()
|
public function handleUndoActivity()
|
||||||
{
|
{
|
||||||
|
@ -917,7 +933,7 @@ class Inbox
|
||||||
->forceDelete();
|
->forceDelete();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleViewActivity()
|
public function handleViewActivity()
|
||||||
|
@ -969,7 +985,7 @@ class Inbox
|
||||||
|
|
||||||
$view = StoryView::firstOrCreate([
|
$view = StoryView::firstOrCreate([
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'profile_id' => $profile->id
|
'profile_id' => $profile->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($view->wasRecentlyCreated == true) {
|
if ($view->wasRecentlyCreated == true) {
|
||||||
|
@ -977,7 +993,6 @@ class Inbox
|
||||||
$story->save();
|
$story->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleStoryReactionActivity()
|
public function handleStoryReactionActivity()
|
||||||
|
@ -1060,7 +1075,7 @@ class Inbox
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
$status->entities = json_encode([
|
$status->entities = json_encode([
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'reaction' => $text
|
'reaction' => $text,
|
||||||
]);
|
]);
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
|
@ -1074,20 +1089,20 @@ class Inbox
|
||||||
'story_actor_username' => $actorProfile->username,
|
'story_actor_username' => $actorProfile->username,
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'story_media_url' => url(Storage::url($story->path)),
|
'story_media_url' => url(Storage::url($story->path)),
|
||||||
'reaction' => $text
|
'reaction' => $text,
|
||||||
]);
|
]);
|
||||||
$dm->save();
|
$dm->save();
|
||||||
|
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $story->profile_id,
|
'to_id' => $story->profile_id,
|
||||||
'from_id' => $actorProfile->id
|
'from_id' => $actorProfile->id,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'story:react',
|
'type' => 'story:react',
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => false
|
'is_hidden' => false,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1099,7 +1114,6 @@ class Inbox
|
||||||
$n->action = 'story:react';
|
$n->action = 'story:react';
|
||||||
$n->save();
|
$n->save();
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleStoryReplyActivity()
|
public function handleStoryReplyActivity()
|
||||||
|
@ -1155,7 +1169,6 @@ class Inbox
|
||||||
|
|
||||||
$actorProfile = Helpers::profileFetch($actor);
|
$actorProfile = Helpers::profileFetch($actor);
|
||||||
|
|
||||||
|
|
||||||
if (AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) {
|
if (AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1183,7 +1196,7 @@ class Inbox
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
$status->entities = json_encode([
|
$status->entities = json_encode([
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'caption' => $text
|
'caption' => $text,
|
||||||
]);
|
]);
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
|
@ -1197,20 +1210,20 @@ class Inbox
|
||||||
'story_actor_username' => $actorProfile->username,
|
'story_actor_username' => $actorProfile->username,
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'story_media_url' => url(Storage::url($story->path)),
|
'story_media_url' => url(Storage::url($story->path)),
|
||||||
'caption' => $text
|
'caption' => $text,
|
||||||
]);
|
]);
|
||||||
$dm->save();
|
$dm->save();
|
||||||
|
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $story->profile_id,
|
'to_id' => $story->profile_id,
|
||||||
'from_id' => $actorProfile->id
|
'from_id' => $actorProfile->id,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'story:comment',
|
'type' => 'story:comment',
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => false
|
'is_hidden' => false,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1222,7 +1235,6 @@ class Inbox
|
||||||
$n->action = 'story:comment';
|
$n->action = 'story:comment';
|
||||||
$n->save();
|
$n->save();
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleFlagActivity()
|
public function handleFlagActivity()
|
||||||
|
@ -1307,7 +1319,7 @@ class Inbox
|
||||||
$instanceHost = parse_url($id, PHP_URL_HOST);
|
$instanceHost = parse_url($id, PHP_URL_HOST);
|
||||||
|
|
||||||
$instance = Instance::updateOrCreate([
|
$instance = Instance::updateOrCreate([
|
||||||
'domain' => $instanceHost
|
'domain' => $instanceHost,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$report = new RemoteReport;
|
$report = new RemoteReport;
|
||||||
|
@ -1318,11 +1330,10 @@ class Inbox
|
||||||
$report->instance_id = $instance->id;
|
$report->instance_id = $instance->id;
|
||||||
$report->report_meta = [
|
$report->report_meta = [
|
||||||
'actor' => $actor,
|
'actor' => $actor,
|
||||||
'object' => $object
|
'object' => $object,
|
||||||
];
|
];
|
||||||
$report->save();
|
$report->save();
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleUpdateActivity()
|
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:intbg' => 30,
|
||||||
'redis:adelete' => 30,
|
'redis:adelete' => 30,
|
||||||
'redis:groups' => 30,
|
'redis:groups' => 30,
|
||||||
|
'redis:move' => 30,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -175,7 +176,7 @@ return [
|
||||||
'production' => [
|
'production' => [
|
||||||
'supervisor-1' => [
|
'supervisor-1' => [
|
||||||
'connection' => 'redis',
|
'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'),
|
'balance' => env('HORIZON_BALANCE_STRATEGY', 'auto'),
|
||||||
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
|
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
|
||||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 20),
|
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 20),
|
||||||
|
@ -189,7 +190,7 @@ return [
|
||||||
'local' => [
|
'local' => [
|
||||||
'supervisor-1' => [
|
'supervisor-1' => [
|
||||||
'connection' => 'redis',
|
'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',
|
'balance' => 'auto',
|
||||||
'minProcesses' => 1,
|
'minProcesses' => 1,
|
||||||
'maxProcesses' => 20,
|
'maxProcesses' => 20,
|
||||||
|
|
Loading…
Reference in a new issue