fedimbed: attempt to resolve fedi users from bridgy bsky users
This commit is contained in:
		
							parent
							
								
									12a2e222b7
								
							
						
					
					
						commit
						b72ddd9614
					
				
					 1 changed files with 149 additions and 52 deletions
				
			
		| 
						 | 
				
			
			@ -143,11 +143,69 @@ async function resolvePlatform(url) {
 | 
			
		|||
function normalizePlatform(platform) {
 | 
			
		||||
  return platform
 | 
			
		||||
    .replace("gotosocial", "GoToSocial")
 | 
			
		||||
    .replace("birdsitelive", '"Twitter" (BirdsiteLive)')
 | 
			
		||||
    .replace("birdsitelive", "BirdsiteLive")
 | 
			
		||||
    .replace(/^(.)/, (_, c) => c.toUpperCase())
 | 
			
		||||
    .replace("Cohost", "cohost");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getCrawledData(url, color, platformName) {
 | 
			
		||||
  const urlObj = new URL(url);
 | 
			
		||||
 | 
			
		||||
  let headTag,
 | 
			
		||||
    lastIconSize = 0,
 | 
			
		||||
    icon;
 | 
			
		||||
  try {
 | 
			
		||||
    const page = await fetch(url, {
 | 
			
		||||
      headers: {
 | 
			
		||||
        "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
      .then((res) => res.text())
 | 
			
		||||
      .catch(() => {});
 | 
			
		||||
 | 
			
		||||
    if (page) {
 | 
			
		||||
      headTag = pageParser.parse(page)?.html?.head;
 | 
			
		||||
    }
 | 
			
		||||
  } catch {
 | 
			
		||||
    // noop
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (headTag) {
 | 
			
		||||
    for (const tag of headTag.link) {
 | 
			
		||||
      if (tag.$rel == "icon" || tag.$rel == "apple-touch-icon") {
 | 
			
		||||
        if (tag.$sizes) {
 | 
			
		||||
          const [w, h] = tag.$sizes.split("x").map((x) => parseInt(x));
 | 
			
		||||
          const size = w * h;
 | 
			
		||||
          if (size > lastIconSize) {
 | 
			
		||||
            lastIconSize = size;
 | 
			
		||||
            icon = tag.$href;
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          icon = tag.$href;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const tag of headTag.meta) {
 | 
			
		||||
      if (tag.$property == "og:site_name" && tag.$content != platformName) {
 | 
			
		||||
        if (platformName == "<no nodeinfo>") {
 | 
			
		||||
          platformName = tag.$content;
 | 
			
		||||
        } else {
 | 
			
		||||
          platformName = `${tag.$content} (${platformName})`;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (!color && tag.$name == "theme-color") {
 | 
			
		||||
        color = parseInt(tag.$content.replace("^#", "0x"));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (icon && icon.startsWith("/")) {
 | 
			
		||||
    icon = urlObj.origin + icon;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {icon, color, platformName};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const keyId = "https://hf.c7.pm/actor#main-key";
 | 
			
		||||
const privKey = fs.readFileSync(require.resolve("#root/priv/private.pem"));
 | 
			
		||||
async function signedFetch(url, options) {
 | 
			
		||||
| 
						 | 
				
			
			@ -280,6 +338,48 @@ async function blueskyQuoteEmbed(quote) {
 | 
			
		|||
    timestamp: quote.value.createdAt,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (quote.author.handle.endsWith(".ap.brid.gy")) {
 | 
			
		||||
    const handle = quote.author.handle.replace(".ap.brid.gy", "");
 | 
			
		||||
    const split = handle.split(".");
 | 
			
		||||
 | 
			
		||||
    const username = split.shift();
 | 
			
		||||
    const domain = split.join(".");
 | 
			
		||||
    const authorUrl = `https://${domain}/users/${username}`;
 | 
			
		||||
 | 
			
		||||
    const authorData = await signedFetch(authorUrl, {
 | 
			
		||||
      headers: {
 | 
			
		||||
        "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
        Accept: "application/activity+json",
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
      .then((res) => res.json())
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        logger.error("fedimbed", `Failed to get author for "${authorUrl}": ${err}`);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    if (authorData) {
 | 
			
		||||
      const platform = (await resolvePlatform(authorUrl)) ?? "<no nodeinfo>";
 | 
			
		||||
      let color = PLATFORM_COLORS[platform];
 | 
			
		||||
      let platformName = normalizePlatform(platform);
 | 
			
		||||
 | 
			
		||||
      const crawled = getCrawledData(authorUrl, color, platformName);
 | 
			
		||||
      if (!color && crawled?.color) {
 | 
			
		||||
        color = crawled.color;
 | 
			
		||||
      }
 | 
			
		||||
      if (crawled?.platformName) {
 | 
			
		||||
        platformName = crawled.platformName;
 | 
			
		||||
      }
 | 
			
		||||
      platformName += " [via Bluesky via Bridgy]";
 | 
			
		||||
 | 
			
		||||
      mainEmbed.footer = {
 | 
			
		||||
        text: platformName,
 | 
			
		||||
        icon_url: crawled?.icon,
 | 
			
		||||
      };
 | 
			
		||||
      mainEmbed.title = `${authorData.name} (${authorData.preferredUsername}@${domain})`;
 | 
			
		||||
      mainEmbed.color = color;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (quote.value.facets?.length > 0) {
 | 
			
		||||
    mainEmbed.description = processBlueskyFacets(mainEmbed.description, quote.value.facets);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -353,7 +453,6 @@ async function blueskyQuoteEmbed(quote) {
 | 
			
		|||
async function bluesky(msg, url, spoiler = false) {
 | 
			
		||||
  const quoteOnly = await hasFlag(msg.guildID, "bskyQuoteOnly");
 | 
			
		||||
 | 
			
		||||
  // really...
 | 
			
		||||
  if (url.includes("bsky.brid.gy")) url = url.replace("bsky.brid.gy/r/https://", "");
 | 
			
		||||
 | 
			
		||||
  const urlObj = new URL(url);
 | 
			
		||||
| 
						 | 
				
			
			@ -429,6 +528,48 @@ async function bluesky(msg, url, spoiler = false) {
 | 
			
		|||
    timestamp: post.record.createdAt,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (post.author.handle.endsWith(".ap.brid.gy")) {
 | 
			
		||||
    const handle = post.author.handle.replace(".ap.brid.gy", "");
 | 
			
		||||
    const split = handle.split(".");
 | 
			
		||||
 | 
			
		||||
    const username = split.shift();
 | 
			
		||||
    const domain = split.join(".");
 | 
			
		||||
    const authorUrl = `https://${domain}/@${username}`;
 | 
			
		||||
 | 
			
		||||
    const authorData = await signedFetch(authorUrl, {
 | 
			
		||||
      headers: {
 | 
			
		||||
        "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
        Accept: "application/activity+json",
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
      .then((res) => res.json())
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        logger.error("fedimbed", `Failed to get author for "${authorUrl}": ${err}`);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    if (authorData) {
 | 
			
		||||
      const platform = (await resolvePlatform(authorUrl)) ?? "<no nodeinfo>";
 | 
			
		||||
      let color = PLATFORM_COLORS[platform];
 | 
			
		||||
      let platformName = normalizePlatform(platform);
 | 
			
		||||
 | 
			
		||||
      const crawled = getCrawledData(authorUrl, color, platformName);
 | 
			
		||||
      if (!color && crawled?.color) {
 | 
			
		||||
        color = crawled.color;
 | 
			
		||||
      }
 | 
			
		||||
      if (crawled?.platformName) {
 | 
			
		||||
        platformName = crawled.platformName;
 | 
			
		||||
      }
 | 
			
		||||
      platformName += " [via Bluesky via Bridgy]";
 | 
			
		||||
 | 
			
		||||
      mainEmbed.footer = {
 | 
			
		||||
        text: platformName,
 | 
			
		||||
        icon_url: crawled?.icon,
 | 
			
		||||
      };
 | 
			
		||||
      mainEmbed.title = `${authorData.name} (${authorData.preferredUsername}@${domain})`;
 | 
			
		||||
      mainEmbed.color = color;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (post.record.facets?.length > 0) {
 | 
			
		||||
    mainEmbed.description = processBlueskyFacets(mainEmbed.description, post.record.facets);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -1220,56 +1361,12 @@ async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		|||
 | 
			
		||||
  const user = author.name ? `${author.name} (${author.handle})` : author.handle;
 | 
			
		||||
 | 
			
		||||
  let headTag,
 | 
			
		||||
    lastIconSize = 0,
 | 
			
		||||
    icon;
 | 
			
		||||
  try {
 | 
			
		||||
    const page = await fetch(url, {
 | 
			
		||||
      headers: {
 | 
			
		||||
        "User-Agent": FRIENDLY_USERAGENT,
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
      .then((res) => res.text())
 | 
			
		||||
      .catch(() => {});
 | 
			
		||||
 | 
			
		||||
    if (page) {
 | 
			
		||||
      headTag = pageParser.parse(page)?.html?.head;
 | 
			
		||||
    }
 | 
			
		||||
  } catch {
 | 
			
		||||
    // noop
 | 
			
		||||
  const crawled = await getCrawledData(url, color, platformName);
 | 
			
		||||
  if (!color && crawled?.color) {
 | 
			
		||||
    color = crawled.color;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (headTag) {
 | 
			
		||||
    for (const tag of headTag.link) {
 | 
			
		||||
      if (tag.$rel == "icon" || tag.$rel == "apple-touch-icon") {
 | 
			
		||||
        if (tag.$sizes) {
 | 
			
		||||
          const [w, h] = tag.$sizes.split("x").map((x) => parseInt(x));
 | 
			
		||||
          const size = w * h;
 | 
			
		||||
          if (size > lastIconSize) {
 | 
			
		||||
            lastIconSize = size;
 | 
			
		||||
            icon = tag.$href;
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          icon = tag.$href;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const tag of headTag.meta) {
 | 
			
		||||
      if (tag.$property == "og:site_name" && tag.$content != platformName) {
 | 
			
		||||
        if (platformName == "<no nodeinfo>") {
 | 
			
		||||
          platformName = tag.$content;
 | 
			
		||||
        } else {
 | 
			
		||||
          platformName = `${tag.$content} (${platformName})`;
 | 
			
		||||
        }
 | 
			
		||||
      } else if (!color && tag.$name == "theme-color") {
 | 
			
		||||
        color = parseInt(tag.$content.replace("^#", "0x"));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (icon && icon.startsWith("/")) {
 | 
			
		||||
    icon = urlObj.origin + icon;
 | 
			
		||||
  if (crawled?.platformName) {
 | 
			
		||||
    platformName = crawled.platformName;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const baseEmbed = {
 | 
			
		||||
| 
						 | 
				
			
			@ -1291,7 +1388,7 @@ async function processUrl(msg, url, spoiler = false, command = false) {
 | 
			
		|||
        }
 | 
			
		||||
      : null,
 | 
			
		||||
    footer: {
 | 
			
		||||
      icon_url: icon,
 | 
			
		||||
      icon_url: crawled?.icon,
 | 
			
		||||
      text: platformName,
 | 
			
		||||
    },
 | 
			
		||||
    thumbnail: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue