diff --git a/README.md b/README.md
index c043a3c..1d2c9bc 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@
 ## Written in TypeScript as a Cloudflare Worker to scale, packed with more features and [best-in-class user privacy πŸ”’](#built-with-privacy-in-mind).
 
 ### Add `fx` before your `twitter.com` link to make it `fxtwitter.com`, OR
+
 ### Change `x.com` to `fixupx.com` in your link
 
 ### For Twitter links on Discord, send a Twitter link and type `s/e/p` to make `twittpr.com`.
diff --git a/src/api/status.ts b/src/api/status.ts
index 45d1960..e5057c8 100644
--- a/src/api/status.ts
+++ b/src/api/status.ts
@@ -40,8 +40,7 @@ const populateTweetProperties = async (
     id: apiUser.id,
     name: apiUser.name,
     screen_name: apiUser.screen_name,
-    avatar_url:
-      (apiUser.avatar_url || '').replace('_normal', '_200x200') || '',
+    avatar_url: (apiUser.avatar_url || '').replace('_normal', '_200x200') || '',
     avatar_color: '0000FF' /* colorFromPalette(
       tweet.user?.profile_image_extensions_media_color?.palette || []
     ),*/,
@@ -71,7 +70,7 @@ const populateTweetProperties = async (
 
   apiTweet.replying_to = tweet.legacy?.in_reply_to_screen_name || null;
   apiTweet.replying_to_status = tweet.legacy?.in_reply_to_status_id_str || null;
-  
+
   const mediaList = Array.from(
     tweet.legacy.extended_entities?.media || tweet.legacy.entities?.media || []
   );
@@ -107,9 +106,14 @@ const populateTweetProperties = async (
   console.log('note_tweet', JSON.stringify(tweet.note_tweet));
   const noteTweetText = tweet.note_tweet?.note_tweet_results?.result?.text;
   /* For now, don't include note tweets */
-  if (noteTweetText && mediaList.length <= 0 && tweet.legacy.entities?.urls?.length <= 0) {
+  if (
+    noteTweetText /*&& mediaList.length <= 0 && tweet.legacy.entities?.urls?.length <= 0*/
+  ) {
     console.log('We meet the conditions to use new note tweets');
-    apiTweet.text = unescapeText(noteTweetText);
+    apiTweet.text = unescapeText(linkFixer(tweet, noteTweetText));
+    apiTweet.is_note_tweet = true;
+  } else {
+    apiTweet.is_note_tweet = false;
   }
 
   /* Handle photos and mosaic if available */
@@ -129,7 +133,7 @@ const populateTweetProperties = async (
   }
 
   /* Populate a Twitter card */
-  
+
   if (tweet.card) {
     const card = renderCard(tweet.card);
     if (card.external_media) {
@@ -143,7 +147,11 @@ const populateTweetProperties = async (
   }
 
   /* If a language is specified in API or by user, let's try translating it! */
-  if (typeof language === 'string' && language.length === 2 && language !== tweet.legacy.lang) {
+  if (
+    typeof language === 'string' &&
+    language.length === 2 &&
+    language !== tweet.legacy.lang
+  ) {
     const translateAPI = await translateTweet(
       tweet,
       conversation.guestToken || '',
@@ -213,15 +221,15 @@ export const statusAPI = async (
   }
 
   // console.log(JSON.stringify(tweet))
-  
+
   if (tweet.__typename === 'TweetUnavailable') {
     if (tweet.reason === 'Protected') {
       writeDataPoint(event, language, wasMediaBlockedNSFW, 'PRIVATE_TWEET', flags);
       return { code: 401, message: 'PRIVATE_TWEET' };
-    // } else if (tweet.reason === 'NsfwLoggedOut') {
-    //   // API failure as elongator should have handled this
-    //   writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
-    //   return { code: 500, message: 'API_FAIL' };
+      // } else if (tweet.reason === 'NsfwLoggedOut') {
+      //   // API failure as elongator should have handled this
+      //   writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
+      //   return { code: 500, message: 'API_FAIL' };
     } else {
       // Api failure at parsing status
       writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
@@ -234,7 +242,7 @@ export const statusAPI = async (
     writeDataPoint(event, language, wasMediaBlockedNSFW, 'API_FAIL', flags);
     return { code: 500, message: 'API_FAIL' };
   }
-  
+
   /*
   if (tweet.retweeted_status_id_str) {
     tweet = conversation?.globalObjects?.tweets?.[tweet.retweeted_status_id_str] || {};
diff --git a/src/embed/status.ts b/src/embed/status.ts
index df6f17f..8a88a2f 100644
--- a/src/embed/status.ts
+++ b/src/embed/status.ts
@@ -6,6 +6,7 @@ import { getAuthorText } from '../helpers/author';
 import { statusAPI } from '../api/status';
 import { renderPhoto } from '../render/photo';
 import { renderVideo } from '../render/video';
+import { renderInstantView } from '../render/instantview';
 
 export const returnError = (error: string): StatusResponse => {
   return {
@@ -34,6 +35,12 @@ export const handleStatus = async (
 
   const api = await statusAPI(status, language, event as FetchEvent, flags);
   const tweet = api?.tweet as APITweet;
+  
+  const isTelegram = (userAgent || '').indexOf('Telegram') > -1;
+  /* Should sensitive posts be allowed Instant View? */
+  const useIV = isTelegram /*&& !tweet.possibly_sensitive*/ && !flags?.direct && !flags?.api && (tweet.media?.mosaic || tweet.is_note_tweet);
+
+  let ivbody = '';
 
   /* Catch this request if it's an API response */
   if (flags?.api) {
@@ -120,12 +127,21 @@ export const handleStatus = async (
      it will gracefully redirect to the destination instead of just seeing a blank screen.
 
      Telegram is dumb and it just gets stuck if this is included, so we never include it for Telegram UAs. */
-  if (userAgent?.indexOf('Telegram') === -1) {
+  if (!isTelegram) {
     headers.push(
       `<meta http-equiv="refresh" content="0;url=https://twitter.com/${tweet.author.screen_name}/status/${tweet.id}"/>`
     );
   }
 
+  if (useIV) {
+    const instructions = renderInstantView({ tweet: tweet, text: newText });
+    headers.push(...instructions.addHeaders);
+    if (instructions.authorText) {
+      authorText = instructions.authorText;
+    }
+    ivbody = instructions.text || '';
+  }
+
   /* This Tweet has a translation attached to it, so we'll render it. */
   if (tweet.translation) {
     const { translation } = tweet;
@@ -240,7 +256,7 @@ export const handleStatus = async (
     let str = '';
 
     /* Telegram Embeds are smaller, so we use a smaller bar to compensate */
-    if (userAgent?.indexOf('Telegram') !== -1) {
+    if (isTelegram) {
       barLength = 24;
     }
 
@@ -273,16 +289,21 @@ export const handleStatus = async (
 
   /* If we have no media to display, instead we'll display the user profile picture in the embed */
   if (!tweet.media?.videos && !tweet.media?.photos && !flags?.textOnly) {
-    headers.push(
-      /* Use a slightly higher resolution image for profile pics */
-      `<meta property="og:image" content="${tweet.author.avatar_url?.replace(
-        '_normal',
-        '_200x200'
-      )}"/>`,
-      `<meta property="twitter:image" content="0"/>`
-    );
+    const avatar = tweet.author.avatar_url?.replace('_200x200', '_normal');
+    if (!useIV) {
+      headers.push(
+        /* Use a slightly higher resolution image for profile pics */
+        `<meta property="og:image" content="${avatar}"/>`,
+        `<meta property="twitter:image" content="0"/>`
+      );
+    } else {
+      headers.push(
+        /* Use a slightly higher resolution image for profile pics */
+        `<meta property="twitter:image" content="${avatar}"/>`
+      );
+    }
   }
-  
+
   if (!flags?.isXDomain) {
     siteName = Strings.X_DOMAIN_NOTICE;
   }
@@ -291,11 +312,22 @@ export const handleStatus = async (
   if (flags?.deprecated) {
     siteName = Strings.DEPRECATED_DOMAIN_NOTICE;
   }
+  /* For supporting Telegram IV, we have to replace newlines with <br> within the og:description <meta> tag because of its weird (undocumented?) behavior.
+     If you don't use IV, it uses newlines just fine. Just like Discord and others. But with IV, suddenly newlines don't actually break the line anymore.
+
+     This is incredibly stupid, and you'd think this weird behavior would not be the case. You'd also think embedding a <br> inside the quotes inside
+     a meta tag shouldn't work, because that's stupid, but alas it does.
+     
+     A possible explanation for this weird behavior is due to the Medium template we are forced to use because Telegram IV is not an open platform
+     and we have to pretend to be Medium in order to get working IV, but haven't figured if the template is causing issues.  */
+  const text = useIV
+    ? sanitizeText(newText).replace(/\n/g, '<br>')
+    : sanitizeText(newText);
 
   /* Push basic headers relating to author, Tweet text, and site name */
   headers.push(
     `<meta property="og:title" content="${tweet.author.name} (@${tweet.author.screen_name})"/>`,
-    `<meta property="og:description" content="${sanitizeText(newText).replace(/\n/g, '<br>')}"/>`,
+    `<meta property="og:description" content="${text}"/>`,
     `<meta property="og:site_name" content="${siteName}"/>`
   );
 
@@ -317,9 +349,9 @@ export const handleStatus = async (
       authorText.substring(0, 200)
     )}${flags?.deprecated ? '&deprecated=true' : ''}&status=${encodeURIComponent(
       status
-    )}&author=${encodeURIComponent(
-      tweet.author?.screen_name || ''
-    )}&useXbranding=${flags?.isXDomain ? 'true' : 'false'}" type="application/json+oembed" title="${tweet.author.name}">`
+    )}&author=${encodeURIComponent(tweet.author?.screen_name || '')}&useXbranding=${
+      flags?.isXDomain ? 'true' : 'false'
+    }" type="application/json+oembed" title="${tweet.author.name}">`
   );
 
   /* When dealing with a Tweet of unknown lang, fall back to en */
@@ -329,7 +361,8 @@ export const handleStatus = async (
   return {
     text: Strings.BASE_HTML.format({
       lang: `lang="${lang}"`,
-      headers: headers.join('')
+      headers: headers.join(''),
+      body: ivbody
     }),
     cacheControl: cacheControl
   };
diff --git a/src/fetch.ts b/src/fetch.ts
index 2f87c3c..048211d 100644
--- a/src/fetch.ts
+++ b/src/fetch.ts
@@ -5,7 +5,7 @@ import { isGraphQLTweet } from './utils/graphql';
 const API_ATTEMPTS = 3;
 
 function generateCSRFToken() {
-  const randomBytes = new Uint8Array(160/2);
+  const randomBytes = new Uint8Array(160 / 2);
   crypto.getRandomValues(randomBytes);
   return Array.from(randomBytes, byte => byte.toString(16).padStart(2, '0')).join('');
 }
@@ -134,7 +134,7 @@ export const twitterFetch = async (
           headers: headers
         });
       }
-      
+
       response = await apiRequest?.json();
     } catch (e: unknown) {
       /* We'll usually only hit this if we get an invalid response from Twitter.
@@ -197,28 +197,34 @@ export const fetchConversation = async (
     `${
       Constants.TWITTER_ROOT
     }/i/api/graphql/2ICDjqPd81tulZcYrtpTuQ/TweetResultByRestId?variables=${encodeURIComponent(
-      JSON.stringify({"tweetId": status,"withCommunity":false,"includePromotedContent":false,"withVoice":false})
+      JSON.stringify({
+        tweetId: status,
+        withCommunity: false,
+        includePromotedContent: false,
+        withVoice: false
+      })
     )}&features=${encodeURIComponent(
       JSON.stringify({
-        creator_subscriptions_tweet_preview_api_enabled:true,
-        tweetypie_unmention_optimization_enabled:true,
-        responsive_web_edit_tweet_api_enabled:true,
-        graphql_is_translatable_rweb_tweet_is_translatable_enabled:true,
-        view_counts_everywhere_api_enabled:true,
-        longform_notetweets_consumption_enabled:true,
-        responsive_web_twitter_article_tweet_consumption_enabled:false,
-        tweet_awards_web_tipping_enabled:false,
-        freedom_of_speech_not_reach_fetch_enabled:true,
-        standardized_nudges_misinfo:true,
-        tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled:true,
-        longform_notetweets_rich_text_read_enabled:true,
-        longform_notetweets_inline_media_enabled:true,
-        responsive_web_graphql_exclude_directive_enabled:true,
-        verified_phone_label_enabled:false,
-        responsive_web_media_download_video_enabled:false,
-        responsive_web_graphql_skip_user_profile_image_extensions_enabled:false,
-        responsive_web_graphql_timeline_navigation_enabled:true,
-        responsive_web_enhance_cards_enabled:false})
+        creator_subscriptions_tweet_preview_api_enabled: true,
+        tweetypie_unmention_optimization_enabled: true,
+        responsive_web_edit_tweet_api_enabled: true,
+        graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
+        view_counts_everywhere_api_enabled: true,
+        longform_notetweets_consumption_enabled: true,
+        responsive_web_twitter_article_tweet_consumption_enabled: false,
+        tweet_awards_web_tipping_enabled: false,
+        freedom_of_speech_not_reach_fetch_enabled: true,
+        standardized_nudges_misinfo: true,
+        tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
+        longform_notetweets_rich_text_read_enabled: true,
+        longform_notetweets_inline_media_enabled: true,
+        responsive_web_graphql_exclude_directive_enabled: true,
+        verified_phone_label_enabled: false,
+        responsive_web_media_download_video_enabled: false,
+        responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
+        responsive_web_graphql_timeline_navigation_enabled: true,
+        responsive_web_enhance_cards_enabled: false
+      })
     )}&fieldToggles=${encodeURIComponent(
       JSON.stringify({
         // TODO Figure out what this property does
@@ -244,7 +250,7 @@ export const fetchConversation = async (
         return true;
       }
       // Final clause for checking if it's valid is if there's errors
-      return Array.isArray(conversation.errors)
+      return Array.isArray(conversation.errors);
     }
   )) as TweetResultsByRestIdResult;
 };
diff --git a/src/helpers/author.ts b/src/helpers/author.ts
index e2c55fe..bf0a4a8 100644
--- a/src/helpers/author.ts
+++ b/src/helpers/author.ts
@@ -24,3 +24,28 @@ export const getAuthorText = (tweet: APITweet): string | null => {
 
   return null;
 };
+
+/* The embed "author" text we populate with replies, retweets, and likes unless it's a video */
+export const getSocialTextIV = (tweet: APITweet): string | null => {
+  /* Build out reply, retweet, like counts */
+  if (tweet.likes > 0 || tweet.retweets > 0 || tweet.replies > 0) {
+    let authorText = '';
+    if (tweet.replies > 0) {
+      authorText += `πŸ’¬ ${formatNumber(tweet.replies)} `;
+    }
+    if (tweet.retweets > 0) {
+      authorText += `πŸ” ${formatNumber(tweet.retweets)} `;
+    }
+    if (tweet.likes > 0) {
+      authorText += `❀️ ${formatNumber(tweet.likes)} `;
+    }
+    if (tweet.views && tweet.views > 0) {
+      authorText += `πŸ‘οΈ ${formatNumber(tweet.views)} `;
+    }
+    authorText = authorText.trim();
+
+    return authorText;
+  }
+
+  return null;
+};
diff --git a/src/helpers/card.ts b/src/helpers/card.ts
index c8e4740..0a4dc69 100644
--- a/src/helpers/card.ts
+++ b/src/helpers/card.ts
@@ -6,7 +6,10 @@ export const renderCard = (
 ): { poll?: APIPoll; external_media?: APIExternalMedia } => {
   // We convert the binding_values array into an object with the legacy format
   // TODO Clean this up
-  const binding_values: Record<string, { string_value?: string; boolean_value?: boolean }> = {};
+  const binding_values: Record<
+    string,
+    { string_value?: string; boolean_value?: boolean }
+  > = {};
   if (Array.isArray(card.legacy.binding_values)) {
     card.legacy.binding_values.forEach(value => {
       if (value.key && value.value) {
@@ -14,7 +17,6 @@ export const renderCard = (
       }
     });
   }
-      
 
   console.log('rendering card');
 
@@ -56,7 +58,10 @@ export const renderCard = (
         });
 
       return { poll: poll };
-    } else if (typeof binding_values.player_url !== 'undefined' && binding_values.player_url.string_value) {
+    } else if (
+      typeof binding_values.player_url !== 'undefined' &&
+      binding_values.player_url.string_value
+    ) {
       /* Oh good, a non-Twitter video URL! This enables YouTube embeds and stuff to just work */
       return {
         external_media: {
diff --git a/src/helpers/linkFixer.ts b/src/helpers/linkFixer.ts
index bef4a7d..f5b8e54 100644
--- a/src/helpers/linkFixer.ts
+++ b/src/helpers/linkFixer.ts
@@ -1,8 +1,8 @@
 /* Helps replace t.co links with their originals */
 export const linkFixer = (tweet: GraphQLTweet, text: string): string => {
   console.log('got entites', {
-    entities: tweet.legacy.entities,
-  })
+    entities: tweet.legacy.entities
+  });
   if (Array.isArray(tweet.legacy.entities?.urls) && tweet.legacy.entities.urls.length) {
     tweet.legacy.entities.urls.forEach((url: TcoExpansion) => {
       let newURL = url.expanded_url;
diff --git a/src/helpers/media.ts b/src/helpers/media.ts
index 24e6b9b..b1e4b84 100644
--- a/src/helpers/media.ts
+++ b/src/helpers/media.ts
@@ -4,8 +4,8 @@ export const processMedia = (media: TweetMedia): APIPhoto | APIVideo | null => {
     return {
       type: 'photo',
       url: media.media_url_https,
-      width: media.original_info.width,
-      height: media.original_info.height,
+      width: media.original_info?.width,
+      height: media.original_info?.height,
       altText: media.ext_alt_text || ''
     };
   } else if (media.type === 'video' || media.type === 'animated_gif') {
@@ -17,8 +17,8 @@ export const processMedia = (media: TweetMedia): APIPhoto | APIVideo | null => {
       url: bestVariant?.url || '',
       thumbnail_url: media.media_url_https,
       duration: (media.video_info?.duration_millis || 0) / 1000,
-      width: media.original_info.width,
-      height: media.original_info.height,
+      width: media.original_info?.width,
+      height: media.original_info?.height,
       format: bestVariant?.content_type || '',
       type: media.type === 'animated_gif' ? 'gif' : 'video'
     };
diff --git a/src/render/instantview.ts b/src/render/instantview.ts
new file mode 100644
index 0000000..b67cc46
--- /dev/null
+++ b/src/render/instantview.ts
@@ -0,0 +1,108 @@
+import { Constants } from "../constants";
+import { getSocialTextIV } from "../helpers/author";
+import { sanitizeText } from "../helpers/utils";
+
+const populateUserLinks = (tweet: APITweet, text: string): string => {
+  /* TODO: Maybe we can add username splices to our API so only genuinely valid users are linked? */
+  text.match(/@(\w{1,15})/g)?.forEach((match) => {
+    const username = match.replace('@', '');
+    text = text.replace(
+      match,
+      `<a href="${Constants.TWITTER_ROOT}/${username}" target="_blank" rel="noopener noreferrer">${match}</a>`
+    );
+  });
+  return text;
+}
+
+const generateTweetMedia = (tweet: APITweet): string => {
+  let media = '';
+  if (tweet.media?.all?.length) {
+    tweet.media.all.forEach((mediaItem) => {
+      switch(mediaItem.type) {
+        case 'photo':
+          media += `<img src="${mediaItem.url}" alt="${tweet.author.name}'s photo" />`;
+          break;
+        case 'video':
+          media += `<video src="${mediaItem.url}" alt="${tweet.author.name}'s video" />`;
+          break;
+        case 'gif':
+          media += `<video src="${mediaItem.url}" alt="${tweet.author.name}'s gif" />`;
+          break;
+      }
+    });
+  }
+  return media;
+}
+
+// const formatDateTime = (date: Date): string => {
+//   const yyyy = date.getFullYear();
+//   const mm = String(date.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed
+//   const dd = String(date.getDate()).padStart(2, '0');
+//   const hh = String(date.getHours()).padStart(2, '0');
+//   const min = String(date.getMinutes()).padStart(2, '0');
+//   return `${hh}:${min} - ${yyyy}/${mm}/${dd}`;
+// }
+
+const htmlifyLinks = (input: string): string => {
+  const urlPattern = /\bhttps?:\/\/\S+/g;
+  return input.replace(urlPattern, (url) => {
+      return `<a href="${url}">${url}</a>`;
+  });
+}
+
+const htmlifyHashtags = (input: string): string => {
+  const hashtagPattern = /#([a-zA-Z_]\w*)/g;
+  return input.replace(hashtagPattern, (match, hashtag) => {
+      const encodedHashtag = encodeURIComponent(hashtag);
+      return `<a href="https://twitter.com/hashtag/${encodedHashtag}?src=hashtag_click">${match}</a>`;
+  });
+}
+
+export const renderInstantView = (properties: RenderProperties): ResponseInstructions => {
+  console.log('Generating Instant View (placeholder)...');
+  const { tweet } = properties;
+  const instructions: ResponseInstructions = { addHeaders: [] };
+  /* Use ISO date for Medium template */
+  const postDate = new Date(tweet.created_at).toISOString();
+
+  /* Include Instant-View related headers. This is an unfinished project. Thanks to https://nikstar.me/post/instant-view/ for the help! */
+  instructions.addHeaders = [
+    `<meta property="al:android:app_name" content="Medium"/>`,
+    `<meta property="article:published_time" content="${postDate}"/>`
+  ];
+
+  let text = sanitizeText(tweet.text).replace(/\n/g, '<br>');
+  text = htmlifyLinks(text);
+  text = htmlifyHashtags(text);
+  text = populateUserLinks(tweet, text);
+
+  instructions.text = `
+  <section class="section-backgroundImage">
+    <figure class="graf--layoutFillWidth"></figure>
+  </section>
+  <section class="section--first" style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 64px;">
+    If you can see this, your browser is doing something weird with your user agent. <a href="${tweet.url}">View original post</a>
+  </section>
+  <article>
+  <h1>${tweet.author.name} (@${tweet.author.screen_name})</h1>
+  <p>Instant View (✨ Beta) - <a href="${tweet.url}">View original</a></p> 
+
+  <!--blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr"> <a href="${tweet.url}">_</a></blockquote-->
+
+  <!-- Embed profile picture, display name, and screen name in table -->
+  <table>
+    <img src="${tweet.author.avatar_url}" alt="${tweet.author.name}'s profile picture" />
+    <h2>${tweet.author.name}</h2>
+    <p>@${tweet.author.screen_name}</p>
+    <p>${getSocialTextIV(tweet)}</p>
+  </table>
+
+  <!-- Embed Tweet text -->
+  <p>${text}</p>
+  ${generateTweetMedia(tweet)} 
+  <a href="${tweet.url}">View original</a>
+</article>
+`;
+
+  return instructions;
+};
diff --git a/src/server.ts b/src/server.ts
index 8230585..53d6bc9 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -313,7 +313,9 @@ router.get('/owoembed', async (request: IRequest) => {
     provider_name:
       searchParams.get('deprecated') === 'true'
         ? Strings.DEPRECATED_DOMAIN_NOTICE_DISCORD
-        : (useXbranding ? name : Strings.X_DOMAIN_NOTICE),
+        : useXbranding
+        ? name
+        : Strings.X_DOMAIN_NOTICE,
     provider_url: url,
     title: Strings.DEFAULT_AUTHOR_TEXT,
     type: 'link',
@@ -394,7 +396,7 @@ export const cacheWrapper = async (
   ) {
     return new Response(Strings.TWITFIX_API_SUNSET, {
       headers: Constants.RESPONSE_HEADERS,
-      status: 404
+      status: 410
     });
   }
 
diff --git a/src/strings.ts b/src/strings.ts
index 591a4ba..729f60d 100644
--- a/src/strings.ts
+++ b/src/strings.ts
@@ -30,7 +30,7 @@ export const Strings = {
 β–ˆβ–ˆβ–ˆ   A better way to embed Tweets on Discord, Telegram, and more.
 β–ˆβ–ˆβ–ˆ   Worker build ${RELEASE_NAME}
 
---><head>{headers}</head><body></body></html>`,
+--><head>{headers}</head><body>{body}</body></html>`,
   ERROR_HTML: `<!DOCTYPE html>
   <html lang="en">
     <head>
@@ -145,7 +145,8 @@ This is caused by Twitter API downtime or a new bug. Try again in a little while
   PLURAL_SECONDS_LEFT: 'seconds left',
   FINAL_POLL_RESULTS: 'Final results',
 
-  ERROR_API_FAIL: 'Tweet failed to load due to an API error. This is most common with NSFW Tweets as Twitter / X currently blocks us from fetching them. We\'re still working on a fix for that.πŸ™',
+  ERROR_API_FAIL:
+    "Tweet failed to load due to an API error. This is most common with NSFW Tweets as Twitter / X currently blocks us from fetching them. We're still working on a fix for that.πŸ™",
   ERROR_PRIVATE: `Sorry, we can't embed this Tweet because the user is private or suspended :(`,
   ERROR_TWEET_NOT_FOUND: `Sorry, that Tweet doesn't exist :(`,
   ERROR_USER_NOT_FOUND: `Sorry, that user doesn't exist :(`,
diff --git a/src/types/twitterTypes.d.ts b/src/types/twitterTypes.d.ts
index 49932da..c84dc2d 100644
--- a/src/types/twitterTypes.d.ts
+++ b/src/types/twitterTypes.d.ts
@@ -310,19 +310,19 @@ type GraphQLTweet = {
   result: GraphQLTweet;
   __typename: 'Tweet';
   rest_id: string; // "1674824189176590336",
-  has_birdwatch_notes: false,
+  has_birdwatch_notes: false;
   core: {
     user_results: {
       result: GraphQLUser;
-    }
-  }
-  edit_control: unknown,
-  edit_perspective: unknown,
-  is_translatable: false,
+    };
+  };
+  edit_control: unknown;
+  edit_perspective: unknown;
+  is_translatable: false;
   views: {
     count: string; // "562"
     state: string; // "EnabledWithCount"
-  }
+  };
   source: string; // "<a href=\"https://mobile.twitter.com\" rel=\"nofollow\">Twitter Web App</a>"
   quoted_status_result?: GraphQLTweet;
   legacy: {
@@ -356,45 +356,54 @@ type GraphQLTweet = {
         indices: [number, number]; // [number, number]
         media_url_https: string; // "https://pbs.twimg.com/media/FAKESCREENSHOT.jpg" With videos appears to be the thumbnail
         type: string; // "photo" Seems to be photo even with videos
-      }[]
+      }[];
       user_mentions: unknown[];
       urls: TcoExpansion[];
       hashtags: unknown[];
       symbols: unknown[];
-    }
+    };
     extended_entities: {
-      media: TweetMedia[]
-    }
-  }
+      media: TweetMedia[];
+    };
+  };
   note_tweet: {
     is_expandable: boolean;
     entity_set: {
       hashtags: unknown[];
       urls: unknown[];
       user_mentions: unknown[];
-    },
+    };
     note_tweet_results: {
       result: {
         text: string;
-      }
-    }
+      };
+    };
   };
   card: {
     rest_id: string; // "card://1674824189176590336",
     legacy: {
       binding_values: {
-        key: `choice${1|2|3|4}_label`|'counts_are_final'|`choice${1|2|3|4}_count`|'last_updated_datetime_utc'|'duration_minutes'|'api'|'card_url'
-        value: {
-          string_value: string; // "Option text"
-          type: 'STRING'
-        }|{
-          boolean_value: boolean; // true
-          type: 'BOOLEAN'
-        }
-      }[]
-    }
-  }
-}
+        key:
+          | `choice${1 | 2 | 3 | 4}_label`
+          | 'counts_are_final'
+          | `choice${1 | 2 | 3 | 4}_count`
+          | 'last_updated_datetime_utc'
+          | 'duration_minutes'
+          | 'api'
+          | 'card_url';
+        value:
+          | {
+              string_value: string; // "Option text"
+              type: 'STRING';
+            }
+          | {
+              boolean_value: boolean; // true
+              type: 'BOOLEAN';
+            };
+      }[];
+    };
+  };
+};
 type TweetTombstone = {
   __typename: 'TweetTombstone';
   tombstone: {
@@ -403,82 +412,91 @@ type TweetTombstone = {
       rtl: boolean; // false;
       text: string; // "You’re unable to view this Tweet because this account owner limits who can view their Tweets. Learn more"
       entities: unknown[];
-    }
-  }
-}
+    };
+  };
+};
 type GraphQLTimelineTweetEntry = {
   /** The entryID contains the tweet ID */
   entryId: `tweet-${number}`; // "tweet-1674824189176590336"
   sortIndex: string;
   content: {
-    entryType: 'TimelineTimelineItem',
-    __typename: 'TimelineTimelineItem',
+    entryType: 'TimelineTimelineItem';
+    __typename: 'TimelineTimelineItem';
     itemContent: {
-      item: 'TimelineTweet',
-      __typename: 'TimelineTweet',
+      item: 'TimelineTweet';
+      __typename: 'TimelineTweet';
       tweet_results: {
-        result: GraphQLTweet|TweetTombstone;
-      }
-    }
-  }
-}
+        result: GraphQLTweet | TweetTombstone;
+      };
+    };
+  };
+};
 type GraphQLConversationThread = {
   entryId: `conversationthread-${number}`; // "conversationthread-1674824189176590336"
   sortIndex: string;
-}
+};
 
