Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import {
2 BRAND_COLOR,
3 STATIC_IMAGES_URL,
4 TRANSFORMS
5} from "@hey/data/constants";
6import escapeHtml from "@hey/helpers/escapeHtml";
7import getAccount from "@hey/helpers/getAccount";
8import getAvatar from "@hey/helpers/getAvatar";
9import getPostData from "@hey/helpers/getPostData";
10import normalizeDescription from "@hey/helpers/normalizeDescription";
11import { PostDocument, type PostFragment } from "@hey/indexer";
12import type { Context } from "hono";
13import { html } from "hono/html";
14import generateOg from "./ogUtils";
15
16const getPost = async (ctx: Context) => {
17 const { slug } = ctx.req.param();
18
19 return generateOg({
20 buildHtml: (post: PostFragment) => {
21 const targetPost =
22 (post as any).__typename === "Repost" ? (post as any).repostOf : post;
23 const { author, metadata } = targetPost as any;
24 const { username } = getAccount(author);
25 const postData = getPostData(metadata);
26 const filteredContent = postData?.content || "";
27 const title = `${(targetPost as any).__typename} by ${username} on Hey`;
28 const description = normalizeDescription(filteredContent, title);
29 const postUrl = `https://hey.xyz/posts/${(post as any).slug}`;
30
31 const escTitle = escapeHtml(title);
32 const escDescription = escapeHtml(description);
33
34 const asset = postData?.asset;
35 const attachments = postData?.attachments || [];
36
37 const imageUris = (() => {
38 const list: string[] = [];
39 if (asset?.type === "Image" && asset.uri) list.push(asset.uri);
40 for (const att of attachments) {
41 if (att.type === "Image" && att.uri) list.push(att.uri);
42 }
43 return Array.from(new Set(list)).slice(0, 4);
44 })();
45
46 // OG/Twitter meta: prefer images; fallback to author avatar. No video support.
47 const ogImageCandidates = imageUris.length
48 ? imageUris
49 : [getAvatar(author, TRANSFORMS.AVATAR_BIG)];
50 const ogType = "article";
51 const twitterCard = imageUris.length ? "summary_large_image" : "summary";
52
53 return html`
54 <html>
55 <head>
56 <meta charSet="utf-8" />
57 <meta name="viewport" content="width=device-width" />
58 <meta http-equiv="content-language" content="en-US" />
59 <meta name="theme-color" content="${BRAND_COLOR}" />
60 <title>${escTitle}</title>
61 <meta name="description" content="${escDescription}" />
62 <meta property="og:title" content="${escTitle}" />
63 <meta property="og:description" content="${escDescription}" />
64 <meta property="og:type" content="${ogType}" />
65 <meta property="og:site_name" content="Hey" />
66 <meta property="og:url" content="https://hey.xyz/posts/${(post as any).slug}" />
67 <meta property="og:logo" content="${STATIC_IMAGES_URL}/app-icon/0.png" />
68 ${ogImageCandidates.map((img) => html`<meta property="og:image" content="${img}" />`)}
69 <meta name="twitter:card" content="${twitterCard}" />
70 <meta name="twitter:title" content="${escTitle}" />
71 <meta name="twitter:description" content="${escDescription}" />
72 <meta name="twitter:image" content="${ogImageCandidates[0]}" />
73 <meta name="twitter:site" content="@heydotxyz" />
74 <link rel="icon" href="https://hey.xyz/favicon.ico" />
75 <link rel="canonical" href="https://hey.xyz/posts/${(post as any).slug}" />
76 </head>
77 <body>
78 <h1>${escTitle}</h1>
79 <h2>${escDescription}</h2>
80 <div>
81 <b>Stats</b>
82 <ul>
83 <li><a href="${postUrl}">Collects: ${targetPost.stats.collects}</a></li>
84 <li><a href="${postUrl}">Tips: ${targetPost.stats.tips}</a></li>
85 <li><a href="${postUrl}">Comments: ${targetPost.stats.comments}</a></li>
86 <li><a href="${postUrl}">Likes: ${targetPost.stats.reactions}</a></li>
87 <li><a href="${postUrl}">Reposts: ${targetPost.stats.reposts}</a></li>
88 <li><a href="${postUrl}/quotes">Quotes: ${targetPost.stats.quotes}</a></li>
89 </ul>
90 </div>
91 </body>
92 </html>
93 `;
94 },
95 ctx,
96 extractData: (data) => data.post as PostFragment | null,
97 query: PostDocument,
98 variables: { request: { post: slug } }
99 });
100};
101
102export default getPost;