From 4aad1c225a2815d96291108d48f127165b0fa646 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 31 May 2024 03:35:40 -0600 Subject: [PATCH] Add api/v1/instance/peers API endpoint, disabled by default --- app/Http/Controllers/Api/ApiV1Controller.php | 22 ++ app/Services/InstanceService.php | 195 ++++++++-------- config/instance.php | 222 ++++++++++--------- routes/api.php | 1 + 4 files changed, 237 insertions(+), 203 deletions(-) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index bea98b9de..34fb256f1 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -4204,4 +4204,26 @@ class ApiV1Controller extends Controller return $this->json([]); } + + /** + * GET /api/v1/instance/peers + * + * + * @return array + */ + public function instancePeers(Request $request) + { + if ((bool) config('instance.show_peers') == false) { + return $this->json([]); + } + + return $this->json( + Cache::remember(InstanceService::CACHE_KEY_API_PEERS_LIST, now()->addHours(24), function () { + return Instance::whereNotNull('nodeinfo_last_fetched') + ->whereBanned(false) + ->where('nodeinfo_last_fetched', '>', now()->subDays(8)) + ->pluck('domain'); + }) + ); + } } diff --git a/app/Services/InstanceService.php b/app/Services/InstanceService.php index 2ad991063..c07e17521 100644 --- a/app/Services/InstanceService.php +++ b/app/Services/InstanceService.php @@ -2,76 +2,84 @@ namespace App\Services; -use Cache; use App\Instance; use App\Util\Blurhash\Blurhash; -use App\Services\ConfigCacheService; +use Cache; class InstanceService { const CACHE_KEY_BY_DOMAIN = 'pf:services:instance:by_domain:'; - const CACHE_KEY_BANNED_DOMAINS = 'instances:banned:domains'; - const CACHE_KEY_UNLISTED_DOMAINS = 'instances:unlisted:domains'; - const CACHE_KEY_NSFW_DOMAINS = 'instances:auto_cw:domains'; - const CACHE_KEY_STATS = 'pf:services:instances:stats'; - const CACHE_KEY_BANNER_BLURHASH = 'pf:services:instance:header-blurhash:v1'; - public function __construct() - { - ini_set('memory_limit', config('pixelfed.memory_limit', '1024M')); - } + const CACHE_KEY_BANNED_DOMAINS = 'instances:banned:domains'; - public static function getByDomain($domain) - { - return Cache::remember(self::CACHE_KEY_BY_DOMAIN.$domain, 3600, function() use($domain) { - return Instance::whereDomain($domain)->first(); - }); - } + const CACHE_KEY_UNLISTED_DOMAINS = 'instances:unlisted:domains'; - public static function getBannedDomains() - { - return Cache::remember(self::CACHE_KEY_BANNED_DOMAINS, 1209600, function() { - return Instance::whereBanned(true)->pluck('domain')->toArray(); - }); - } + const CACHE_KEY_NSFW_DOMAINS = 'instances:auto_cw:domains'; - public static function getUnlistedDomains() - { - return Cache::remember(self::CACHE_KEY_UNLISTED_DOMAINS, 1209600, function() { - return Instance::whereUnlisted(true)->pluck('domain')->toArray(); - }); - } + const CACHE_KEY_STATS = 'pf:services:instances:stats'; - public static function getNsfwDomains() - { - return Cache::remember(self::CACHE_KEY_NSFW_DOMAINS, 1209600, function() { - return Instance::whereAutoCw(true)->pluck('domain')->toArray(); - }); - } + const CACHE_KEY_BANNER_BLURHASH = 'pf:services:instance:header-blurhash:v1'; - public static function software($domain) - { - $key = 'instances:software:' . strtolower($domain); - return Cache::remember($key, 86400, function() use($domain) { - $instance = Instance::whereDomain($domain)->first(); - if(!$instance) { - return; - } - return $instance->software; - }); - } + const CACHE_KEY_API_PEERS_LIST = 'pf:services:instance:api:peers:list:v0'; - public static function stats() - { - return Cache::remember(self::CACHE_KEY_STATS, 86400, function() { - return [ - 'total_count' => Instance::count(), - 'new_count' => Instance::where('created_at', '>', now()->subDays(14))->count(), - 'banned_count' => Instance::whereBanned(true)->count(), - 'nsfw_count' => Instance::whereAutoCw(true)->count() - ]; - }); - } + public function __construct() + { + ini_set('memory_limit', config('pixelfed.memory_limit', '1024M')); + } + + public static function getByDomain($domain) + { + return Cache::remember(self::CACHE_KEY_BY_DOMAIN.$domain, 3600, function () use ($domain) { + return Instance::whereDomain($domain)->first(); + }); + } + + public static function getBannedDomains() + { + return Cache::remember(self::CACHE_KEY_BANNED_DOMAINS, 1209600, function () { + return Instance::whereBanned(true)->pluck('domain')->toArray(); + }); + } + + public static function getUnlistedDomains() + { + return Cache::remember(self::CACHE_KEY_UNLISTED_DOMAINS, 1209600, function () { + return Instance::whereUnlisted(true)->pluck('domain')->toArray(); + }); + } + + public static function getNsfwDomains() + { + return Cache::remember(self::CACHE_KEY_NSFW_DOMAINS, 1209600, function () { + return Instance::whereAutoCw(true)->pluck('domain')->toArray(); + }); + } + + public static function software($domain) + { + $key = 'instances:software:'.strtolower($domain); + + return Cache::remember($key, 86400, function () use ($domain) { + $instance = Instance::whereDomain($domain)->first(); + if (! $instance) { + return; + } + + return $instance->software; + }); + } + + public static function stats() + { + return Cache::remember(self::CACHE_KEY_STATS, 86400, function () { + return [ + 'total_count' => Instance::count(), + 'new_count' => Instance::where('created_at', '>', now()->subDays(14))->count(), + 'banned_count' => Instance::whereBanned(true)->count(), + 'nsfw_count' => Instance::whereAutoCw(true)->count(), + ]; + }); + } public static function refresh() { @@ -79,6 +87,7 @@ class InstanceService Cache::forget(self::CACHE_KEY_UNLISTED_DOMAINS); Cache::forget(self::CACHE_KEY_NSFW_DOMAINS); Cache::forget(self::CACHE_KEY_STATS); + Cache::forget(self::CACHE_KEY_API_PEERS_LIST); self::getBannedDomains(); self::getUnlistedDomains(); @@ -89,50 +98,50 @@ class InstanceService public static function headerBlurhash() { - return Cache::rememberForever(self::CACHE_KEY_BANNER_BLURHASH, function() { - if(str_ends_with(config_cache('app.banner_image'), 'headers/default.jpg')) { - return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt'; - } - $cached = config_cache('instance.banner.blurhash'); + return Cache::rememberForever(self::CACHE_KEY_BANNER_BLURHASH, function () { + if (str_ends_with(config_cache('app.banner_image'), 'headers/default.jpg')) { + return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt'; + } + $cached = config_cache('instance.banner.blurhash'); - if($cached) { - return $cached; - } + if ($cached) { + return $cached; + } - $file = config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')); + $file = config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')); - $image = imagecreatefromstring(file_get_contents($file)); - if(!$image) { - return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt'; - } - $width = imagesx($image); - $height = imagesy($image); + $image = imagecreatefromstring(file_get_contents($file)); + if (! $image) { + return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt'; + } + $width = imagesx($image); + $height = imagesy($image); - $pixels = []; - for ($y = 0; $y < $height; ++$y) { - $row = []; - for ($x = 0; $x < $width; ++$x) { - $index = imagecolorat($image, $x, $y); - $colors = imagecolorsforindex($image, $index); + $pixels = []; + for ($y = 0; $y < $height; $y++) { + $row = []; + for ($x = 0; $x < $width; $x++) { + $index = imagecolorat($image, $x, $y); + $colors = imagecolorsforindex($image, $index); - $row[] = [$colors['red'], $colors['green'], $colors['blue']]; - } - $pixels[] = $row; - } + $row[] = [$colors['red'], $colors['green'], $colors['blue']]; + } + $pixels[] = $row; + } - // Free the allocated GdImage object from memory: - imagedestroy($image); + // Free the allocated GdImage object from memory: + imagedestroy($image); - $components_x = 4; - $components_y = 4; - $blurhash = Blurhash::encode($pixels, $components_x, $components_y); - if(strlen($blurhash) > 191) { - return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt'; - } + $components_x = 4; + $components_y = 4; + $blurhash = Blurhash::encode($pixels, $components_x, $components_y); + if (strlen($blurhash) > 191) { + return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt'; + } - ConfigCacheService::put('instance.banner.blurhash', $blurhash); + ConfigCacheService::put('instance.banner.blurhash', $blurhash); - return $blurhash; - }); + return $blurhash; + }); } } diff --git a/config/instance.php b/config/instance.php index cfc0468ab..3c931cf7e 100644 --- a/config/instance.php +++ b/config/instance.php @@ -1,136 +1,136 @@ env('FORCE_HTTPS_URLS', true), + 'force_https_urls' => env('FORCE_HTTPS_URLS', true), - 'description' => env('INSTANCE_DESCRIPTION', 'Pixelfed - Photo sharing for everyone'), + 'description' => env('INSTANCE_DESCRIPTION', 'Pixelfed - Photo sharing for everyone'), - 'contact' => [ - 'enabled' => env('INSTANCE_CONTACT_FORM', false), - 'max_per_day' => env('INSTANCE_CONTACT_MAX_PER_DAY', 1), - ], + 'contact' => [ + 'enabled' => env('INSTANCE_CONTACT_FORM', false), + 'max_per_day' => env('INSTANCE_CONTACT_MAX_PER_DAY', 1), + ], - 'discover' => [ - 'public' => env('INSTANCE_DISCOVER_PUBLIC', false), - 'loops' => [ - 'enabled' => env('EXP_LOOPS', false), - ], - 'tags' => [ - 'is_public' => env('INSTANCE_PUBLIC_HASHTAGS', false) - ], - ], + 'discover' => [ + 'public' => env('INSTANCE_DISCOVER_PUBLIC', false), + 'loops' => [ + 'enabled' => env('EXP_LOOPS', false), + ], + 'tags' => [ + 'is_public' => env('INSTANCE_PUBLIC_HASHTAGS', false), + ], + ], - 'email' => env('INSTANCE_CONTACT_EMAIL'), + 'email' => env('INSTANCE_CONTACT_EMAIL'), - 'timeline' => [ - 'home' => [ - 'cached' => env('PF_HOME_TIMELINE_CACHE', false), - 'cache_ttl' => env('PF_HOME_TIMELINE_CACHE_TTL', 900) - ], + 'timeline' => [ + 'home' => [ + 'cached' => env('PF_HOME_TIMELINE_CACHE', false), + 'cache_ttl' => env('PF_HOME_TIMELINE_CACHE_TTL', 900), + ], - 'local' => [ - 'cached' => env('INSTANCE_PUBLIC_TIMELINE_CACHED', false), - 'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false) - ], + 'local' => [ + 'cached' => env('INSTANCE_PUBLIC_TIMELINE_CACHED', 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) - ] - ], + '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), + ], + ], - 'page' => [ - '404' => [ - 'header' => env('PAGE_404_HEADER', 'Sorry, this page isn\'t available.'), - 'body' => env('PAGE_404_BODY', 'The link you followed may be broken, or the page may have been removed. Go back to Pixelfed.') - ], - '503' => [ - 'header' => env('PAGE_503_HEADER', 'Service Unavailable'), - 'body' => env('PAGE_503_BODY', 'Our service is in maintenance mode, please try again later.') - ] - ], + 'page' => [ + '404' => [ + 'header' => env('PAGE_404_HEADER', 'Sorry, this page isn\'t available.'), + 'body' => env('PAGE_404_BODY', 'The link you followed may be broken, or the page may have been removed. Go back to Pixelfed.'), + ], + '503' => [ + 'header' => env('PAGE_503_HEADER', 'Service Unavailable'), + 'body' => env('PAGE_503_BODY', 'Our service is in maintenance mode, please try again later.'), + ], + ], - 'username' => [ - 'banned' => env('BANNED_USERNAMES'), - 'remote' => [ - 'formats' => ['@', 'from', 'custom'], - 'format' => in_array(env('USERNAME_REMOTE_FORMAT', '@'), ['@','from','custom']) ? env('USERNAME_REMOTE_FORMAT', '@') : '@', - 'custom' => env('USERNAME_REMOTE_CUSTOM_TEXT', null) - ] - ], + 'username' => [ + 'banned' => env('BANNED_USERNAMES'), + 'remote' => [ + 'formats' => ['@', 'from', 'custom'], + 'format' => in_array(env('USERNAME_REMOTE_FORMAT', '@'), ['@', 'from', 'custom']) ? env('USERNAME_REMOTE_FORMAT', '@') : '@', + 'custom' => env('USERNAME_REMOTE_CUSTOM_TEXT', null), + ], + ], - 'polls' => [ - 'enabled' => false - ], + 'polls' => [ + 'enabled' => false, + ], - 'stories' => [ - 'enabled' => env('STORIES_ENABLED', false), - ], + 'stories' => [ + 'enabled' => env('STORIES_ENABLED', false), + ], - 'restricted' => [ - 'enabled' => env('RESTRICTED_INSTANCE', false), - 'level' => 1 - ], + 'restricted' => [ + 'enabled' => env('RESTRICTED_INSTANCE', false), + 'level' => 1, + ], - 'oauth' => [ - 'token_expiration' => env('OAUTH_TOKEN_DAYS', 365), - 'refresh_expiration' => env('OAUTH_REFRESH_DAYS', 400), - 'pat' => [ - 'enabled' => env('OAUTH_PAT_ENABLED', false), - 'id' => env('OAUTH_PAT_ID'), - ] - ], + 'oauth' => [ + 'token_expiration' => env('OAUTH_TOKEN_DAYS', 365), + 'refresh_expiration' => env('OAUTH_REFRESH_DAYS', 400), + 'pat' => [ + 'enabled' => env('OAUTH_PAT_ENABLED', false), + 'id' => env('OAUTH_PAT_ID'), + ], + ], - 'label' => [ - 'covid' => [ - 'enabled' => env('ENABLE_COVID_LABEL', true), - 'url' => env('COVID_LABEL_URL', 'https://www.who.int/emergencies/diseases/novel-coronavirus-2019/advice-for-public'), - 'org' => env('COVID_LABEL_ORG', 'visit the WHO website') - ] - ], + 'label' => [ + 'covid' => [ + 'enabled' => env('ENABLE_COVID_LABEL', true), + 'url' => env('COVID_LABEL_URL', 'https://www.who.int/emergencies/diseases/novel-coronavirus-2019/advice-for-public'), + 'org' => env('COVID_LABEL_ORG', 'visit the WHO website'), + ], + ], - 'enable_cc' => env('ENABLE_CONFIG_CACHE', true), + 'enable_cc' => env('ENABLE_CONFIG_CACHE', true), - 'has_legal_notice' => env('INSTANCE_LEGAL_NOTICE', false), + 'has_legal_notice' => env('INSTANCE_LEGAL_NOTICE', false), - 'embed' => [ - 'profile' => env('INSTANCE_PROFILE_EMBEDS', true), - 'post' => env('INSTANCE_POST_EMBEDS', true), - ], + 'embed' => [ + 'profile' => env('INSTANCE_PROFILE_EMBEDS', true), + 'post' => env('INSTANCE_POST_EMBEDS', true), + ], - 'hide_nsfw_on_public_feeds' => env('PF_HIDE_NSFW_ON_PUBLIC_FEEDS', false), + 'hide_nsfw_on_public_feeds' => env('PF_HIDE_NSFW_ON_PUBLIC_FEEDS', false), - 'avatar' => [ - 'local_to_cloud' => env('PF_LOCAL_AVATAR_TO_CLOUD', false) - ], + 'avatar' => [ + 'local_to_cloud' => env('PF_LOCAL_AVATAR_TO_CLOUD', false), + ], - 'admin_invites' => [ - 'enabled' => env('PF_ADMIN_INVITES_ENABLED', true) - ], + 'admin_invites' => [ + 'enabled' => env('PF_ADMIN_INVITES_ENABLED', true), + ], - 'user_filters' => [ - 'max_user_blocks' => env('PF_MAX_USER_BLOCKS', 50), - 'max_user_mutes' => env('PF_MAX_USER_MUTES', 50), - 'max_domain_blocks' => env('PF_MAX_DOMAIN_BLOCKS', 50), - ], + 'user_filters' => [ + 'max_user_blocks' => env('PF_MAX_USER_BLOCKS', 50), + 'max_user_mutes' => env('PF_MAX_USER_MUTES', 50), + 'max_domain_blocks' => env('PF_MAX_DOMAIN_BLOCKS', 50), + ], - 'reports' => [ - 'email' => [ - 'enabled' => env('INSTANCE_REPORTS_EMAIL_ENABLED', false), - 'to' => env('INSTANCE_REPORTS_EMAIL_ADDRESSES'), - 'autospam' => env('INSTANCE_REPORTS_EMAIL_AUTOSPAM', false) - ] - ], + 'reports' => [ + 'email' => [ + 'enabled' => env('INSTANCE_REPORTS_EMAIL_ENABLED', false), + 'to' => env('INSTANCE_REPORTS_EMAIL_ADDRESSES'), + 'autospam' => env('INSTANCE_REPORTS_EMAIL_AUTOSPAM', false), + ], + ], - 'landing' => [ - 'show_directory' => env('INSTANCE_LANDING_SHOW_DIRECTORY', true), - 'show_explore' => env('INSTANCE_LANDING_SHOW_EXPLORE', true), - ], + 'landing' => [ + 'show_directory' => env('INSTANCE_LANDING_SHOW_DIRECTORY', true), + 'show_explore' => env('INSTANCE_LANDING_SHOW_EXPLORE', true), + ], - 'banner' => [ - 'blurhash' => env('INSTANCE_BANNER_BLURHASH', 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt') - ], + 'banner' => [ + 'blurhash' => env('INSTANCE_BANNER_BLURHASH', 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt'), + ], 'parental_controls' => [ 'enabled' => env('INSTANCE_PARENTAL_CONTROLS', false), @@ -143,14 +143,14 @@ return [ ], 'software-update' => [ - 'disable_failed_warning' => env('INSTANCE_SOFTWARE_UPDATE_DISABLE_FAILED_WARNING', false) + 'disable_failed_warning' => env('INSTANCE_SOFTWARE_UPDATE_DISABLE_FAILED_WARNING', false), ], 'notifications' => [ 'gc' => [ 'enabled' => env('INSTANCE_NOTIFY_AUTO_GC', false), - 'delete_after_days' => env('INSTANCE_NOTIFY_AUTO_GC_DEL_AFTER_DAYS', 365) - ] + 'delete_after_days' => env('INSTANCE_NOTIFY_AUTO_GC_DEL_AFTER_DAYS', 365), + ], ], 'curated_registration' => [ @@ -173,7 +173,9 @@ return [ 'max_per_day' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_VERIFY_MPD', 10), ], 'on_user_response' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_USER_RESPONSE', false), - ] + ], ], ], + + 'show_peers' => env('INSTANCE_SHOW_PEERS', false), ]; diff --git a/routes/api.php b/routes/api.php index d2e581a37..7cee24869 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,7 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::post('apps', 'Api\ApiV1Controller@apps'); Route::get('apps/verify_credentials', 'Api\ApiV1Controller@getApp')->middleware($middleware); Route::get('instance', 'Api\ApiV1Controller@instance'); + Route::get('instance/peers', 'Api\ApiV1Controller@instancePeers'); Route::get('bookmarks', 'Api\ApiV1Controller@bookmarks')->middleware($middleware); Route::get('accounts/verify_credentials', 'Api\ApiV1Controller@verifyCredentials')->middleware($middleware);