From 7019c09e5be5e00142d96271d3f5d3f72c363b79 Mon Sep 17 00:00:00 2001
From: Lim Chee Aun <cheeaun@gmail.com>
Date: Sat, 25 Nov 2023 21:26:27 +0800
Subject: [PATCH] Better resolving of links

---
 src/pages/http-route.jsx             | 63 ++++++++++++++++++++++------
 src/utils/get-instance-status-url.js | 39 ++++++++++++-----
 2 files changed, 79 insertions(+), 23 deletions(-)

diff --git a/src/pages/http-route.jsx b/src/pages/http-route.jsx
index 5729e9c2..f3189f1a 100644
--- a/src/pages/http-route.jsx
+++ b/src/pages/http-route.jsx
@@ -1,34 +1,73 @@
-import { useLayoutEffect } from 'preact/hooks';
+import { useLayoutEffect, useState } from 'preact/hooks';
 import { useLocation } from 'react-router-dom';
 
 import Link from '../components/link';
-import getInstanceStatusURL from '../utils/get-instance-status-url';
+import Loader from '../components/loader';
+import { api } from '../utils/api';
+import getInstanceStatusURL, {
+  getInstanceStatusObject,
+} from '../utils/get-instance-status-url';
 
 export default function HttpRoute() {
   const location = useLocation();
   const url = location.pathname.replace(/^\//, '');
-  const statusURL = getInstanceStatusURL(url);
+  const statusObject = getInstanceStatusObject(url);
+  // const statusURL = getInstanceStatusURL(url);
+  const statusURL = statusObject?.instance
+    ? `/${statusObject.instance}/s/${statusObject.id}`
+    : null;
+  const [uiState, setUIState] = useState('loading');
 
   useLayoutEffect(() => {
-    if (statusURL) {
-      setTimeout(() => {
-        window.location.hash = statusURL + '?view=full';
-      }, 300);
-    }
+    setUIState('loading');
+    (async () => {
+      const { instance, id } = statusObject;
+      const { masto } = api({ instance });
+
+      // Check if status returns 200
+      try {
+        const status = await masto.v1.statuses.$select(id).fetch();
+        if (status) {
+          window.location.hash = statusURL + '?view=full';
+          return;
+        }
+      } catch (e) {}
+
+      // Fallback to search
+      {
+        const { masto: currentMasto, instance: currentInstance } = api();
+        const result = await currentMasto.v2.search.fetch({
+          q: url,
+          type: 'statuses',
+          limit: 1,
+          resolve: true,
+        });
+        if (result.statuses.length) {
+          const status = result.statuses[0];
+          window.location.hash = `/${currentInstance}/s/${status.id}?view=full`;
+        } else {
+          // Fallback to original URL, which will probably show error
+          window.location.hash = statusURL + '?view=full';
+        }
+      }
+    })();
   }, [statusURL]);
 
   return (
     <div class="ui-state" tabIndex="-1">
-      {statusURL ? (
+      {uiState === 'loading' ? (
         <>
-          <h2>Redirecting…</h2>
+          <Loader abrupt />
+          <h2>Resolving…</h2>
           <p>
-            <a href={`#${statusURL}?view=full`}>{statusURL}</a>
+            <a href={url} target="_blank" rel="noopener noreferrer">
+              {url}
+            </a>
           </p>
         </>
       ) : (
         <>
-          <h2>Unable to process URL</h2>
+          <h2>Unable to resolve URL</h2>
           <p>
             <a href={url} target="_blank" rel="noopener noreferrer">
               {url}
diff --git a/src/utils/get-instance-status-url.js b/src/utils/get-instance-status-url.js
index 28c810ff..ebbcaa94 100644
--- a/src/utils/get-instance-status-url.js
+++ b/src/utils/get-instance-status-url.js
@@ -1,19 +1,36 @@
-export const statusRegex = /\/@([^@\/]+)@?([^\/]+)?\/([^\/]+)\/?$/i;
-export const statusNoteRegex = /\/notes\/([^\/]+)\/?$/i;
-function getInstanceStatusURL(url) {
+// export const statusRegex = /\/@([^@\/]+)@?([^\/]+)?\/([^\/]+)\/?$/i;
+// export const statusNoteRegex = /\/notes\/([^\/]+)\/?$/i;
+
+const statusPostRegexes = [
+  /^\/@[^@\/]+\/(?:statuses|posts)\/([^\/]+)/i, // GoToSocial, Takahe
+  /\/notes\/([^\/]+)/i, // Misskey, Firefish
+  /^\/(?:notice|objects)\/([a-z0-9-]+)/i, // Pleroma
+  /\/@[^@\/]+@?[^\/]+?\/([^\/]+)/i, // Mastodon
+];
+
+export function getInstanceStatusObject(url) {
   // Regex /:username/:id, where username = @username or @username@domain, id = anything
   const { hostname, pathname } = new URL(url);
-  const [, username, domain, id] = pathname.match(statusRegex) || [];
-
-  if (id) {
-    return `/${hostname}/s/${id}`;
+  // const [, username, domain, id] = pathname.match(statusRegex) || [];
+  for (const regex of statusPostRegexes) {
+    const [, id] = pathname.match(regex) || [];
+    console.log(pathname, regex, id);
+    if (id) {
+      return {
+        instance: hostname,
+        id,
+      };
+    }
   }
+  return null;
+}
 
-  const [, noteId] = pathname.match(statusNoteRegex) || [];
-
-  if (noteId) {
-    return `/${hostname}/s/${noteId}`;
+function getInstanceStatusURL(url) {
+  const { instance, id } = getInstanceStatusObject(url);
+  if (instance && id) {
+    return `/${instance}/s/${id}`;
   }
+  return null;
 }
 
 export default getInstanceStatusURL;