diff --git a/public/assets/bsky.js b/public/assets/bsky.js new file mode 100644 index 0000000..b59279d --- /dev/null +++ b/public/assets/bsky.js @@ -0,0 +1,26 @@ +(async () => { + const status = document.querySelector('.js-status'); + status.textContent = 'Looking up DID...'; + + const format = BSKY_FORMAT.replace('%s', location.protocol.substring(0, location.protocol.length - 1)); + let did = null; + + try { + const response = await fetch(`${location.protocol}//${BSKY_HANDLE}/.well-known/atproto-did`); + did = await response.text(); + } catch(ex) { + status.style.color = 'red'; + status.textContent = `Could not find DID! ${ex}`; + return; + } + + if(typeof did !== 'string' || !did.startsWith('did:')) { + status.style.color = 'red'; + status.textContent = 'Look up result was not a valid DID.'; + return; + } + + const url = format.replace('%s', did); + status.textContent = `Redirecting to ${url}...`; + location.replace(url); +})(); diff --git a/public/assets/fedi.js b/public/assets/fedi.js new file mode 100644 index 0000000..00a0594 --- /dev/null +++ b/public/assets/fedi.js @@ -0,0 +1,39 @@ +(async () => { + const status = document.querySelector('.js-status'); + status.textContent = 'Looking up Fediverse profile...'; + + let url = null; + try { + const response = await fetch(`${location.protocol}//${FEDI_INSTANCE}/.well-known/webfinger?format=json&resource=acct:${FEDI_USERNAME}@${FEDI_INSTANCE}`); + const webfinger = await response.json(); + + if(typeof webfinger === 'object') { + if(Array.isArray(webfinger.links)) + for(const link of webfinger.links) + if(typeof link === 'object' && link.rel === 'http://webfinger.net/rel/profile-page' && typeof link.href === 'string') { + url = link.href; + break; + } + + if(typeof url !== 'string' && Array.isArray(webfinger.aliases)) + for(const alias of webfinger.aliases) + if(typeof alias === 'string') { + url = alias; + break; + } + } + } catch(ex) { + status.style.color = 'red'; + status.textContent = `Could not complete Webfinger lookup! ${ex}`; + return; + } + + if(typeof url !== 'string' || (!url.startsWith('https://') && !url.startsWith('http://'))) { + status.style.color = 'red'; + status.textContent = 'Could not find an acceptable profile URL.'; + return; + } + + status.textContent = `Redirecting to ${url}...`; + location.replace(url); +})(); diff --git a/src/RedirectorRoutes.php b/src/RedirectorRoutes.php index 44a66a2..c6d251e 100644 --- a/src/RedirectorRoutes.php +++ b/src/RedirectorRoutes.php @@ -49,9 +49,10 @@ final class RedirectorRoutes implements IRouteHandler { $this->redirect($response, $request, $info->getString(1)); } - private function redirectSimple($response, $request, string $format, string $argument) { - $scheme = empty($_SERVER['HTTPS']) ? 'http' : 'https'; - $argument = rawurlencode($argument); + private function redirectSimple($response, $request, string $format, string $argument, bool $forceSecure = false, bool $encodeArgument = true) { + $scheme = !$forceSecure && empty($_SERVER['HTTPS']) ? 'http' : 'https'; + if($encodeArgument) + $argument = rawurlencode($argument); $url = sprintf($format, $scheme, $argument); $this->redirect($response, $request, $url); @@ -77,4 +78,59 @@ final class RedirectorRoutes implements IRouteHandler { public function redirectForumPost($response, $request, string $postId) { $this->redirectSimple($response, $request, $this->urls->getString('forum_post'), $postId); } + + #[HttpGet('/bsky/((did:[a-z0-9]+:[A-Za-z0-9.\-_:%]+)|(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])))')] + public function redirecyBluesky($response, $request, string $handle) { + $did = null; + + if(str_starts_with($handle, 'did:')) + $did = $handle; + else { + $timeout = ini_get('default_socket_timeout'); + try { + ini_set('default_socket_timeout', 3); + $records = dns_get_record(sprintf('_atproto.%s', $handle), DNS_TXT); + + + if(is_array($records)) + foreach($records as $record) + if(array_key_exists('txt', $record) && str_starts_with($record['txt'], 'did=')) { + $did = trim(substr(trim($record['txt']), 4)); + break; + } + } finally { + ini_set('default_socket_timeout', $timeout); + } + } + + $format = $this->urls->getString('bsky_profile', '%s://bsky.app/profile/%s'); + if(is_string($did)) { + $this->redirectSimple($response, $request, $format, $did, encodeArgument: false); + return; + } + + $handle = rawurlencode($handle); + echo << + +