diff --git a/app/Console/Commands/SendUpdateActor.php b/app/Console/Commands/SendUpdateActor.php new file mode 100644 index 000000000..ea0c0d5eb --- /dev/null +++ b/app/Console/Commands/SendUpdateActor.php @@ -0,0 +1,149 @@ +count(); + $totalInstanceCount = Instance::count(); + $this->info('Found ' . $totalUserCount . ' local accounts and ' . $totalInstanceCount . ' remote instances'); + + $task = $this->choice( + 'What do you want to do?', + [ + 'View top instances', + 'Send updates to an instance' + ], + 0 + ); + + if($task === 'View top instances') { + $this->table( + ['domain', 'user_count', 'last_synced'], + Instance::orderByDesc('user_count')->take(20)->get(['domain', 'user_count', 'actors_last_synced_at'])->toArray() + ); + return Command::SUCCESS; + } else { + $domain = $this->anticipate('Enter the instance domain', function ($input) { + return Instance::where('domain', 'like', '%' . $input . '%')->pluck('domain')->toArray(); + }); + if(!$this->confirm('Are you sure you want to send actor updates to ' . $domain . '?')) { + return; + } + if($cur = Instance::whereDomain($domain)->whereNotNull('actors_last_synced_at')->first()) { + if(!$this->option('force')) { + $this->error('ERROR: Cannot re-sync this instance, it was already synced on ' . $cur->actors_last_synced_at); + return; + } + } + $this->line(' '); + $this->error('Keep this window open during this process or it will not complete!'); + $sharedInbox = Profile::whereDomain($domain)->whereNotNull('sharedInbox')->first(); + if(!$sharedInbox) { + $this->error('ERROR: Cannot find the sharedInbox of ' . $domain); + return; + } + $url = $sharedInbox->sharedInbox; + $this->line(' '); + $this->info('Found sharedInbox: ' . $url); + $bar = $this->output->createProgressBar($totalUserCount); + $bar->start(); + User::chunk(50, function($users) use($bar, $url) { + foreach($users as $user) { + $profile = Profile::find($user->profile_id); + if(!$profile) { + continue; + } + $body = $this->updateObject($profile); + Helpers::sendSignedObject($profile, $url, $body); + usleep(500); + $bar->advance(); + } + }); + $bar->finish(); + $this->line(' '); + $instance = Instance::whereDomain($domain)->firstOrFail(); + $instance->actors_last_synced_at = now(); + $instance->save(); + $this->info('Finished!'); + return Command::SUCCESS; + } + + return Command::SUCCESS; + } + + protected function updateObject($profile) + { + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', + ], + ], + 'id' => $profile->permalink('#updates/' . time()), + 'actor' => $profile->permalink(), + 'type' => 'Update', + 'object' => $this->actorObject($profile) + ]; + } + + protected function actorObject($profile) + { + $permalink = $profile->permalink(); + return [ + 'id' => $permalink, + 'type' => 'Person', + 'following' => $permalink . '/following', + 'followers' => $permalink . '/followers', + 'inbox' => $permalink . '/inbox', + 'outbox' => $permalink . '/outbox', + 'preferredUsername' => $profile->username, + 'name' => $profile->name, + 'summary' => $profile->bio, + 'url' => $profile->url(), + 'manuallyApprovesFollowers' => (bool) $profile->is_private, + 'publicKey' => [ + 'id' => $permalink . '#main-key', + 'owner' => $permalink, + 'publicKeyPem' => $profile->public_key, + ], + 'icon' => [ + 'type' => 'Image', + 'mediaType' => 'image/jpeg', + 'url' => $profile->avatarUrl(), + ], + 'endpoints' => [ + 'sharedInbox' => config('app.url') . '/f/inbox' + ] + ]; + } +}