mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-23 13:33:18 +00:00
commit
255c41fb83
47 changed files with 1103 additions and 436 deletions
|
@ -26,6 +26,7 @@
|
||||||
- Add ffmpeg config, disable logging by default ([108e3803](https://github.com/pixelfed/pixelfed/commit/108e3803))
|
- Add ffmpeg config, disable logging by default ([108e3803](https://github.com/pixelfed/pixelfed/commit/108e3803))
|
||||||
- Refactor AP profileFetch logic to fix race conditions and improve updating fields and avatars ([505261da](https://github.com/pixelfed/pixelfed/commit/505261da))
|
- Refactor AP profileFetch logic to fix race conditions and improve updating fields and avatars ([505261da](https://github.com/pixelfed/pixelfed/commit/505261da))
|
||||||
- Update network timeline api, limit falloff to 2 days ([13a66303](https://github.com/pixelfed/pixelfed/commit/13a66303))
|
- Update network timeline api, limit falloff to 2 days ([13a66303](https://github.com/pixelfed/pixelfed/commit/13a66303))
|
||||||
|
- Update Inbox, store follow request activity ([c82f2085](https://github.com/pixelfed/pixelfed/commit/c82f2085))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.11.3 (2022-05-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.2...v0.11.3)
|
## [v0.11.3 (2022-05-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.2...v0.11.3)
|
||||||
|
|
|
@ -29,9 +29,6 @@ class GenerateInstanceActor extends Command
|
||||||
}
|
}
|
||||||
|
|
||||||
if(InstanceActor::exists()) {
|
if(InstanceActor::exists()) {
|
||||||
$this->line(' ');
|
|
||||||
$this->error('Instance actor already exists!');
|
|
||||||
$this->line(' ');
|
|
||||||
$actor = InstanceActor::whereNotNull('public_key')
|
$actor = InstanceActor::whereNotNull('public_key')
|
||||||
->whereNotNull('private_key')
|
->whereNotNull('private_key')
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
@ -42,7 +39,8 @@ class GenerateInstanceActor extends Command
|
||||||
Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() use($actor) {
|
Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() use($actor) {
|
||||||
return $actor->private_key;
|
return $actor->private_key;
|
||||||
});
|
});
|
||||||
exit;
|
$this->info('Instance actor succesfully generated. You do not need to run this command again.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$pkiConfig = [
|
$pkiConfig = [
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use \PDO;
|
||||||
|
|
||||||
class Installer extends Command
|
class Installer extends Command
|
||||||
{
|
{
|
||||||
|
@ -12,7 +13,7 @@ class Installer extends Command
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'install';
|
protected $signature = 'install {--dangerously-overwrite-env : Re-run installation and overwrite current .env }';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
|
@ -21,6 +22,8 @@ class Installer extends Command
|
||||||
*/
|
*/
|
||||||
protected $description = 'CLI Installer';
|
protected $description = 'CLI Installer';
|
||||||
|
|
||||||
|
public $installType = 'Simple';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new command instance.
|
* Create a new command instance.
|
||||||
*
|
*
|
||||||
|
@ -54,23 +57,48 @@ class Installer extends Command
|
||||||
$this->info(' ');
|
$this->info(' ');
|
||||||
$this->info('Pixelfed version: ' . config('pixelfed.version'));
|
$this->info('Pixelfed version: ' . config('pixelfed.version'));
|
||||||
$this->line(' ');
|
$this->line(' ');
|
||||||
$this->info('Scanning system...');
|
$this->envCheck();
|
||||||
$this->preflightCheck();
|
|
||||||
}
|
}
|
||||||
protected function preflightCheck()
|
|
||||||
|
protected function envCheck()
|
||||||
{
|
{
|
||||||
$this->line(' ');
|
if( file_exists(base_path('.env')) &&
|
||||||
$this->info('Checking for installed dependencies...');
|
filesize(base_path('.env')) !== 0 &&
|
||||||
$redis = Redis::connection();
|
!$this->option('dangerously-overwrite-env')
|
||||||
if($redis->ping()) {
|
) {
|
||||||
$this->info('- Found redis!');
|
$this->line('');
|
||||||
} else {
|
$this->error('Installation aborted, found existing .env file');
|
||||||
$this->error('- Redis not found, aborting installation');
|
$this->line('Run the following command to re-run the installer:');
|
||||||
|
$this->line('');
|
||||||
|
$this->info('php artisan install --dangerously-overwrite-env');
|
||||||
|
$this->line('');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
$this->installType();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function installType()
|
||||||
|
{
|
||||||
|
$type = $this->choice('Select installation type', ['Simple', 'Advanced'], 0);
|
||||||
|
$this->installType = $type;
|
||||||
|
$this->preflightCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function preflightCheck()
|
||||||
|
{
|
||||||
|
if($this->installType === 'Advanced') {
|
||||||
|
$this->info('Scanning system...');
|
||||||
|
$this->line(' ');
|
||||||
|
$this->info('Checking for installed dependencies...');
|
||||||
|
$redis = Redis::connection();
|
||||||
|
if($redis->ping()) {
|
||||||
|
$this->info('- Found redis!');
|
||||||
|
} else {
|
||||||
|
$this->error('- Redis not found, aborting installation');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->checkPhpDependencies();
|
$this->checkPhpDependencies();
|
||||||
$this->checkPermissions();
|
|
||||||
$this->envCheck();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function checkPhpDependencies()
|
protected function checkPhpDependencies()
|
||||||
|
@ -81,31 +109,39 @@ class Installer extends Command
|
||||||
'curl',
|
'curl',
|
||||||
'json',
|
'json',
|
||||||
'mbstring',
|
'mbstring',
|
||||||
'openssl'
|
'openssl',
|
||||||
];
|
];
|
||||||
$ffmpeg = exec('which ffmpeg');
|
if($this->installType === 'Advanced') {
|
||||||
if(empty($ffmpeg)) {
|
$ffmpeg = exec('which ffmpeg');
|
||||||
$this->error('FFmpeg not found, please install it.');
|
if(empty($ffmpeg)) {
|
||||||
$this->error('Cancelling installation.');
|
$this->error('FFmpeg not found, please install it.');
|
||||||
exit;
|
$this->error('Cancelling installation.');
|
||||||
} else {
|
exit;
|
||||||
$this->info('- Found FFmpeg!');
|
} else {
|
||||||
}
|
$this->info('- Found FFmpeg!');
|
||||||
$this->line('');
|
}
|
||||||
$this->info('Checking for required php extensions...');
|
$this->line('');
|
||||||
|
$this->info('Checking for required php extensions...');
|
||||||
|
}
|
||||||
foreach($extensions as $ext) {
|
foreach($extensions as $ext) {
|
||||||
if(extension_loaded($ext) == false) {
|
if(extension_loaded($ext) == false) {
|
||||||
$this->error("- {$ext} extension not found, aborting installation");
|
$this->error("\"{$ext}\" PHP extension not found, aborting installation");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->info("- Required PHP extensions found!");
|
if($this->installType === 'Advanced') {
|
||||||
|
$this->info("- Required PHP extensions found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->checkPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function checkPermissions()
|
protected function checkPermissions()
|
||||||
{
|
{
|
||||||
$this->line('');
|
if($this->installType === 'Advanced') {
|
||||||
$this->info('Checking for proper filesystem permissions...');
|
$this->line('');
|
||||||
|
$this->info('Checking for proper filesystem permissions...');
|
||||||
|
}
|
||||||
|
|
||||||
$paths = [
|
$paths = [
|
||||||
base_path('bootstrap'),
|
base_path('bootstrap'),
|
||||||
|
@ -119,100 +155,152 @@ class Installer extends Command
|
||||||
$this->error(" $path");
|
$this->error(" $path");
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
$this->info("- Found valid permissions for {$path}");
|
if($this->installType === 'Advanced') {
|
||||||
|
$this->info("- Found valid permissions for {$path}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected function envCheck()
|
$this->createEnv();
|
||||||
{
|
|
||||||
if(!file_exists(base_path('.env')) || filesize(base_path('.env')) == 0) {
|
|
||||||
$this->line('');
|
|
||||||
$this->info('No .env configuration file found. We will create one now!');
|
|
||||||
$this->createEnv();
|
|
||||||
} else {
|
|
||||||
$confirm = $this->confirm('Found .env file, do you want to overwrite it?');
|
|
||||||
if(!$confirm) {
|
|
||||||
$this->info('Cancelling installation.');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$confirm = $this->confirm('Are you really sure you want to overwrite it?');
|
|
||||||
if(!$confirm) {
|
|
||||||
$this->info('Cancelling installation.');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$this->error('Warning ... if you did not backup your .env before its overwritten it will be permanently deleted.');
|
|
||||||
$confirm = $this->confirm('The application may be installed already, are you really sure you want to overwrite it?');
|
|
||||||
if(!$confirm) {
|
|
||||||
$this->info('Cancelling installation.');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->postInstall();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createEnv()
|
protected function createEnv()
|
||||||
{
|
{
|
||||||
$this->line('');
|
$this->line('');
|
||||||
// copy env
|
|
||||||
if(!file_exists(app()->environmentFilePath())) {
|
if(!file_exists(app()->environmentFilePath())) {
|
||||||
exec('cp .env.example .env');
|
exec('cp .env.example .env');
|
||||||
$this->call('key:generate');
|
$this->updateEnvFile('APP_ENV', 'setup');
|
||||||
|
$this->call('key:generate');
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = $this->ask('Site name [ex: Pixelfed]');
|
$name = $this->ask('Site name [ex: Pixelfed]');
|
||||||
$this->updateEnvFile('APP_NAME', $name ?? 'pixelfed');
|
$this->updateEnvFile('APP_NAME', $name ?? 'pixelfed');
|
||||||
|
|
||||||
$domain = $this->ask('Site Domain [ex: pixelfed.com]');
|
$domain = $this->ask('Site Domain [ex: pixelfed.com]');
|
||||||
|
if(empty($domain)) {
|
||||||
|
$this->error('You must set the site domain');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if(starts_with($domain, 'http')) {
|
||||||
|
$this->error('The site domain cannot start with https://, you must use the FQDN (eg: example.org)');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if(strpos($domain, '.') == false) {
|
||||||
|
$this->error('You must enter a valid site domain');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$this->updateEnvFile('APP_DOMAIN', $domain ?? 'example.org');
|
$this->updateEnvFile('APP_DOMAIN', $domain ?? 'example.org');
|
||||||
$this->updateEnvFile('ADMIN_DOMAIN', $domain ?? 'example.org');
|
$this->updateEnvFile('ADMIN_DOMAIN', $domain ?? 'example.org');
|
||||||
$this->updateEnvFile('SESSION_DOMAIN', $domain ?? 'example.org');
|
$this->updateEnvFile('SESSION_DOMAIN', $domain ?? 'example.org');
|
||||||
$this->updateEnvFile('APP_URL', 'https://' . $domain ?? 'https://example.org');
|
$this->updateEnvFile('APP_URL', 'https://' . $domain);
|
||||||
|
|
||||||
$database = $this->choice('Select database driver', ['mysql', 'pgsql'], 0);
|
$database = $this->choice('Select database driver', ['mysql', 'pgsql'], 0);
|
||||||
$this->updateEnvFile('DB_CONNECTION', $database ?? 'mysql');
|
$this->updateEnvFile('DB_CONNECTION', $database ?? 'mysql');
|
||||||
switch ($database) {
|
|
||||||
case 'mysql':
|
|
||||||
$database_host = $this->ask('Select database host', '127.0.0.1');
|
|
||||||
$this->updateEnvFile('DB_HOST', $database_host ?? 'mysql');
|
|
||||||
|
|
||||||
$database_port = $this->ask('Select database port', 3306);
|
$database_host = $this->ask('Select database host', '127.0.0.1');
|
||||||
$this->updateEnvFile('DB_PORT', $database_port ?? 3306);
|
$this->updateEnvFile('DB_HOST', $database_host ?? 'mysql');
|
||||||
|
|
||||||
$database_db = $this->ask('Select database', 'pixelfed');
|
$database_port_default = $database === 'mysql' ? 3306 : 5432;
|
||||||
$this->updateEnvFile('DB_DATABASE', $database_db ?? 'pixelfed');
|
$database_port = $this->ask('Select database port', $database_port_default);
|
||||||
|
$this->updateEnvFile('DB_PORT', $database_port ?? $database_port_default);
|
||||||
|
|
||||||
$database_username = $this->ask('Select database username', 'pixelfed');
|
$database_db = $this->ask('Select database', 'pixelfed');
|
||||||
$this->updateEnvFile('DB_USERNAME', $database_username ?? 'pixelfed');
|
$this->updateEnvFile('DB_DATABASE', $database_db ?? 'pixelfed');
|
||||||
|
|
||||||
$db_pass = str_random(64);
|
$database_username = $this->ask('Select database username', 'pixelfed');
|
||||||
$database_password = $this->secret('Select database password', $db_pass);
|
$this->updateEnvFile('DB_USERNAME', $database_username ?? 'pixelfed');
|
||||||
$this->updateEnvFile('DB_PASSWORD', $database_password);
|
|
||||||
break;
|
$db_pass = str_random(64);
|
||||||
|
$database_password = $this->secret('Select database password', $db_pass);
|
||||||
|
$this->updateEnvFile('DB_PASSWORD', $database_password);
|
||||||
|
|
||||||
|
$dsn = "{$database}:dbname={$database_db};host={$database_host};port={$database_port};";
|
||||||
|
try {
|
||||||
|
$dbh = new PDO($dsn, $database_username, $database_password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$this->error('Cannot connect to database, check your credentials and try again');
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$cache = $this->choice('Select cache driver', ["redis", "apc", "array", "database", "file", "memcached"], 0);
|
if($this->installType === 'Advanced') {
|
||||||
$this->updateEnvFile('CACHE_DRIVER', $cache ?? 'redis');
|
$cache = $this->choice('Select cache driver', ["redis", "apc", "array", "database", "file", "memcached"], 0);
|
||||||
|
$this->updateEnvFile('CACHE_DRIVER', $cache ?? 'redis');
|
||||||
|
|
||||||
$session = $this->choice('Select session driver', ["redis", "file", "cookie", "database", "apc", "memcached", "array"], 0);
|
$session = $this->choice('Select session driver', ["redis", "file", "cookie", "database", "apc", "memcached", "array"], 0);
|
||||||
$this->updateEnvFile('SESSION_DRIVER', $session ?? 'redis');
|
$this->updateEnvFile('SESSION_DRIVER', $session ?? 'redis');
|
||||||
|
|
||||||
$redis_host = $this->ask('Set redis host', 'localhost');
|
$redis_host = $this->ask('Set redis host', 'localhost');
|
||||||
$this->updateEnvFile('REDIS_HOST', $redis_host);
|
$this->updateEnvFile('REDIS_HOST', $redis_host);
|
||||||
|
|
||||||
$redis_password = $this->ask('Set redis password', 'null');
|
$redis_password = $this->ask('Set redis password', 'null');
|
||||||
$this->updateEnvFile('REDIS_PASSWORD', $redis_password);
|
$this->updateEnvFile('REDIS_PASSWORD', $redis_password);
|
||||||
|
|
||||||
$redis_port = $this->ask('Set redis port', 6379);
|
$redis_port = $this->ask('Set redis port', 6379);
|
||||||
$this->updateEnvFile('REDIS_PORT', $redis_port);
|
$this->updateEnvFile('REDIS_PORT', $redis_port);
|
||||||
|
}
|
||||||
|
|
||||||
$open_registration = $this->choice('Allow new registrations?', ['true', 'false'], 1);
|
$open_registration = $this->choice('Allow new registrations?', ['false', 'true'], 0);
|
||||||
$this->updateEnvFile('OPEN_REGISTRATION', $open_registration);
|
$this->updateEnvFile('OPEN_REGISTRATION', $open_registration);
|
||||||
|
|
||||||
$enforce_email_verification = $this->choice('Enforce email verification?', ['true', 'false'], 0);
|
$activitypub_federation = $this->choice('Enable ActivityPub federation?', ['false', 'true'], 1);
|
||||||
|
$this->updateEnvFile('ACTIVITY_PUB', $activitypub_federation);
|
||||||
|
$this->updateEnvFile('AP_INBOX', $activitypub_federation);
|
||||||
|
$this->updateEnvFile('AP_SHAREDINBOX', $activitypub_federation);
|
||||||
|
$this->updateEnvFile('AP_REMOTE_FOLLOW', $activitypub_federation);
|
||||||
|
|
||||||
|
$enforce_email_verification = $this->choice('Enforce email verification?', ['false', 'true'], 1);
|
||||||
$this->updateEnvFile('ENFORCE_EMAIL_VERIFICATION', $enforce_email_verification);
|
$this->updateEnvFile('ENFORCE_EMAIL_VERIFICATION', $enforce_email_verification);
|
||||||
|
|
||||||
|
$enable_mobile_apis = $this->choice('Enable mobile app/apis support?', ['false', 'true'], 1);
|
||||||
|
$this->updateEnvFile('OAUTH_ENABLED', $enable_mobile_apis);
|
||||||
|
$this->updateEnvFile('EXP_EMC', $enable_mobile_apis);
|
||||||
|
|
||||||
|
$optimize_media = $this->choice('Optimize media uploads? Requires jpegoptim and other dependencies!', ['false', 'true'], 0);
|
||||||
|
$this->updateEnvFile('PF_OPTIMIZE_IMAGES', $optimize_media);
|
||||||
|
|
||||||
|
if($this->installType === 'Advanced') {
|
||||||
|
|
||||||
|
if($optimize_media === 'true') {
|
||||||
|
$image_quality = $this->ask('Set image optimization quality between 1-100. Default is 80%, lower values use less disk space at the expense of image quality.', '80');
|
||||||
|
if($image_quality < 1) {
|
||||||
|
$this->error('Min image quality is 1. You should avoid such a low value, 60 at minimum is recommended.');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if($image_quality > 100) {
|
||||||
|
$this->error('Max image quality is 100');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$this->updateEnvFile('IMAGE_QUALITY', $image_quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
$max_photo_size = $this->ask('Max photo upload size in kilobytes. Default 15000 which is equal to 15MB', '15000');
|
||||||
|
if($max_photo_size * 1024 > $this->parseSize(ini_get('post_max_size'))) {
|
||||||
|
$this->error('Max photo size (' . (round($max_photo_size / 1000)) . 'M) cannot exceed php.ini `post_max_size` of ' . ini_get('post_max_size'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$this->updateEnvFile('MAX_PHOTO_SIZE', $max_photo_size);
|
||||||
|
|
||||||
|
$max_caption_length = $this->ask('Max caption limit. Default to 500, max 5000.', '500');
|
||||||
|
if($max_caption_length > 5000) {
|
||||||
|
$this->error('Max caption length is 5000 characters.');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$this->updateEnvFile('MAX_CAPTION_LENGTH', $max_caption_length);
|
||||||
|
|
||||||
|
$max_album_length = $this->ask('Max photos allowed per album. Choose a value between 1 and 10.', '4');
|
||||||
|
if($max_album_length < 1) {
|
||||||
|
$this->error('Min album length is 1 photos per album.');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if($max_album_length > 10) {
|
||||||
|
$this->error('Max album length is 10 photos per album.');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$this->updateEnvFile('MAX_ALBUM_LENGTH', $max_album_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->updateEnvFile('APP_ENV', 'production');
|
||||||
|
$this->postInstall();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function updateEnvFile($key, $value)
|
protected function updateEnvFile($key, $value)
|
||||||
|
@ -247,8 +335,35 @@ class Installer extends Command
|
||||||
|
|
||||||
protected function postInstall()
|
protected function postInstall()
|
||||||
{
|
{
|
||||||
$this->callSilent('config:cache');
|
$this->line('');
|
||||||
//$this->callSilent('route:cache');
|
$this->info('We recommend running database migrations now, or you can do it manually later.');
|
||||||
|
$confirm = $this->choice('Do you want to run the database migrations?', ['No', 'Yes'], 0);
|
||||||
|
if($confirm === 'Yes') {
|
||||||
|
$this->callSilently('config:clear');
|
||||||
|
sleep(3);
|
||||||
|
$this->call('migrate', ['--force' => true]);
|
||||||
|
$this->callSilently('instance:actor');
|
||||||
|
$this->callSilently('passport:install');
|
||||||
|
|
||||||
|
$confirm = $this->choice('Do you want to create an admin account?', ['No', 'Yes'], 0);
|
||||||
|
if($confirm === 'Yes') {
|
||||||
|
$this->call('user:create');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->callSilently('config:cache');
|
||||||
|
}
|
||||||
|
|
||||||
$this->info('Pixelfed has been successfully installed!');
|
$this->info('Pixelfed has been successfully installed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function parseSize($size) {
|
||||||
|
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
|
||||||
|
$size = preg_replace('/[^0-9\.]/', '', $size);
|
||||||
|
if ($unit) {
|
||||||
|
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return round($size);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,16 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class FollowRequest extends Model
|
class FollowRequest extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = ['follower_id', 'following_id'];
|
protected $fillable = ['follower_id', 'following_id', 'activity', 'handled_at'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'activity' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function actor()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class, 'follower_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
public function follower()
|
public function follower()
|
||||||
{
|
{
|
||||||
|
@ -18,13 +27,14 @@ class FollowRequest extends Model
|
||||||
return $this->belongsTo(Profile::class, 'following_id', 'id');
|
return $this->belongsTo(Profile::class, 'following_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function actor()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Profile::class, 'follower_id', 'id');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function target()
|
public function target()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Profile::class, 'following_id', 'id');
|
return $this->belongsTo(Profile::class, 'following_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function permalink($append = null, $namespace = '#accepts')
|
||||||
|
{
|
||||||
|
$path = $this->target->permalink("{$namespace}/follows/{$this->id}{$append}");
|
||||||
|
return url($path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ use App\Transformer\Api\Mastodon\v1\AccountTransformer;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\UserFilterService;
|
use App\Services\UserFilterService;
|
||||||
use App\Services\RelationshipService;
|
use App\Services\RelationshipService;
|
||||||
|
use App\Jobs\FollowPipeline\FollowAcceptPipeline;
|
||||||
|
use App\Jobs\FollowPipeline\FollowRejectPipeline;
|
||||||
|
|
||||||
class AccountController extends Controller
|
class AccountController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -363,12 +365,13 @@ class AccountController extends Controller
|
||||||
'accounts' => $followers->take(10)->map(function($a) {
|
'accounts' => $followers->take(10)->map(function($a) {
|
||||||
$actor = $a->actor;
|
$actor = $a->actor;
|
||||||
return [
|
return [
|
||||||
'id' => $actor->id,
|
'rid' => (string) $a->id,
|
||||||
|
'id' => (string) $actor->id,
|
||||||
'username' => $actor->username,
|
'username' => $actor->username,
|
||||||
'avatar' => $actor->avatarUrl(),
|
'avatar' => $actor->avatarUrl(),
|
||||||
'url' => $actor->url(),
|
'url' => $actor->url(),
|
||||||
'local' => $actor->domain == null,
|
'local' => $actor->domain == null,
|
||||||
'following' => $actor->followedBy(Auth::user()->profile)
|
'account' => AccountService::get($actor->id)
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
@ -390,17 +393,35 @@ class AccountController extends Controller
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'accept':
|
case 'accept':
|
||||||
$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();
|
||||||
FollowPipeline::dispatch($follow);
|
|
||||||
$followRequest->delete();
|
$profile = Profile::findOrFail($pid);
|
||||||
|
$profile->followers_count++;
|
||||||
|
$profile->save();
|
||||||
|
AccountService::del($profile->id);
|
||||||
|
|
||||||
|
$profile = Profile::findOrFail($follower->id);
|
||||||
|
$profile->following_count++;
|
||||||
|
$profile->save();
|
||||||
|
AccountService::del($profile->id);
|
||||||
|
|
||||||
|
if($follower->domain != null && $follower->private_key === null) {
|
||||||
|
FollowAcceptPipeline::dispatch($followRequest);
|
||||||
|
} else {
|
||||||
|
FollowPipeline::dispatch($follow);
|
||||||
|
$followRequest->delete();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'reject':
|
case 'reject':
|
||||||
$followRequest->is_rejected = true;
|
if($follower->domain != null && $follower->private_key === null) {
|
||||||
$followRequest->save();
|
FollowRejectPipeline::dispatch($followRequest);
|
||||||
|
} else {
|
||||||
|
$followRequest->delete();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ use App\Services\{
|
||||||
FollowerService,
|
FollowerService,
|
||||||
InstanceService,
|
InstanceService,
|
||||||
LikeService,
|
LikeService,
|
||||||
|
NetworkTimelineService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
MediaPathService,
|
MediaPathService,
|
||||||
PublicTimelineService,
|
PublicTimelineService,
|
||||||
|
@ -82,6 +83,8 @@ use App\Services\DiscoverService;
|
||||||
use App\Services\CustomEmojiService;
|
use App\Services\CustomEmojiService;
|
||||||
use App\Services\MarkerService;
|
use App\Services\MarkerService;
|
||||||
use App\Models\Conversation;
|
use App\Models\Conversation;
|
||||||
|
use App\Jobs\FollowPipeline\FollowAcceptPipeline;
|
||||||
|
use App\Jobs\FollowPipeline\FollowRejectPipeline;
|
||||||
|
|
||||||
class ApiV1Controller extends Controller
|
class ApiV1Controller extends Controller
|
||||||
{
|
{
|
||||||
|
@ -441,7 +444,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
if($pid != $account['id']) {
|
if($pid != $account['id']) {
|
||||||
if($account['locked']) {
|
if($account['locked']) {
|
||||||
if(FollowerService::follows($pid, $account['id'])) {
|
if(!FollowerService::follows($pid, $account['id'])) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -488,7 +491,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
if($pid != $account['id']) {
|
if($pid != $account['id']) {
|
||||||
if($account['locked']) {
|
if($account['locked']) {
|
||||||
if(FollowerService::follows($pid, $account['id'])) {
|
if(!FollowerService::follows($pid, $account['id'])) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -722,6 +725,13 @@ class ApiV1Controller extends Controller
|
||||||
->exists();
|
->exists();
|
||||||
|
|
||||||
if($isFollowing == false) {
|
if($isFollowing == false) {
|
||||||
|
$followRequest = FollowRequest::whereFollowerId($user->profile_id)
|
||||||
|
->whereFollowingId($target->id)
|
||||||
|
->first();
|
||||||
|
if($followRequest) {
|
||||||
|
$followRequest->delete();
|
||||||
|
RelationshipService::refresh($target->id, $user->profile_id);
|
||||||
|
}
|
||||||
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
|
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
|
@ -1149,15 +1159,22 @@ class ApiV1Controller extends Controller
|
||||||
public function accountFollowRequests(Request $request)
|
public function accountFollowRequests(Request $request)
|
||||||
{
|
{
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'sometimes|integer|min:1|max:100'
|
||||||
|
]);
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
$followRequests = FollowRequest::whereFollowingId($user->profile->id)->pluck('follower_id');
|
$res = FollowRequest::whereFollowingId($user->profile->id)
|
||||||
|
->limit($request->input('limit', 40))
|
||||||
|
->pluck('follower_id')
|
||||||
|
->map(function($id) {
|
||||||
|
return AccountService::getMastodon($id, true);
|
||||||
|
})
|
||||||
|
->filter(function($acct) {
|
||||||
|
return $acct && isset($acct['id']);
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
$profiles = Profile::find($followRequests);
|
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
|
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1171,10 +1188,46 @@ class ApiV1Controller extends Controller
|
||||||
public function accountFollowRequestAccept(Request $request, $id)
|
public function accountFollowRequestAccept(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$target = AccountService::getMastodon($id);
|
||||||
|
|
||||||
// todo
|
if(!$target) {
|
||||||
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([]);
|
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
|
||||||
|
|
||||||
|
if(!$followRequest) {
|
||||||
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$follower = $followRequest->follower;
|
||||||
|
$follow = new Follower();
|
||||||
|
$follow->profile_id = $follower->id;
|
||||||
|
$follow->following_id = $pid;
|
||||||
|
$follow->save();
|
||||||
|
|
||||||
|
$profile = Profile::findOrFail($pid);
|
||||||
|
$profile->followers_count++;
|
||||||
|
$profile->save();
|
||||||
|
AccountService::del($profile->id);
|
||||||
|
|
||||||
|
$profile = Profile::findOrFail($follower->id);
|
||||||
|
$profile->following_count++;
|
||||||
|
$profile->save();
|
||||||
|
AccountService::del($profile->id);
|
||||||
|
|
||||||
|
if($follower->domain != null && $follower->private_key === null) {
|
||||||
|
FollowAcceptPipeline::dispatch($followRequest);
|
||||||
|
} else {
|
||||||
|
FollowPipeline::dispatch($follow);
|
||||||
|
$followRequest->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationshipService::refresh($pid, $id);
|
||||||
|
$res = RelationshipService::get($pid, $id);
|
||||||
|
$res['followed_by'] = true;
|
||||||
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1187,10 +1240,30 @@ class ApiV1Controller extends Controller
|
||||||
public function accountFollowRequestReject(Request $request, $id)
|
public function accountFollowRequestReject(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$target = AccountService::getMastodon($id);
|
||||||
|
|
||||||
// todo
|
if(!$target) {
|
||||||
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([]);
|
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
|
||||||
|
|
||||||
|
if(!$followRequest) {
|
||||||
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$follower = $followRequest->follower;
|
||||||
|
|
||||||
|
if($follower->domain != null && $follower->private_key === null) {
|
||||||
|
FollowRejectPipeline::dispatch($followRequest);
|
||||||
|
} else {
|
||||||
|
$followRequest->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationshipService::refresh($pid, $id);
|
||||||
|
$res = RelationshipService::get($pid, $id);
|
||||||
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1811,7 +1884,7 @@ class ApiV1Controller extends Controller
|
||||||
->take(($limit * 2))
|
->take(($limit * 2))
|
||||||
->get()
|
->get()
|
||||||
->map(function($s) use($pid) {
|
->map(function($s) use($pid) {
|
||||||
$status = StatusService::getMastodon($s['id']);
|
$status = StatusService::getMastodon($s['id'], false);
|
||||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1842,7 +1915,7 @@ class ApiV1Controller extends Controller
|
||||||
->take(($limit * 2))
|
->take(($limit * 2))
|
||||||
->get()
|
->get()
|
||||||
->map(function($s) use($pid) {
|
->map(function($s) use($pid) {
|
||||||
$status = StatusService::getMastodon($s['id']);
|
$status = StatusService::getMastodon($s['id'], false);
|
||||||
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1899,28 +1972,46 @@ class ApiV1Controller extends Controller
|
||||||
$this->validate($request,[
|
$this->validate($request,[
|
||||||
'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
|
'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
|
||||||
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
|
||||||
'limit' => 'nullable|integer|max:100'
|
'limit' => 'nullable|integer|max:100',
|
||||||
|
'remote' => 'sometimes'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$min = $request->input('min_id');
|
$min = $request->input('min_id');
|
||||||
$max = $request->input('max_id');
|
$max = $request->input('max_id');
|
||||||
$limit = $request->input('limit') ?? 20;
|
$limit = $request->input('limit') ?? 20;
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
$remote = $request->has('remote');
|
||||||
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
||||||
|
|
||||||
Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() {
|
if($remote && config('instance.timeline.network.cached')) {
|
||||||
if(PublicTimelineService::count() == 0) {
|
Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() {
|
||||||
PublicTimelineService::warmCache(true, 400);
|
if(NetworkTimelineService::count() == 0) {
|
||||||
}
|
NetworkTimelineService::warmCache(true, config('instance.timeline.network.cache_dropoff'));
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if ($max) {
|
if ($max) {
|
||||||
$feed = PublicTimelineService::getRankedMaxId($max, $limit + 5);
|
$feed = NetworkTimelineService::getRankedMaxId($max, $limit + 5);
|
||||||
} else if ($min) {
|
} else if ($min) {
|
||||||
$feed = PublicTimelineService::getRankedMinId($min, $limit + 5);
|
$feed = NetworkTimelineService::getRankedMinId($min, $limit + 5);
|
||||||
} else {
|
} else {
|
||||||
$feed = PublicTimelineService::get(0, $limit + 5);
|
$feed = NetworkTimelineService::get(0, $limit + 5);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() {
|
||||||
|
if(PublicTimelineService::count() == 0) {
|
||||||
|
PublicTimelineService::warmCache(true, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($max) {
|
||||||
|
$feed = PublicTimelineService::getRankedMaxId($max, $limit + 5);
|
||||||
|
} else if ($min) {
|
||||||
|
$feed = PublicTimelineService::getRankedMinId($min, $limit + 5);
|
||||||
|
} else {
|
||||||
|
$feed = PublicTimelineService::get(0, $limit + 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$res = collect($feed)
|
$res = collect($feed)
|
||||||
->map(function($k) use($user) {
|
->map(function($k) use($user) {
|
||||||
|
@ -1943,6 +2034,9 @@ class ApiV1Controller extends Controller
|
||||||
// ->toArray();
|
// ->toArray();
|
||||||
|
|
||||||
$baseUrl = config('app.url') . '/api/v1/timelines/public?limit=' . $limit . '&';
|
$baseUrl = config('app.url') . '/api/v1/timelines/public?limit=' . $limit . '&';
|
||||||
|
if($remote) {
|
||||||
|
$baseUrl .= 'remote=1&';
|
||||||
|
}
|
||||||
$minId = $res->map(function($s) {
|
$minId = $res->map(function($s) {
|
||||||
return ['id' => $s['id']];
|
return ['id' => $s['id']];
|
||||||
})->min('id');
|
})->min('id');
|
||||||
|
|
|
@ -217,4 +217,20 @@ class LiveStreamController extends Controller
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getConfig(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!config('livestreaming.enabled'), 400);
|
||||||
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
|
$res = [
|
||||||
|
'enabled' => config('livestreaming.enabled'),
|
||||||
|
'broadcast' => [
|
||||||
|
'sources' => config('livestreaming.broadcast.sources'),
|
||||||
|
'limits' => config('livestreaming.broadcast.limits')
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ use App\Services\{
|
||||||
LikeService,
|
LikeService,
|
||||||
PublicTimelineService,
|
PublicTimelineService,
|
||||||
ProfileService,
|
ProfileService,
|
||||||
|
NetworkTimelineService,
|
||||||
ReblogService,
|
ReblogService,
|
||||||
RelationshipService,
|
RelationshipService,
|
||||||
StatusService,
|
StatusService,
|
||||||
|
@ -521,7 +522,7 @@ class PublicApiController extends Controller
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get()
|
->get()
|
||||||
->map(function($s) use ($user) {
|
->map(function($s) use ($user) {
|
||||||
$status = StatusService::get($s->id);
|
$status = StatusService::get($s->id, false);
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -567,7 +568,7 @@ class PublicApiController extends Controller
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get()
|
->get()
|
||||||
->map(function($s) use ($user) {
|
->map(function($s) use ($user) {
|
||||||
$status = StatusService::get($s->id);
|
$status = StatusService::get($s->id, false);
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -608,59 +609,92 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
||||||
|
|
||||||
if($min || $max) {
|
if(config('instance.timeline.network.cached') == false) {
|
||||||
$dir = $min ? '>' : '<';
|
if($min || $max) {
|
||||||
$id = $min ?? $max;
|
$dir = $min ? '>' : '<';
|
||||||
$timeline = Status::select(
|
$id = $min ?? $max;
|
||||||
'id',
|
$timeline = Status::select(
|
||||||
'uri',
|
'id',
|
||||||
'type',
|
'uri',
|
||||||
'scope',
|
'type',
|
||||||
'created_at',
|
'scope',
|
||||||
)
|
'created_at',
|
||||||
->where('id', $dir, $id)
|
)
|
||||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
->where('id', $dir, $id)
|
||||||
->whereNotIn('profile_id', $filtered)
|
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
->whereNotIn('profile_id', $filtered)
|
||||||
->whereNotNull('uri')
|
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||||
->whereScope('public')
|
->whereNotNull('uri')
|
||||||
->where('id', '>', $amin)
|
->whereScope('public')
|
||||||
->orderBy('created_at', 'desc')
|
->where('id', '>', $amin)
|
||||||
->limit($limit)
|
->orderBy('created_at', 'desc')
|
||||||
->get()
|
->limit($limit)
|
||||||
->map(function($s) use ($user) {
|
->get()
|
||||||
$status = StatusService::get($s->id);
|
->map(function($s) use ($user) {
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status = StatusService::get($s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
return $status;
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
});
|
return $status;
|
||||||
$res = $timeline->toArray();
|
});
|
||||||
} else {
|
$res = $timeline->toArray();
|
||||||
$timeline = Status::select(
|
} else {
|
||||||
'id',
|
$timeline = Status::select(
|
||||||
'uri',
|
'id',
|
||||||
'type',
|
'uri',
|
||||||
'scope',
|
'type',
|
||||||
'created_at',
|
'scope',
|
||||||
)
|
'created_at',
|
||||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
)
|
||||||
->whereNotIn('profile_id', $filtered)
|
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
->whereNotIn('profile_id', $filtered)
|
||||||
->whereNotNull('uri')
|
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||||
->whereScope('public')
|
->whereNotNull('uri')
|
||||||
->where('id', '>', $amin)
|
->whereScope('public')
|
||||||
->orderBy('created_at', 'desc')
|
->where('id', '>', $amin)
|
||||||
->limit($limit)
|
->orderBy('created_at', 'desc')
|
||||||
->get()
|
->limit($limit)
|
||||||
->map(function($s) use ($user) {
|
->get()
|
||||||
$status = StatusService::get($s->id);
|
->map(function($s) use ($user) {
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status = StatusService::get($s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
return $status;
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
});
|
return $status;
|
||||||
$res = $timeline->toArray();
|
});
|
||||||
|
$res = $timeline->toArray();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() {
|
||||||
|
if(NetworkTimelineService::count() == 0) {
|
||||||
|
NetworkTimelineService::warmCache(true, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($max) {
|
||||||
|
$feed = NetworkTimelineService::getRankedMaxId($max, $limit);
|
||||||
|
} else if ($min) {
|
||||||
|
$feed = NetworkTimelineService::getRankedMinId($min, $limit);
|
||||||
|
} else {
|
||||||
|
$feed = NetworkTimelineService::get(0, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = collect($feed)
|
||||||
|
->map(function($k) use($user) {
|
||||||
|
$status = StatusService::get($k);
|
||||||
|
if($status && isset($status['account']) && $user) {
|
||||||
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
|
||||||
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $k);
|
||||||
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
|
||||||
|
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
|
||||||
|
}
|
||||||
|
return $status;
|
||||||
|
})
|
||||||
|
->filter(function($s) use($filtered) {
|
||||||
|
return $s && isset($s['account']) && in_array($s['account']['id'], $filtered) == false;
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
|
@ -704,7 +738,7 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
if($pid != $account['id']) {
|
if($pid != $account['id']) {
|
||||||
if($account['locked']) {
|
if($account['locked']) {
|
||||||
if(FollowerService::follows($pid, $account['id'])) {
|
if(!FollowerService::follows($pid, $account['id'])) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -744,7 +778,7 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
if($pid != $account['id']) {
|
if($pid != $account['id']) {
|
||||||
if($account['locked']) {
|
if($account['locked']) {
|
||||||
if(FollowerService::follows($pid, $account['id'])) {
|
if(!FollowerService::follows($pid, $account['id'])) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,9 @@ class TrustProxies extends Middleware
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
protected $headers = Request::HEADER_X_FORWARDED_FOR |
|
||||||
|
Request::HEADER_X_FORWARDED_HOST |
|
||||||
|
Request::HEADER_X_FORWARDED_PORT |
|
||||||
|
Request::HEADER_X_FORWARDED_PROTO |
|
||||||
|
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||||
}
|
}
|
||||||
|
|
69
app/Jobs/FollowPipeline/FollowAcceptPipeline.php
Normal file
69
app/Jobs/FollowPipeline/FollowAcceptPipeline.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\FollowPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
use Cache, Log;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
use App\FollowRequest;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use App\Transformer\ActivityPub\Verb\AcceptFollow;
|
||||||
|
|
||||||
|
class FollowAcceptPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $followRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(FollowRequest $followRequest)
|
||||||
|
{
|
||||||
|
$this->followRequest = $followRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$follow = $this->followRequest;
|
||||||
|
$actor = $follow->actor;
|
||||||
|
$target = $follow->target;
|
||||||
|
|
||||||
|
if($actor->domain == null || $actor->inbox_url == null || !$target->private_key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fractal = new Fractal\Manager();
|
||||||
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
$resource = new Fractal\Resource\Item($follow, new AcceptFollow());
|
||||||
|
$activity = $fractal->createData($resource)->toArray();
|
||||||
|
$url = $actor->sharedInbox ?? $actor->inbox_url;
|
||||||
|
|
||||||
|
Helpers::sendSignedObject($target, $url, $activity);
|
||||||
|
|
||||||
|
$follow->delete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,11 +63,6 @@ class FollowPipeline implements ShouldQueue
|
||||||
$notification->item_id = $target->id;
|
$notification->item_id = $target->id;
|
||||||
$notification->item_type = "App\Profile";
|
$notification->item_type = "App\Profile";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
|
||||||
$redis = Redis::connection();
|
|
||||||
|
|
||||||
$nkey = config('cache.prefix').':user.'.$target->id.'.notifications';
|
|
||||||
$redis->lpush($nkey, $notification->id);
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
}
|
}
|
||||||
|
|
69
app/Jobs/FollowPipeline/FollowRejectPipeline.php
Normal file
69
app/Jobs/FollowPipeline/FollowRejectPipeline.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\FollowPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
use Cache, Log;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
use App\FollowRequest;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use App\Transformer\ActivityPub\Verb\RejectFollow;
|
||||||
|
|
||||||
|
class FollowRejectPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $followRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(FollowRequest $followRequest)
|
||||||
|
{
|
||||||
|
$this->followRequest = $followRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$follow = $this->followRequest;
|
||||||
|
$actor = $follow->actor;
|
||||||
|
$target = $follow->target;
|
||||||
|
|
||||||
|
if($actor->domain == null || $actor->inbox_url == null || !$target->private_key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fractal = new Fractal\Manager();
|
||||||
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
$resource = new Fractal\Resource\Item($follow, new RejectFollow());
|
||||||
|
$activity = $fractal->createData($resource)->toArray();
|
||||||
|
$url = $actor->sharedInbox ?? $actor->inbox_url;
|
||||||
|
|
||||||
|
Helpers::sendSignedObject($target, $url, $activity);
|
||||||
|
|
||||||
|
$follow->delete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,21 @@ class LiveStream extends Model
|
||||||
$host = config('livestreaming.server.host');
|
$host = config('livestreaming.server.host');
|
||||||
$port = ':' . config('livestreaming.server.port');
|
$port = ':' . config('livestreaming.server.port');
|
||||||
$path = '/' . config('livestreaming.server.path') . '?';
|
$path = '/' . config('livestreaming.server.path') . '?';
|
||||||
|
$query = http_build_query([
|
||||||
|
'name' => $this->stream_id,
|
||||||
|
'key' => $this->stream_key,
|
||||||
|
'ts' => time()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $proto . $host . $port . $path . $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStreamRtmpUrl()
|
||||||
|
{
|
||||||
|
$proto = 'rtmp://';
|
||||||
|
$host = config('livestreaming.server.host');
|
||||||
|
$port = ':' . config('livestreaming.server.port');
|
||||||
|
$path = '/' . config('livestreaming.server.path') . '/'. $this->stream_id . '?';
|
||||||
$query = http_build_query([
|
$query = http_build_query([
|
||||||
'key' => $this->stream_key,
|
'key' => $this->stream_key,
|
||||||
'ts' => time()
|
'ts' => time()
|
||||||
|
|
|
@ -271,7 +271,28 @@ class Profile extends Model
|
||||||
$this->permalink('/followers')
|
$this->permalink('/followers')
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'unlisted':
|
||||||
|
$audience = [
|
||||||
|
'to' => [
|
||||||
|
],
|
||||||
|
'cc' => [
|
||||||
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
|
$this->permalink('/followers')
|
||||||
|
]
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'private':
|
||||||
|
$audience = [
|
||||||
|
'to' => [
|
||||||
|
$this->permalink('/followers')
|
||||||
|
],
|
||||||
|
'cc' => [
|
||||||
|
]
|
||||||
|
];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return $audience;
|
return $audience;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,11 +78,11 @@ class FollowerService
|
||||||
}
|
}
|
||||||
return $profile
|
return $profile
|
||||||
->followers()
|
->followers()
|
||||||
->whereLocalProfile(false)
|
|
||||||
->get()
|
->get()
|
||||||
->map(function($follow) {
|
->map(function($follow) {
|
||||||
return $follow->sharedInbox ?? $follow->inbox_url;
|
return $follow->sharedInbox ?? $follow->inbox_url;
|
||||||
})
|
})
|
||||||
|
->filter()
|
||||||
->unique()
|
->unique()
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
95
app/Services/NetworkTimelineService.php
Normal file
95
app/Services/NetworkTimelineService.php
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use App\{
|
||||||
|
Profile,
|
||||||
|
Status,
|
||||||
|
UserFilter
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkTimelineService
|
||||||
|
{
|
||||||
|
const CACHE_KEY = 'pf:services:timeline:network';
|
||||||
|
|
||||||
|
public static function get($start = 0, $stop = 10)
|
||||||
|
{
|
||||||
|
if($stop > 100) {
|
||||||
|
$stop = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Redis::zrevrange(self::CACHE_KEY, $start, $stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRankedMaxId($start = null, $limit = 10)
|
||||||
|
{
|
||||||
|
if(!$start) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY, $start, '-inf', [
|
||||||
|
'withscores' => true,
|
||||||
|
'limit' => [1, $limit]
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRankedMinId($end = null, $limit = 10)
|
||||||
|
{
|
||||||
|
if(!$end) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY, '+inf', $end, [
|
||||||
|
'withscores' => true,
|
||||||
|
'limit' => [0, $limit]
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function add($val)
|
||||||
|
{
|
||||||
|
if(self::count() > config('instance.timeline.network.cache_dropoff')) {
|
||||||
|
if(config('database.redis.client') === 'phpredis') {
|
||||||
|
Redis::zpopmin(self::CACHE_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Redis::zadd(self::CACHE_KEY, $val, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rem($val)
|
||||||
|
{
|
||||||
|
return Redis::zrem(self::CACHE_KEY, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function del($val)
|
||||||
|
{
|
||||||
|
return self::rem($val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function count()
|
||||||
|
{
|
||||||
|
return Redis::zcard(self::CACHE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function warmCache($force = false, $limit = 100)
|
||||||
|
{
|
||||||
|
if(self::count() == 0 || $force == true) {
|
||||||
|
Redis::del(self::CACHE_KEY);
|
||||||
|
$ids = Status::whereNotNull('uri')
|
||||||
|
->whereScope('public')
|
||||||
|
->whereNull('in_reply_to_id')
|
||||||
|
->whereNull('reblog_of_id')
|
||||||
|
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||||
|
->where('created_at', '>', now()->subHours(config('instance.timeline.network.max_hours_old')))
|
||||||
|
->orderByDesc('created_at')
|
||||||
|
->limit($limit)
|
||||||
|
->pluck('id');
|
||||||
|
foreach($ids as $id) {
|
||||||
|
self::add($id);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,8 @@ class RelationshipService
|
||||||
|
|
||||||
public static function refresh($aid, $tid)
|
public static function refresh($aid, $tid)
|
||||||
{
|
{
|
||||||
|
Cache::forget('pf:services:follow:audience:' . $aid);
|
||||||
|
Cache::forget('pf:services:follow:audience:' . $tid);
|
||||||
self::delete($tid, $aid);
|
self::delete($tid, $aid);
|
||||||
self::delete($aid, $tid);
|
self::delete($aid, $tid);
|
||||||
self::get($tid, $aid);
|
self::get($tid, $aid);
|
||||||
|
|
|
@ -3,26 +3,24 @@
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use App\UserFilter;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
use App\{
|
class UserFilterService
|
||||||
Follower,
|
{
|
||||||
Profile,
|
|
||||||
UserFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserFilterService {
|
|
||||||
|
|
||||||
const USER_MUTES_KEY = 'pf:services:mutes:ids:';
|
const USER_MUTES_KEY = 'pf:services:mutes:ids:';
|
||||||
const USER_BLOCKS_KEY = 'pf:services:blocks:ids:';
|
const USER_BLOCKS_KEY = 'pf:services:blocks:ids:';
|
||||||
|
|
||||||
public static function mutes(int $profile_id) : array
|
public static function mutes(int $profile_id)
|
||||||
{
|
{
|
||||||
$key = self::USER_MUTES_KEY . $profile_id;
|
$key = self::USER_MUTES_KEY . $profile_id;
|
||||||
$cached = Redis::zrevrange($key, 0, -1);
|
$warm = Cache::has($key . ':cached');
|
||||||
if($cached) {
|
if($warm) {
|
||||||
return $cached;
|
return Redis::zrevrange($key, 0, -1) ?? [];
|
||||||
} else {
|
} else {
|
||||||
|
if(Redis::zrevrange($key, 0, -1)) {
|
||||||
|
return Redis::zrevrange($key, 0, -1);
|
||||||
|
}
|
||||||
$ids = UserFilter::whereFilterType('mute')
|
$ids = UserFilter::whereFilterType('mute')
|
||||||
->whereUserId($profile_id)
|
->whereUserId($profile_id)
|
||||||
->pluck('filterable_id')
|
->pluck('filterable_id')
|
||||||
|
@ -30,29 +28,34 @@ class UserFilterService {
|
||||||
foreach ($ids as $muted_id) {
|
foreach ($ids as $muted_id) {
|
||||||
Redis::zadd($key, (int) $muted_id, (int) $muted_id);
|
Redis::zadd($key, (int) $muted_id, (int) $muted_id);
|
||||||
}
|
}
|
||||||
|
Cache::set($key . ':cached', 1, 7776000);
|
||||||
return $ids;
|
return $ids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function blocks(int $profile_id) : array
|
public static function blocks(int $profile_id)
|
||||||
{
|
{
|
||||||
$key = self::USER_BLOCKS_KEY . $profile_id;
|
$key = self::USER_BLOCKS_KEY . $profile_id;
|
||||||
$cached = Redis::zrevrange($key, 0, -1);
|
$warm = Cache::has($key . ':cached');
|
||||||
if($cached) {
|
if($warm) {
|
||||||
return $cached;
|
return Redis::zrevrange($key, 0, -1) ?? [];
|
||||||
} else {
|
} else {
|
||||||
|
if(Redis::zrevrange($key, 0, -1)) {
|
||||||
|
return Redis::zrevrange($key, 0, -1);
|
||||||
|
}
|
||||||
$ids = UserFilter::whereFilterType('block')
|
$ids = UserFilter::whereFilterType('block')
|
||||||
->whereUserId($profile_id)
|
->whereUserId($profile_id)
|
||||||
->pluck('filterable_id')
|
->pluck('filterable_id')
|
||||||
->toArray();
|
->toArray();
|
||||||
foreach ($ids as $blocked_id) {
|
foreach ($ids as $blocked_id) {
|
||||||
Redis::zadd($key, $blocked_id, $blocked_id);
|
Redis::zadd($key, (int) $blocked_id, (int) $blocked_id);
|
||||||
}
|
}
|
||||||
|
Cache::set($key . ':cached', 1, 7776000);
|
||||||
return $ids;
|
return $ids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function filters(int $profile_id) : array
|
public static function filters(int $profile_id)
|
||||||
{
|
{
|
||||||
return array_unique(array_merge(self::mutes($profile_id), self::blocks($profile_id)));
|
return array_unique(array_merge(self::mutes($profile_id), self::blocks($profile_id)));
|
||||||
}
|
}
|
||||||
|
|
25
app/Transformer/ActivityPub/Verb/AcceptFollow.php
Normal file
25
app/Transformer/ActivityPub/Verb/AcceptFollow.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\ActivityPub\Verb;
|
||||||
|
|
||||||
|
use App\FollowRequest;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
|
class AcceptFollow extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform(FollowRequest $follow)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'type' => 'Accept',
|
||||||
|
'id' => $follow->permalink(),
|
||||||
|
'actor' => $follow->target->permalink(),
|
||||||
|
'object' => [
|
||||||
|
'type' => 'Follow',
|
||||||
|
'id' => $follow->activity && isset($follow->activity['id']) ? $follow->activity['id'] : null,
|
||||||
|
'actor' => $follow->actor->permalink(),
|
||||||
|
'object' => $follow->target->permalink()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
25
app/Transformer/ActivityPub/Verb/RejectFollow.php
Normal file
25
app/Transformer/ActivityPub/Verb/RejectFollow.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Transformer\ActivityPub\Verb;
|
||||||
|
|
||||||
|
use App\FollowRequest;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
|
class RejectFollow extends Fractal\TransformerAbstract
|
||||||
|
{
|
||||||
|
public function transform(FollowRequest $follow)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'type' => 'Reject',
|
||||||
|
'id' => $follow->permalink(null, '#rejects'),
|
||||||
|
'actor' => $follow->target->permalink(),
|
||||||
|
'object' => [
|
||||||
|
'type' => 'Follow',
|
||||||
|
'id' => $follow->activity && isset($follow->activity['id']) ? $follow->activity['id'] : null,
|
||||||
|
'actor' => $follow->actor->permalink(),
|
||||||
|
'object' => $follow->target->permalink()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ use App\Services\CustomEmojiService;
|
||||||
use App\Services\InstanceService;
|
use App\Services\InstanceService;
|
||||||
use App\Services\MediaPathService;
|
use App\Services\MediaPathService;
|
||||||
use App\Services\MediaStorageService;
|
use App\Services\MediaStorageService;
|
||||||
|
use App\Services\NetworkTimelineService;
|
||||||
use App\Jobs\MediaPipeline\MediaStoragePipeline;
|
use App\Jobs\MediaPipeline\MediaStoragePipeline;
|
||||||
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
|
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
|
||||||
use App\Util\Media\License;
|
use App\Util\Media\License;
|
||||||
|
@ -490,6 +491,16 @@ class Helpers {
|
||||||
if(isset($activity['tag']) && is_array($activity['tag']) && !empty($activity['tag'])) {
|
if(isset($activity['tag']) && is_array($activity['tag']) && !empty($activity['tag'])) {
|
||||||
StatusTagsPipeline::dispatch($activity, $status);
|
StatusTagsPipeline::dispatch($activity, $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( config('instance.timeline.network.cached') &&
|
||||||
|
$status->in_reply_to_id === null &&
|
||||||
|
$status->reblog_of_id === null &&
|
||||||
|
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) &&
|
||||||
|
$status->created_at->gt(now()->subHours(config('instance.timeline.network.max_hours_old')))
|
||||||
|
) {
|
||||||
|
NetworkTimelineService::add($status->id);
|
||||||
|
}
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -473,17 +473,12 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if($target->is_private == true) {
|
if($target->is_private == true) {
|
||||||
FollowRequest::firstOrCreate([
|
FollowRequest::updateOrCreate([
|
||||||
'follower_id' => $actor->id,
|
'follower_id' => $actor->id,
|
||||||
'following_id' => $target->id
|
'following_id' => $target->id,
|
||||||
|
],[
|
||||||
|
'activity' => collect($this->payload)->only(['id','actor','object','type'])->toArray()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Cache::forget('profile:follower_count:'.$target->id);
|
|
||||||
Cache::forget('profile:follower_count:'.$actor->id);
|
|
||||||
Cache::forget('profile:following_count:'.$target->id);
|
|
||||||
Cache::forget('profile:following_count:'.$actor->id);
|
|
||||||
FollowerService::add($actor->id, $target->id);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$follower = new Follower;
|
$follower = new Follower;
|
||||||
$follower->profile_id = $actor->id;
|
$follower->profile_id = $actor->id;
|
||||||
|
|
406
composer.lock
generated
406
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -24,6 +24,12 @@ return [
|
||||||
'timeline' => [
|
'timeline' => [
|
||||||
'local' => [
|
'local' => [
|
||||||
'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false)
|
'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false)
|
||||||
|
],
|
||||||
|
|
||||||
|
'network' => [
|
||||||
|
'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', false) : false,
|
||||||
|
'cache_dropoff' => env('INSTANCE_NETWORK_TIMELINE_CACHE_DROPOFF', 100),
|
||||||
|
'max_hours_old' => env('INSTANCE_NETWORK_TIMELINE_CACHE_MAX_HOUR_INGEST', 6)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,20 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'broadcast' => [
|
'broadcast' => [
|
||||||
'max_duration' => env('HLS_LIVE_BROADCAST_MAX_DURATION', 60),
|
'delete_token_after_finished' => (bool) env('HLS_LIVE_BROADCAST_DELETE_TOKEN_AFTER', true),
|
||||||
'max_active' => env('HLS_LIVE_BROADCAST_MAX_ACTIVE', 10),
|
'max_duration' => (int) env('HLS_LIVE_BROADCAST_MAX_DURATION', 60),
|
||||||
|
'max_active' => (int) env('HLS_LIVE_BROADCAST_MAX_ACTIVE', 10),
|
||||||
|
|
||||||
'limits' => [
|
'limits' => [
|
||||||
'enabled' => env('HLS_LIVE_BROADCAST_LIMITS', true),
|
'enabled' => (bool) env('HLS_LIVE_BROADCAST_LIMITS', true),
|
||||||
'min_follower_count' => env('HLS_LIVE_BROADCAST_LIMITS_MIN_FOLLOWERS', 100),
|
'min_follower_count' => (int) env('HLS_LIVE_BROADCAST_LIMITS_MIN_FOLLOWERS', 100),
|
||||||
'min_account_age' => env('HLS_LIVE_BROADCAST_LIMITS_MIN_ACCOUNT_AGE', 14),
|
'min_account_age' => (int) env('HLS_LIVE_BROADCAST_LIMITS_MIN_ACCOUNT_AGE', 14),
|
||||||
'admins_only' => env('HLS_LIVE_BROADCAST_LIMITS_ADMINS_ONLY', true)
|
'admins_only' => (bool) env('HLS_LIVE_BROADCAST_LIMITS_ADMINS_ONLY', true)
|
||||||
|
],
|
||||||
|
|
||||||
|
'sources' => [
|
||||||
|
'app' => (bool) env('HLS_LIVE_BROADCAST_SOURCE_APP', false),
|
||||||
|
'web' => (bool) env('HLS_LIVE_BROADCAST_SOURCE_WEB', false)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -239,7 +239,7 @@ return [
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
'max_collection_length' => (int) env('PF_MAX_COLLECTION_LENGTH', 18),
|
'max_collection_length' => (int) env('PF_MAX_COLLECTION_LENGTH', 100),
|
||||||
|
|
||||||
'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
|
'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddObjectColumnToFollowRequestsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('follow_requests', function (Blueprint $table) {
|
||||||
|
$table->json('activity')->nullable()->after('following_id');
|
||||||
|
$table->timestamp('handled_at')->nullable()->after('is_local');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('follow_requests', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('activity');
|
||||||
|
$table->dropColumn('handled_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
BIN
public/css/app.css
vendored
BIN
public/css/app.css
vendored
Binary file not shown.
BIN
public/css/appdark.css
vendored
BIN
public/css/appdark.css
vendored
Binary file not shown.
BIN
public/js/compose-ojtjadoml.js
vendored
BIN
public/js/compose-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/daci-ojtjadoml.js
vendored
BIN
public/js/daci-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/dffc-ojtjadoml.js
vendored
BIN
public/js/dffc-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/dmsg-ojtjadoml.js
vendored
BIN
public/js/dmsg-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/dmyh-ojtjadoml.js
vendored
BIN
public/js/dmyh-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/dmym-ojtjadoml.js
vendored
BIN
public/js/dmym-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/dsfc-ojtjadoml.js
vendored
BIN
public/js/dsfc-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/dssc-ojtjadoml.js
vendored
BIN
public/js/dssc-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/home-ojtjadoml.js
vendored
BIN
public/js/home-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/manifest.js
vendored
BIN
public/js/manifest.js
vendored
Binary file not shown.
BIN
public/js/notifications-ojtjadoml.js
vendored
BIN
public/js/notifications-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/post-ojtjadoml.js
vendored
BIN
public/js/post-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/profile-ojtjadoml.js
vendored
BIN
public/js/profile-ojtjadoml.js
vendored
Binary file not shown.
BIN
public/js/spa.js
vendored
BIN
public/js/spa.js
vendored
Binary file not shown.
BIN
public/js/vendor.js
vendored
BIN
public/js/vendor.js
vendored
Binary file not shown.
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* BootstrapVue Icons, generated from Bootstrap Icons 1.2.2
|
* BootstrapVue Icons, generated from Bootstrap Icons 1.5.0
|
||||||
*
|
*
|
||||||
* @link https://icons.getbootstrap.com/
|
* @link https://icons.getbootstrap.com/
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
|
Binary file not shown.
|
@ -105,5 +105,6 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
|
||||||
Route::get('chat/latest', 'LiveStreamController@getLatestChat')->middleware($middleware);
|
Route::get('chat/latest', 'LiveStreamController@getLatestChat')->middleware($middleware);
|
||||||
Route::post('chat/message', 'LiveStreamController@addChatComment')->middleware($middleware);
|
Route::post('chat/message', 'LiveStreamController@addChatComment')->middleware($middleware);
|
||||||
Route::post('chat/delete', 'LiveStreamController@deleteChatComment')->middleware($middleware);
|
Route::post('chat/delete', 'LiveStreamController@deleteChatComment')->middleware($middleware);
|
||||||
|
Route::get('config', 'LiveStreamController@getConfig')->middleware($middleware);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue