Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenGraph embeds #416

Open
ioistired opened this issue Jun 28, 2021 · 6 comments · May be fixed by #643 or #421
Open

OpenGraph embeds #416

ioistired opened this issue Jun 28, 2021 · 6 comments · May be fixed by #643 or #421

Comments

@ioistired
Copy link

It would be nice if sharing a link to a szurubooru post on supported platforms (Twitter, Facebook, chat clients etc) would embed a preview of the post. However, as far as I'm aware (not sure though) this requires the metadata to be embedded in the HTML, rather than added by client side javascript, so it might require the backend to also start serving frontend pages or something along those lines.

@robobenklein
Copy link
Contributor

Here's my proposed design for enabling OpenGraph tags without changing the structure of the single-page app (SPA), based on the idea from https://stackoverflow.com/questions/50263513/redirect-crawlers-to-internal-microservice-in-nginx

  1. Request comes in from crawler / bot
  2. Nginx performs a match against the user agent (if it's not a crawler/bot, serve normally)
  3. For bots, redirect internally (as the reverse proxy) to http://backend/opengraph/$1
  4. Server backend generates an HTML stub page with just the opengraph tags (this is where permissions will be handled - e.g. denying access if the default post access level is not public)

Step 2 is the part where I am not sure it's the best implementation, since matching the user agent is an evolving problem which needs to be updated for every service that OG tags would be displayed on. (For example the discordbot user agent is not handled in the stackoverflow post I linked: "Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)")

@ioistired
Copy link
Author

Or we could make the frontend a server side app which serves static HTML except for all pages that have OpenGraph tags, for which it serves static HTML + the tags

@robobenklein
Copy link
Contributor

@ioistired how would the frontend go about retrieving that content for the og tags from the backend server? AFAIK the current nginx config has no serverside processing going on at all right now, so what would that additional code look like?

@ioistired
Copy link
Author

There'd be a backend server for the frontend. index.htm currently looks like this:

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8"/>
  <meta content="width=device-width,initial-scale=1,maximum-scale=1" name="viewport"/>
  <meta content="#24aadd" name="theme-color"/>
  <meta content="yes" name="apple-mobile-web-app-capable"/>
  <meta content="black" name="apple-mobile-web-app-status-bar-style"/>
  <meta content="#ffffff" name="msapplication-TileColor"/>
  <meta content="/img/mstile-150x150.png" name="msapplication-TileImage"/>
  <title>
   Loading...
  </title>
  <!-- snip -->
 </head>
 <body>
  <!-- snip -->
 </body>
</html>

i'm proposing we set up a simple little web server in Python that normally continues to serve that, but when requesting something it can generate OpenGraph embeds for, it additionally requests info about the URL from the backend and adds it to the <head> element. Does that make more sense?

@ioistired
Copy link
Author

ioistired commented Jul 8, 2021

I have implemented the approach I described above as #421. Here's a screenshot:

image

Note that Twitter video embeds are still a work in progress. Currently they're cut off for some reason:

image

@phil-flip
Copy link
Contributor

phil-flip commented Aug 23, 2024

Incase anyone needs it, I implemented my own embed solution until this gets done. It uses the API to still keep the website hidden but be being able to share art directly into Discord, without copying the source (which in most cases also has a terrible embed support).

Keep in mind, that this requires to set the reverse proxy of choice to forward any request to "/post" and no "auth" Cookie set to be sent to this script. It currently forwards to the source (as all of my pics have a source) but it can be freely modified.
Maybe at a later date I will publish the full repo with more indeph detail how to use it, if this is something wished for.

import { serve } from "bun";

const rawToken = `${process.env.szuruUser}:${process.env.szuruToken}`;

const token = Buffer.from(rawToken).toString("base64");

if (process.env.NODE_ENV === "development")
  console.debug(`🚧 Running in debug mode!`);

serve({
  port: process.env.PORT,
  async fetch(req) {
    const url = new URL(req.url);
    const postID = url.pathname.split("/")[2];
    if (Number.isNaN(Number(postID)))
      return new Response(null, { status: 400, statusText: "Not a post ID" });
    const postDataRaw = await fetch(
      `${process.env.APP_ENDPOINT}/api/post/${postID}`,
      {
        method: "GET",
        headers: {
          Authorization: `Token ${token}`,
          "Content-Type": "application/json",
          Accept: "application/json",
        },
      }
    );
    const postDataBody = await postDataRaw.json();

    const artist =
      postDataBody.tags.filter((tag: any) => tag.category === "artist")[0] ||
      "Source";
    const source = postDataBody.source.split("\n")[0];
    const postData = [
      {
        property: "twitter:title",
        content: artist,
      },
      {
        property: 'twitter:description',
        content: postDataBody.tags.map((tag: any) => tag.names[0]).join(' '),
      },
      {
        property: "twitter:image",
        content: `${process.env.APP_ENDPOINT}/${postDataBody.contentUrl}`,
      },
      {
        property: "twitter:card",
        content: "summary_large_image",
      },
    ];

    const heads = postData
      .map(
        ({ property, content }) =>
          `<meta property="${property}" content="${content}" />`
      )
      .join("\n");
    const htmlRes = `
<html>
  <head>
    ${heads}
  </head>
  <body>

    ${source ? `
      <script> window.location.href = "${source}" </script>
      <pre>JS disabled! <a href="${source}">Click here to get redirected to the source.</a></pre>
    ` : '<pre>No source found!</pre>'}
    
  </body>
</html>
  `;

    return new Response(htmlRes, {
      status: 200,
      headers: {
        "content-type": "text/html",
      },
    });
  },
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants