+ * Test if the body contains valid content
+ *
+ * @param string $body
+ * @return boolean
+ */
+ private static function invalidBody(string $body)
+ {
+ // Currently we only test for a HTML element.
+ // Possibly we enhance this in the future.
+ return !strpos($body, '>');
+ }
+
+ /**
+ * Update GServer entries
+ */
+ public static function discover()
+ {
+ // Update the server list
+ self::discoverFederation();
+
+ $no_of_queries = 5;
+
+ $requery_days = intval(DI::config()->get('system', 'poco_requery_days'));
+
+ if ($requery_days == 0) {
+ $requery_days = 7;
+ }
+
+ $last_update = date('c', time() - (60 * 60 * 24 * $requery_days));
+
+ $gservers = DBA::select('gserver', ['id', 'url', 'nurl', 'network', 'poco', 'directory-type'],
+ ["NOT `failed` AND `directory-type` != ? AND `last_poco_query` < ?", GServer::DT_NONE, $last_update],
+ ['order' => ['RAND()']]);
+
+ while ($gserver = DBA::fetch($gservers)) {
+ Logger::info('Update peer list', ['server' => $gserver['url'], 'id' => $gserver['id']]);
+ Worker::add(PRIORITY_LOW, 'UpdateServerPeers', $gserver['url']);
+
+ Logger::info('Update directory', ['server' => $gserver['url'], 'id' => $gserver['id']]);
+ Worker::add(PRIORITY_LOW, 'UpdateServerDirectory', $gserver);
+
+ $fields = ['last_poco_query' => DateTimeFormat::utcNow()];
+ DBA::update('gserver', $fields, ['nurl' => $gserver['nurl']]);
+
+ if (--$no_of_queries == 0) {
+ break;
+ }
+ }
+
+ DBA::close($gservers);
+ }
+
+ /**
+ * Discover federated servers
+ */
+ private static function discoverFederation()
+ {
+ $last = DI::config()->get('poco', 'last_federation_discovery');
+
+ if ($last) {
+ $next = $last + (24 * 60 * 60);
+
+ if ($next > time()) {
+ return;
+ }
+ }
+
+ // Discover federated servers
+ $protocols = ['activitypub', 'diaspora', 'dfrn', 'ostatus'];
+ foreach ($protocols as $protocol) {
+ $query = '{nodes(protocol:"' . $protocol . '"){host}}';
+ $curlResult = DI::httpClient()->fetch('https://the-federation.info/graphql?query=' . urlencode($query), 0, HttpClient::ACCEPT_JSON);
+ if (!empty($curlResult)) {
+ $data = json_decode($curlResult, true);
+ if (!empty($data['data']['nodes'])) {
+ foreach ($data['data']['nodes'] as $server) {
+ // Using "only_nodeinfo" since servers that are listed on that page should always have it.
+ self::add('https://' . $server['host'], true);
+ }
+ }
+ }
+ }
+
+ // Disvover Mastodon servers
+ $accesstoken = DI::config()->get('system', 'instances_social_key');
+
+ if (!empty($accesstoken)) {
+ $api = 'https://instances.social/api/1.0/instances/list?count=0';
+ $curlResult = DI::httpClient()->get($api, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $accesstoken]], HttpClientOptions::ACCEPT_CONTENT => HttpClient::ACCEPT_JSON]);
+ if ($curlResult->isSuccess()) {
+ $servers = json_decode($curlResult->getBody(), true);
+
+ foreach ($servers['instances'] as $server) {
+ $url = (is_null($server['https_score']) ? 'http' : 'https') . '://' . $server['name'];
+ self::add($url);
+ }
+ }
+ }
+
+ DI::config()->set('poco', 'last_federation_discovery', time());
+ }
+
+ /**
+ * Set the protocol for the given server