-type GraphQLTimelineEntry = GraphQLTimelineTweetEntry|GraphQLConversationThread|unknown;
+type GraphQLTimelineEntry =
+  | GraphQLTimelineTweetEntry
+  | GraphQLConversationThread
+  | unknown;
 
-type V2ThreadInstruction = TimeLineAddEntriesInstruction | TimeLineTerminateTimelineInstruction;
+type V2ThreadInstruction =
+  | TimeLineAddEntriesInstruction
+  | TimeLineTerminateTimelineInstruction;
 
 type TimeLineAddEntriesInstruction = {
   type: 'TimelineAddEntries';
   entries: GraphQLTimelineEntry[];
-}
+};
 
 type TimeLineTerminateTimelineInstruction = {
   type: 'TimelineTerminateTimeline';
   direction: 'Top';
-}
+};
 type GraphQLTweetNotFoundResponse = {
-  errors: [{
-    message: string; // "_Missing: No status found with that ID"
-    locations: unknown[];
-    path: string[]; // ["threaded_conversation_with_injections_v2"]
-    extensions: {
-      name: string; // "GenericError"
-      source: string; // "Server"
+  errors: [
+    {
+      message: string; // "_Missing: No status found with that ID"
+      locations: unknown[];
+      path: string[]; // ["threaded_conversation_with_injections_v2"]
+      extensions: {
+        name: string; // "GenericError"
+        source: string; // "Server"
+        code: number; // 144
+        kind: string; // "NonFatal"
+        tracing: {
+          trace_id: string; // "2e39ff747de237db"
+        };
+      };
       code: number; // 144
       kind: string; // "NonFatal"
+      name: string; // "GenericError"
+      source: string; // "Server"
       tracing: {
         trace_id: string; // "2e39ff747de237db"
-      }
+      };
     }
-    code: number; // 144
-    kind: string; // "NonFatal"
-    name: string; // "GenericError"
-    source: string; // "Server"
-    tracing: {
-      trace_id: string; // "2e39ff747de237db"
-    }
-  }]
+  ];
   data: Record<string, never>;
-}
+};
 type GraphQLTweetFoundResponse = {
   data: {
     threaded_conversation_with_injections_v2: {
-      instructions: V2ThreadInstruction[]
-    }
-  }
-}
+      instructions: V2ThreadInstruction[];
+    };
+  };
+};
 type TweetResultsByRestIdResult = {
   errors?: unknown[];
   data?: {
     tweetResult?: {
-      result?: {
-        __typename: 'TweetUnavailable';
-        reason: 'NsfwLoggedOut'|'Protected';
-      }|GraphQLTweet
-    }
-  }
-}
\ No newline at end of file
+      result?:
+        | {
+            __typename: 'TweetUnavailable';
+            reason: 'NsfwLoggedOut' | 'Protected';
+          }
+        | GraphQLTweet;
+    };
+  };
+};
diff --git a/src/types/types.d.ts b/src/types/types.d.ts
index 05a5857..69ffc15 100644
--- a/src/types/types.d.ts
+++ b/src/types/types.d.ts
@@ -172,6 +172,8 @@ interface APITweet {
 
   source: string;
 
+  is_note_tweet: boolean;
+
   twitter_card: 'tweet' | 'summary' | 'summary_large_image' | 'player';
 }
 
