fedimbed: much needed cleanup and reply context
This commit is contained in:
		
							parent
							
								
									d39bf5f2a4
								
							
						
					
					
						commit
						9fba9d2c9e
					
				
					 1 changed files with 279 additions and 182 deletions
				
			
		| 
						 | 
				
			
			@ -122,6 +122,14 @@ async function resolvePlatform(url) {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function normalizePlatform(platform) {
 | 
			
		||||
  return platform
 | 
			
		||||
    .replace("gotosocial", "GoToSocial")
 | 
			
		||||
    .replace("birdsitelive", '"Twitter" (BirdsiteLive)')
 | 
			
		||||
    .replace(/^(.)/, (_, c) => c.toUpperCase())
 | 
			
		||||
    .replace("Cohost", "cohost");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const keyId = "https://hf.c7.pm/actor#main-key";
 | 
			
		||||
const privKey = fs.readFileSync(require.resolve("#root/priv/private.pem"));
 | 
			
		||||
async function signedFetch(url, options) {
 | 
			
		||||
| 
						 | 
				
			
			@ -406,7 +414,7 @@ async function bluesky(msg, url, spoiler = false) {
 | 
			
		|||
  if (data.thread.parent) {
 | 
			
		||||
    const reply = data.thread.parent.post;
 | 
			
		||||
    mainEmbed.author = {
 | 
			
		||||
      name: `Replying to: ${reply.author.displayName} (${reply.author.handle})`,
 | 
			
		||||
      name: `Replying to: ${reply.author.displayName} (@${reply.author.handle})`,
 | 
			
		||||
      icon_url: "https://cdn.discordapp.com/emojis/1308640078825787412.png",
 | 
			
		||||
      url: `https://bsky.app/profile/${reply.author.handle}/post/${reply.uri.substring(
 | 
			
		||||
        reply.uri.lastIndexOf("/") + 1
 | 
			
		||||
| 
						 | 
				
			
			@ -570,104 +578,53 @@ async function bluesky(msg, url, spoiler = false) {
 | 
			
		|||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		||||
  let canFedi = await hasFlag(msg.guildID, "fedimbed");
 | 
			
		||||
  let canBsky = await hasFlag(msg.guildID, "bskyEmbeds");
 | 
			
		||||
async function fetchPost(url, platform, forceMastoAPI = false) {
 | 
			
		||||
  let urlObj = new URL(url);
 | 
			
		||||
  let postData;
 | 
			
		||||
 | 
			
		||||
  if (command === true) {
 | 
			
		||||
    canFedi = true;
 | 
			
		||||
    canBsky = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let invalidUrl = false;
 | 
			
		||||
  let urlObj;
 | 
			
		||||
  try {
 | 
			
		||||
    urlObj = new URL(url);
 | 
			
		||||
  } catch {
 | 
			
		||||
    invalidUrl = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (invalidUrl) return {};
 | 
			
		||||
 | 
			
		||||
  if (BSKY_DOMAINS.includes(urlObj.hostname.toLowerCase())) {
 | 
			
		||||
    if (canBsky) {
 | 
			
		||||
      return await bluesky(msg, url, spoiler);
 | 
			
		||||
    } else {
 | 
			
		||||
      return {};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!canFedi) return {};
 | 
			
		||||
 | 
			
		||||
  // some lemmy instances have old reddit frontend subdomains
 | 
			
		||||
  // but these frontends are just frontends and dont actually expose the API
 | 
			
		||||
  if (urlObj.hostname.startsWith("old.")) {
 | 
			
		||||
    urlObj.hostname = urlObj.hostname.replace("old.", "");
 | 
			
		||||
    url = urlObj.href;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let platform = (await resolvePlatform(url)) ?? "<no nodeinfo>";
 | 
			
		||||
  let color = PLATFORM_COLORS[platform];
 | 
			
		||||
  let platformName = platform
 | 
			
		||||
    .replace("gotosocial", "GoToSocial")
 | 
			
		||||
    .replace("birdsitelive", '"Twitter" (BirdsiteLive)')
 | 
			
		||||
    .replace(/^(.)/, (_, c) => c.toUpperCase())
 | 
			
		||||
    .replace("Cohost", "cohost");
 | 
			
		||||
 | 
			
		||||
  const images = [];
 | 
			
		||||
  const videos = [];
 | 
			
		||||
  const audios = [];
 | 
			
		||||
  let content,
 | 
			
		||||
    cw,
 | 
			
		||||
    author,
 | 
			
		||||
    timestamp,
 | 
			
		||||
    title,
 | 
			
		||||
    poll,
 | 
			
		||||
    emotes = [],
 | 
			
		||||
    sensitive = false;
 | 
			
		||||
 | 
			
		||||
  // Fetch post
 | 
			
		||||
  let rawPostData;
 | 
			
		||||
  try {
 | 
			
		||||
    rawPostData = await signedFetch(url, {
 | 
			
		||||
      headers: {
 | 
			
		||||
        "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
        Accept: "application/activity+json",
 | 
			
		||||
      },
 | 
			
		||||
    }).then((res) => res.text());
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    logger.error("fedimbed", `Failed to signed fetch "${url}", retrying unsigned: ${err}`);
 | 
			
		||||
  }
 | 
			
		||||
  if (!rawPostData) {
 | 
			
		||||
  if (!forceMastoAPI) {
 | 
			
		||||
    let rawPostData;
 | 
			
		||||
    try {
 | 
			
		||||
      rawPostData = await fetch(url, {
 | 
			
		||||
      rawPostData = await signedFetch(url, {
 | 
			
		||||
        headers: {
 | 
			
		||||
          "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
          Accept: "application/activity+json",
 | 
			
		||||
        },
 | 
			
		||||
      }).then((res) => res.text());
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      logger.error("fedimbed", `Failed to fetch "${url}": ${err}`);
 | 
			
		||||
      logger.error("fedimbed", `Failed to signed fetch "${url}", retrying unsigned: ${err}`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let postData;
 | 
			
		||||
  if (rawPostData?.startsWith("{")) {
 | 
			
		||||
    try {
 | 
			
		||||
      postData = JSON.parse(rawPostData);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      logger.error("fedimbed", `Failed to decode JSON for "${url}": ${err}\n  "${rawPostData}"`);
 | 
			
		||||
    if (!rawPostData) {
 | 
			
		||||
      try {
 | 
			
		||||
        rawPostData = await fetch(url, {
 | 
			
		||||
          headers: {
 | 
			
		||||
            "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
            Accept: "application/activity+json",
 | 
			
		||||
          },
 | 
			
		||||
        }).then((res) => res.text());
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        logger.error("fedimbed", `Failed to fetch "${url}": ${err}`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    logger.warn("fedimbed", `Got non-JSON for "${url}": ${rawPostData}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (postData?.error) {
 | 
			
		||||
    logger.error("fedimbed", `Received error for "${url}": ${postData.error}`);
 | 
			
		||||
    if (rawPostData?.startsWith("{")) {
 | 
			
		||||
      try {
 | 
			
		||||
        postData = JSON.parse(rawPostData);
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        logger.error("fedimbed", `Failed to decode JSON for "${url}": ${err}\n  "${rawPostData}"`);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      logger.warn("fedimbed", `Got non-JSON for "${url}": ${rawPostData}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (postData?.error) {
 | 
			
		||||
      logger.error("fedimbed", `Received error for "${url}": ${postData.error}`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!postData) {
 | 
			
		||||
    // We failed to get post.
 | 
			
		||||
    // Assume it was due to AFM or forced HTTP signatures and use MastoAPI
 | 
			
		||||
    // We failed to get post. (or we forced ourselves to be here)
 | 
			
		||||
    // Assume it was due to AFM or some other issue and (try to) use MastoAPI (or equivalent)
 | 
			
		||||
 | 
			
		||||
    // Follow redirect from /object since we need the ID from /notice
 | 
			
		||||
    if (PATH_REGEX.pleroma.test(urlObj.pathname)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -754,119 +711,211 @@ async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		|||
 | 
			
		||||
      if (!postData2) {
 | 
			
		||||
        logger.warn("fedimbed", `Bailing trying to re-embed "${url}": Failed to get post from normal and MastoAPI.`);
 | 
			
		||||
        return null;
 | 
			
		||||
      } else if (postData2.error) {
 | 
			
		||||
        logger.error(
 | 
			
		||||
          "fedimbed",
 | 
			
		||||
          `Bailing trying to re-embed "${url}", MastoAPI gave us error: ${JSON.stringify(postData2.error)}`
 | 
			
		||||
        );
 | 
			
		||||
      } else {
 | 
			
		||||
        cw = postData2.spoiler_warning ?? postData2.spoiler_text ?? postData2.cw;
 | 
			
		||||
        content =
 | 
			
		||||
          postData2.akkoma?.source?.content ??
 | 
			
		||||
          postData2.pleroma?.content?.["text/plain"] ??
 | 
			
		||||
          postData2.text ??
 | 
			
		||||
          postData2.content;
 | 
			
		||||
        author = {
 | 
			
		||||
          name:
 | 
			
		||||
            postData2.account?.display_name ??
 | 
			
		||||
            postData2.account?.username ??
 | 
			
		||||
            postData2.user?.name ??
 | 
			
		||||
            postData2.user?.username,
 | 
			
		||||
          handle:
 | 
			
		||||
            postData2.account?.fqn ?? `${postData2.account?.username ?? postData2.user?.username}@${urlObj.hostname}`,
 | 
			
		||||
          url: postData2.account?.url ?? `${urlObj.origin}/@${postData2.account?.username ?? postData2.user?.username}`,
 | 
			
		||||
          avatar: postData2.account?.avatar ?? postData2.user?.avatarUrl,
 | 
			
		||||
        };
 | 
			
		||||
        timestamp = postData2.created_at ?? postData2.createdAt;
 | 
			
		||||
        emotes = postData2.emojis.filter((x) => !x.name.endsWith("#.")).map((x) => ({name: `:${x.name}:`, url: x.url}));
 | 
			
		||||
        sensitive = postData2.sensitive;
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
        const attachments = postData2.media_attachments ?? postData2.files;
 | 
			
		||||
        if (attachments) {
 | 
			
		||||
          for (const attachment of attachments) {
 | 
			
		||||
            const contentType = await fetch(attachment.url, {
 | 
			
		||||
              method: "HEAD",
 | 
			
		||||
            }).then((res) => res.headers.get("Content-Type"));
 | 
			
		||||
      postData2._fedimbed_mastoapi = true;
 | 
			
		||||
      return postData2;
 | 
			
		||||
    } else {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    return postData;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
            if (contentType) {
 | 
			
		||||
              if (contentType.startsWith("image/")) {
 | 
			
		||||
                images.push({
 | 
			
		||||
                  url: attachment.url,
 | 
			
		||||
                  desc: attachment.description ?? attachment.comment,
 | 
			
		||||
                  type: contentType,
 | 
			
		||||
                });
 | 
			
		||||
              } else if (contentType.startsWith("video/")) {
 | 
			
		||||
                videos.push({
 | 
			
		||||
                  url: attachment.url,
 | 
			
		||||
                  desc: attachment.description ?? attachment.comment,
 | 
			
		||||
                  type: contentType,
 | 
			
		||||
                });
 | 
			
		||||
              } else if (contentType.startsWith("audio/")) {
 | 
			
		||||
                audios.push({
 | 
			
		||||
                  url: attachment.url,
 | 
			
		||||
                  desc: attachment.description ?? attachment.comment,
 | 
			
		||||
                  type: contentType,
 | 
			
		||||
                });
 | 
			
		||||
              }
 | 
			
		||||
            } else {
 | 
			
		||||
              const type = attachment.type?.toLowerCase();
 | 
			
		||||
async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		||||
  let canFedi = await hasFlag(msg.guildID, "fedimbed");
 | 
			
		||||
  let canBsky = await hasFlag(msg.guildID, "bskyEmbeds");
 | 
			
		||||
 | 
			
		||||
              const fileType =
 | 
			
		||||
                attachment.pleroma?.mime_type ?? type.indexOf("/") > -1
 | 
			
		||||
                  ? type
 | 
			
		||||
                  : type +
 | 
			
		||||
                    "/" +
 | 
			
		||||
                    (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image"
 | 
			
		||||
                      ? "png"
 | 
			
		||||
                      : type == "video"
 | 
			
		||||
                      ? "mp4"
 | 
			
		||||
                      : "mpeg");
 | 
			
		||||
              if (type.startsWith("image")) {
 | 
			
		||||
                images.push({
 | 
			
		||||
                  url: attachment.url,
 | 
			
		||||
                  desc: attachment.description ?? attachment.comment,
 | 
			
		||||
                  type: fileType,
 | 
			
		||||
                });
 | 
			
		||||
              } else if (type.startsWith("video")) {
 | 
			
		||||
                videos.push({
 | 
			
		||||
                  url: attachment.url,
 | 
			
		||||
                  desc: attachment.description ?? attachment.comment,
 | 
			
		||||
                  type: fileType,
 | 
			
		||||
                });
 | 
			
		||||
              } else if (type.startsWith("audio")) {
 | 
			
		||||
                audios.push({
 | 
			
		||||
                  url: attachment.url,
 | 
			
		||||
                  desc: attachment.description ?? attachment.comment,
 | 
			
		||||
                  type: fileType,
 | 
			
		||||
                });
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
  if (command === true) {
 | 
			
		||||
    canFedi = true;
 | 
			
		||||
    canBsky = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let invalidUrl = false;
 | 
			
		||||
  let urlObj;
 | 
			
		||||
  try {
 | 
			
		||||
    urlObj = new URL(url);
 | 
			
		||||
  } catch {
 | 
			
		||||
    invalidUrl = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (invalidUrl) return {};
 | 
			
		||||
 | 
			
		||||
  if (BSKY_DOMAINS.includes(urlObj.hostname.toLowerCase())) {
 | 
			
		||||
    if (canBsky) {
 | 
			
		||||
      return await bluesky(msg, url, spoiler);
 | 
			
		||||
    } else {
 | 
			
		||||
      return {};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!canFedi) return {};
 | 
			
		||||
 | 
			
		||||
  // some lemmy instances have old reddit frontend subdomains
 | 
			
		||||
  // but these frontends are just frontends and dont actually expose the API
 | 
			
		||||
  if (urlObj.hostname.startsWith("old.")) {
 | 
			
		||||
    urlObj.hostname = urlObj.hostname.replace("old.", "");
 | 
			
		||||
    url = urlObj.href;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let platform = (await resolvePlatform(url)) ?? "<no nodeinfo>";
 | 
			
		||||
  let color = PLATFORM_COLORS[platform];
 | 
			
		||||
  let platformName = normalizePlatform(platform);
 | 
			
		||||
 | 
			
		||||
  const images = [];
 | 
			
		||||
  const videos = [];
 | 
			
		||||
  const audios = [];
 | 
			
		||||
  let content,
 | 
			
		||||
    cw,
 | 
			
		||||
    author,
 | 
			
		||||
    timestamp,
 | 
			
		||||
    title,
 | 
			
		||||
    poll,
 | 
			
		||||
    context,
 | 
			
		||||
    contextUrl,
 | 
			
		||||
    emotes = [],
 | 
			
		||||
    sensitive = false;
 | 
			
		||||
 | 
			
		||||
  // Fetch post
 | 
			
		||||
  const postData = await fetchPost(url, platform);
 | 
			
		||||
 | 
			
		||||
  if (postData._fedimbed_mastoapi) {
 | 
			
		||||
    if (postData.url) {
 | 
			
		||||
      const realUrlObj = new URL(postData.url);
 | 
			
		||||
      if (realUrlObj.origin != urlObj.origin) {
 | 
			
		||||
        platform = await resolvePlatform(postData.url);
 | 
			
		||||
        color = PLATFORM_COLORS[platform];
 | 
			
		||||
        platformName = normalizePlatform(platform);
 | 
			
		||||
        url = postData.url;
 | 
			
		||||
        urlObj = realUrlObj;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cw = postData.spoiler_warning ?? postData.spoiler_text ?? postData.cw;
 | 
			
		||||
    content =
 | 
			
		||||
      postData.akkoma?.source?.content ??
 | 
			
		||||
      postData.pleroma?.content?.["text/plain"] ??
 | 
			
		||||
      postData.text ??
 | 
			
		||||
      postData.content;
 | 
			
		||||
    author = {
 | 
			
		||||
      name:
 | 
			
		||||
        postData.account?.display_name ?? postData.account?.username ?? postData.user?.name ?? postData.user?.username,
 | 
			
		||||
      handle: postData.account?.fqn ?? `${postData.account?.username ?? postData.user?.username}@${urlObj.hostname}`,
 | 
			
		||||
      url: postData.account?.url ?? `${urlObj.origin}/@${postData.account?.username ?? postData.user?.username}`,
 | 
			
		||||
      avatar: postData.account?.avatar ?? postData.user?.avatarUrl,
 | 
			
		||||
    };
 | 
			
		||||
    timestamp = postData.created_at ?? postData.createdAt;
 | 
			
		||||
    emotes = postData.emojis.filter((x) => !x.name.endsWith("#.")).map((x) => ({name: `:${x.name}:`, url: x.url}));
 | 
			
		||||
    sensitive = postData.sensitive;
 | 
			
		||||
 | 
			
		||||
    if (postData.in_reply_to_id) {
 | 
			
		||||
      // this url is a dummy and will failed if gone to normally
 | 
			
		||||
      const replyData = await fetchPost(
 | 
			
		||||
        `https://${urlObj.origin}/@fedimbed_reply_fake_user_sorry/${postData.in_reply_to_id}`,
 | 
			
		||||
        platform,
 | 
			
		||||
        true
 | 
			
		||||
      );
 | 
			
		||||
      if (replyData) {
 | 
			
		||||
        contextUrl = replyData.url;
 | 
			
		||||
        context = `Replying to: ${
 | 
			
		||||
          replyData.account?.display_name ??
 | 
			
		||||
          replyData.account?.username ??
 | 
			
		||||
          replyData.user?.name ??
 | 
			
		||||
          replyData.user?.username
 | 
			
		||||
        } (${
 | 
			
		||||
          replyData.account?.fqn ?? `${replyData.account?.username ?? replyData.user?.username}@${urlObj.hostname}`
 | 
			
		||||
        })`;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const attachments = postData.media_attachments ?? postData.files;
 | 
			
		||||
    if (attachments) {
 | 
			
		||||
      for (const attachment of attachments) {
 | 
			
		||||
        const contentType = await fetch(attachment.url, {
 | 
			
		||||
          method: "HEAD",
 | 
			
		||||
        }).then((res) => res.headers.get("Content-Type"));
 | 
			
		||||
 | 
			
		||||
        if (contentType) {
 | 
			
		||||
          if (contentType.startsWith("image/")) {
 | 
			
		||||
            images.push({
 | 
			
		||||
              url: attachment.url,
 | 
			
		||||
              desc: attachment.description ?? attachment.comment,
 | 
			
		||||
              type: contentType,
 | 
			
		||||
            });
 | 
			
		||||
          } else if (contentType.startsWith("video/")) {
 | 
			
		||||
            videos.push({
 | 
			
		||||
              url: attachment.url,
 | 
			
		||||
              desc: attachment.description ?? attachment.comment,
 | 
			
		||||
              type: contentType,
 | 
			
		||||
            });
 | 
			
		||||
          } else if (contentType.startsWith("audio/")) {
 | 
			
		||||
            audios.push({
 | 
			
		||||
              url: attachment.url,
 | 
			
		||||
              desc: attachment.description ?? attachment.comment,
 | 
			
		||||
              type: contentType,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          const type = attachment.type?.toLowerCase();
 | 
			
		||||
 | 
			
		||||
          const fileType =
 | 
			
		||||
            attachment.pleroma?.mime_type ?? type.indexOf("/") > -1
 | 
			
		||||
              ? type
 | 
			
		||||
              : type +
 | 
			
		||||
                "/" +
 | 
			
		||||
                (url.match(/\.([a-z0-9]{3,4})$/)?.[0] ?? type == "image" ? "png" : type == "video" ? "mp4" : "mpeg");
 | 
			
		||||
          if (type.startsWith("image")) {
 | 
			
		||||
            images.push({
 | 
			
		||||
              url: attachment.url,
 | 
			
		||||
              desc: attachment.description ?? attachment.comment,
 | 
			
		||||
              type: fileType,
 | 
			
		||||
            });
 | 
			
		||||
          } else if (type.startsWith("video")) {
 | 
			
		||||
            videos.push({
 | 
			
		||||
              url: attachment.url,
 | 
			
		||||
              desc: attachment.description ?? attachment.comment,
 | 
			
		||||
              type: fileType,
 | 
			
		||||
            });
 | 
			
		||||
          } else if (type.startsWith("audio")) {
 | 
			
		||||
            audios.push({
 | 
			
		||||
              url: attachment.url,
 | 
			
		||||
              desc: attachment.description ?? attachment.comment,
 | 
			
		||||
              type: fileType,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (!spoiler && postData2.sensitive && attachments.length > 0) {
 | 
			
		||||
          spoiler = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (postData2.poll) {
 | 
			
		||||
          poll = {
 | 
			
		||||
            end: new Date(postData2.poll.expires_at),
 | 
			
		||||
            total: postData2.poll.votes_count,
 | 
			
		||||
            options: postData2.poll.options.map((o) => ({
 | 
			
		||||
              name: o.title,
 | 
			
		||||
              count: o.votes_count,
 | 
			
		||||
            })),
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!spoiler && postData.sensitive && attachments.length > 0) {
 | 
			
		||||
      spoiler = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (postData.poll) {
 | 
			
		||||
      poll = {
 | 
			
		||||
        end: new Date(postData.poll.expires_at),
 | 
			
		||||
        total: postData.poll.votes_count,
 | 
			
		||||
        options: postData.poll.options.map((o) => ({
 | 
			
		||||
          name: o.title,
 | 
			
		||||
          count: o.votes_count,
 | 
			
		||||
        })),
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    if (postData.id) {
 | 
			
		||||
      const realUrlObj = new URL(postData.id);
 | 
			
		||||
      if (realUrlObj.origin != urlObj.origin) {
 | 
			
		||||
        platform = await resolvePlatform(postData.id);
 | 
			
		||||
        color = PLATFORM_COLORS[platform];
 | 
			
		||||
        platformName = platform.replace("gotosocial", "GoToSocial").replace(/^(.)/, (_, c) => c.toUpperCase());
 | 
			
		||||
        platformName = normalizePlatform(platform);
 | 
			
		||||
        url = postData.id;
 | 
			
		||||
        urlObj = realUrlObj;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -882,6 +931,49 @@ async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		|||
      emotes = tag.filter((x) => !!x.icon).map((x) => ({name: x.name, url: x.icon.url}));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (postData.inReplyTo) {
 | 
			
		||||
      contextUrl = postData.inReplyTo;
 | 
			
		||||
      context = "Replying to: ";
 | 
			
		||||
 | 
			
		||||
      const replyData = await fetchPost(postData.inReplyTo, platform);
 | 
			
		||||
      if (replyData) {
 | 
			
		||||
        if (replyData._fedimbed_mastoapi) {
 | 
			
		||||
          context += `${
 | 
			
		||||
            replyData.account?.display_name ??
 | 
			
		||||
            replyData.account?.username ??
 | 
			
		||||
            replyData.user?.name ??
 | 
			
		||||
            replyData.user?.username
 | 
			
		||||
          } (@${
 | 
			
		||||
            replyData.account?.fqn ?? `${replyData.account?.username ?? replyData.user?.username}@${urlObj.hostname}`
 | 
			
		||||
          })`;
 | 
			
		||||
        } else {
 | 
			
		||||
          const authorData = await signedFetch(postData.actor ?? postData.attributedTo, {
 | 
			
		||||
            headers: {
 | 
			
		||||
              "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
              Accept: "application/activity+json",
 | 
			
		||||
            },
 | 
			
		||||
          })
 | 
			
		||||
            .then((res) => res.json())
 | 
			
		||||
            .catch((err) => {
 | 
			
		||||
              /*if (platform !== "cohost")*/ logger.error("fedimbed", `Failed to get author for "${url}": ${err}`);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
          if (authorData) {
 | 
			
		||||
            const authorUrlObj = new URL(authorData.url ?? authorData.id);
 | 
			
		||||
            context += `${authorData.name} (${authorData.preferredUsername}@${authorUrlObj.hostname})`;
 | 
			
		||||
          } else {
 | 
			
		||||
            // bootleg author
 | 
			
		||||
            const authorUrl = replyData.actor ?? replyData.attributedTo;
 | 
			
		||||
            const authorUrlObj = new URL(authorUrl);
 | 
			
		||||
            const name = authorUrlObj.pathname.substring(authorUrlObj.pathname.lastIndexOf("/") + 1);
 | 
			
		||||
            context += `${name}@${authorUrlObj.hostname}`;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        context += "<failed to get reply author>";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // NB: gts doesnt send singular attachments as array
 | 
			
		||||
    const attachments = Array.isArray(postData.attachment) ? postData.attachment : [postData.attachment];
 | 
			
		||||
    for (const attachment of attachments) {
 | 
			
		||||
| 
						 | 
				
			
			@ -991,8 +1083,7 @@ async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		|||
    })
 | 
			
		||||
      .then((res) => res.json())
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        // only posts can be activity+json right now, reduce log spam
 | 
			
		||||
        if (platform !== "cohost") logger.error("fedimbed", `Failed to get author for "${url}": ${err}`);
 | 
			
		||||
        /*if (platform !== "cohost")*/ logger.error("fedimbed", `Failed to get author for "${url}": ${err}`);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    if (authorData) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1004,7 +1095,7 @@ async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		|||
        avatar: authorData.icon?.url,
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      // bootleg author, mainly for cohost
 | 
			
		||||
      // bootleg author
 | 
			
		||||
      const authorUrl = postData.actor ?? postData.attributedTo;
 | 
			
		||||
      const authorUrlObj = new URL(authorUrl);
 | 
			
		||||
      const name = authorUrlObj.pathname.substring(authorUrlObj.pathname.lastIndexOf("/") + 1);
 | 
			
		||||
| 
						 | 
				
			
			@ -1083,6 +1174,12 @@ async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		|||
          name: user,
 | 
			
		||||
          url: author.url,
 | 
			
		||||
        }
 | 
			
		||||
      : context
 | 
			
		||||
      ? {
 | 
			
		||||
          name: context,
 | 
			
		||||
          url: contextUrl,
 | 
			
		||||
          icon_url: "https://cdn.discordapp.com/emojis/1308640078825787412.png",
 | 
			
		||||
        }
 | 
			
		||||
      : null,
 | 
			
		||||
    footer: {
 | 
			
		||||
      text: platformName,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue