mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-10 00:34:50 +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,15 +25,16 @@ 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($user->has_roles && !UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark or unbookmark a post from an account that has migrated');
|
||||||
|
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
||||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
abort_if($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);
|
||||||
abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404);
|
abort_if(! in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']), 404);
|
||||||
|
|
||||||
if($status->scope == 'private') {
|
if ($status->scope == 'private') {
|
||||||
if($user->profile_id !== $status->profile_id && !FollowerService::follows($user->profile_id, $status->profile_id)) {
|
if ($user->profile_id !== $status->profile_id && ! FollowerService::follows($user->profile_id, $status->profile_id)) {
|
||||||
if($exists = Bookmark::whereStatusId($status->id)->whereProfileId($user->profile_id)->first()) {
|
if ($exists = Bookmark::whereStatusId($status->id)->whereProfileId($user->profile_id)->first()) {
|
||||||
BookmarkService::del($user->profile_id, $status->id);
|
BookmarkService::del($user->profile_id, $status->id);
|
||||||
$exists->delete();
|
$exists->delete();
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ class BookmarkController extends Controller
|
||||||
['status_id' => $status->id], ['profile_id' => $user->profile_id]
|
['status_id' => $status->id], ['profile_id' => $user->profile_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!$bookmark->wasRecentlyCreated) {
|
if (! $bookmark->wasRecentlyCreated) {
|
||||||
BookmarkService::del($user->profile_id, $status->id);
|
BookmarkService::del($user->profile_id, $status->id);
|
||||||
$bookmark->delete();
|
$bookmark->delete();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -202,7 +202,7 @@ class AccountService
|
||||||
}
|
}
|
||||||
|
|
||||||
$count = Status::whereProfileId($id)
|
$count = Status::whereProfileId($id)
|
||||||
->whereNull(['in_reply_to_id','reblog_of_id'])
|
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||||
->whereIn('scope', ['public', 'unlisted', 'private'])
|
->whereIn('scope', ['public', 'unlisted', 'private'])
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -18,8 +17,8 @@ class NodeinfoService
|
||||||
'User-Agent' => "(Pixelfed/{$version}; +{$appUrl})",
|
'User-Agent' => "(Pixelfed/{$version}; +{$appUrl})",
|
||||||
];
|
];
|
||||||
|
|
||||||
$url = 'https://' . $domain;
|
$url = 'https://'.$domain;
|
||||||
$wk = $url . '/.well-known/nodeinfo';
|
$wk = $url.'/.well-known/nodeinfo';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$res = Http::withOptions([
|
$res = Http::withOptions([
|
||||||
|
@ -36,18 +35,18 @@ class NodeinfoService
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$res) {
|
if (! $res) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$json = $res->json();
|
$json = $res->json();
|
||||||
|
|
||||||
if( !isset($json['links'])) {
|
if (! isset($json['links'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_array($json['links'])) {
|
if (is_array($json['links'])) {
|
||||||
if(isset($json['links']['href'])) {
|
if (isset($json['links']['href'])) {
|
||||||
$href = $json['links']['href'];
|
$href = $json['links']['href'];
|
||||||
} else {
|
} else {
|
||||||
$href = $json['links'][0]['href'];
|
$href = $json['links'][0]['href'];
|
||||||
|
@ -59,8 +58,8 @@ class NodeinfoService
|
||||||
$domain = parse_url($url, PHP_URL_HOST);
|
$domain = parse_url($url, PHP_URL_HOST);
|
||||||
$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';
|
||||||
|
|
File diff suppressed because it is too large
Load diff
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