diff --git a/src/utils/graphql.ts b/src/utils/graphql.ts
index a6b23cd..6ee85ee 100644
--- a/src/utils/graphql.ts
+++ b/src/utils/graphql.ts
@@ -1,7 +1,22 @@
-export const isGraphQLTweetNotFoundResponse = (response: unknown): response is GraphQLTweetNotFoundResponse => {
-    return typeof response === 'object' && response !== null && 'errors' in response && Array.isArray(response.errors) && response.errors.length > 0 && 'message' in response.errors[0] && response.errors[0].message === '_Missing: No status found with that ID';
+export const isGraphQLTweetNotFoundResponse = (
+  response: unknown
+): response is GraphQLTweetNotFoundResponse => {
+  return (
+    typeof response === 'object' &&
+    response !== null &&
+    'errors' in response &&
+    Array.isArray(response.errors) &&
+    response.errors.length > 0 &&
+    'message' in response.errors[0] &&
+    response.errors[0].message === '_Missing: No status found with that ID'
+  );
 };
 
 export const isGraphQLTweet = (response: unknown): response is GraphQLTweet => {
-    return typeof response === 'object' && response !== null && '__typename' in response && response.__typename === 'Tweet';
-}
\ No newline at end of file
+  return (
+    typeof response === 'object' &&
+    response !== null &&
+    '__typename' in response &&
+    response.__typename === 'Tweet'
+  );
+};
diff --git a/test/index.test.ts b/test/index.test.ts
index 1427155..655218a 100644
--- a/test/index.test.ts
+++ b/test/index.test.ts
@@ -284,4 +284,4 @@ test('API fetch user that does not exist', async () => {
   expect(response.code).toEqual(404);
   expect(response.message).toEqual('User not found');
   expect(response.user).toBeUndefined();
-});
\ No newline at end of file
+});