Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const app = new Hono<{ Bindings: CloudflareBindings }>()
raw = await p.fetch(id, {
EMBED_USER_AGENT: c.env.EMBED_USER_AGENT,
FACEBOOK_MARKETPLACE_COOKIE: c.env.FACEBOOK_MARKETPLACE_COOKIE,
TRUTH_SOCIAL_ACCESS_TOKEN: c.env.TRUTH_SOCIAL_ACCESS_TOKEN,
});
} catch (cause) {
const errorContext = getErrorContext(cause);
Expand Down
1 change: 1 addition & 0 deletions apps/api/worker-configuration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface __BaseEnv_CloudflareBindings {
AUTH_SECRET: string;
EMBED_USER_AGENT: string;
FACEBOOK_MARKETPLACE_COOKIE?: string;
TRUTH_SOCIAL_ACCESS_TOKEN?: string;
OTEL_ENDPOINT: string;
}
declare namespace Cloudflare {
Expand Down
3 changes: 2 additions & 1 deletion apps/bot/src/lib/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ function buildMediaEmbed(media: NormalizedPost["media"], spoiler?: EmbedFlags["S
}

function addPostComponents(embed: ContainerBuilder, post: PostData, headingPrefix?: string) {
const platformName = post.platform === "TruthSocial" ? "Truth Social" : post.platform;
const poll = post.platform === "Twitter" ? post.poll : undefined;
const translation =
post.platform === "Twitter" &&
Expand Down Expand Up @@ -171,7 +172,7 @@ function addPostComponents(embed: ContainerBuilder, post: PostData, headingPrefi
),
(footer) =>
footer.setContent(
`${getEmojiByName(post.platform)} • ${time(post.timestamp, TimestampStyles.LongDateShortTime)} • ${hyperlink(`View on ${post.platform}`, post.url)}`,
`${getEmojiByName(post.platform)} • ${time(post.timestamp, TimestampStyles.LongDateShortTime)} • ${hyperlink(`View on ${platformName}`, post.url)}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing Truth Social Emoji

Truth Social posts now reach this footer, but getEmojiByName(post.platform) looks up a TruthSocial application emoji while no TruthSocial.png asset is synchronized. This supplies an undefined ID to formatEmoji, leaving every Truth Social embed with an invalid or visibly broken platform emoji reference. Add the corresponding emoji asset or provide a deliberate fallback.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/bot/src/lib/builder.ts
Line: 175

Comment:
**Missing Truth Social Emoji**

Truth Social posts now reach this footer, but `getEmojiByName(post.platform)` looks up a `TruthSocial` application emoji while no `TruthSocial.png` asset is synchronized. This supplies an undefined ID to `formatEmoji`, leaving every Truth Social embed with an invalid or visibly broken platform emoji reference. Add the corresponding emoji asset or provide a deliberate fallback.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

),
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/platforms/src/platforms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { Instagram } from "./instagram";
export { TikTok } from "./tiktok";
export { Threads } from "./threads";
export { FacebookMarketplace } from "./facebook-marketplace";
export { TruthSocial } from "./truth-social";
27 changes: 27 additions & 0 deletions packages/platforms/src/platforms/truth-social.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export interface TruthSocialAccount {
display_name: string;
username: string;
url: string;
avatar: string;
}

export interface TruthSocialMedia {
type: string;
url: string;
preview_url?: string;
description?: string;
}

export interface TruthSocialStatus {
id: string;
created_at: string;
url: string;
content: string;
account: TruthSocialAccount;
replies_count: number;
reblogs_count: number;
favourites_count: number;
media_attachments: TruthSocialMedia[];
quote?: TruthSocialStatus | null;
in_reply_to?: TruthSocialStatus | null;
}
81 changes: 81 additions & 0 deletions packages/platforms/src/platforms/truth-social.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import he from "he";

import type { Platform } from "../types";
import type { TruthSocialStatus } from "./truth-social.d";

const MATCH_RE =
/^(?:https?:\/\/)?(?:www\.)?truthsocial\.com\/@[^/?]+\/(?:posts\/)?(\d+)\/?(?:[?#].*)?$/;
const MAX_CONTEXT_DEPTH = 1;

function stripHtml(html: string) {
return he.decode(
html
.replace(/<br\s*\/?>/gi, "\n")
.replace(/<\/p>\s*<p>/gi, "\n\n")
.replace(/<[^>]+>/g, ""),
);
}

export const TruthSocial: Platform<"TruthSocial", TruthSocialStatus, {}> = {
type: "TruthSocial",
async match(url) {
return url.match(MATCH_RE)?.[1] ?? null;
},
async fetch(id, env) {
if (!/^\d+$/.test(id)) {
throw { code: 400, message: "Invalid Truth Social status ID" };
}
if (!env?.TRUTH_SOCIAL_ACCESS_TOKEN) {
throw { code: 500, message: "TRUTH_SOCIAL_ACCESS_TOKEN is required" };
}

const response = await fetch(`https://truthsocial.com/api/v1/statuses/${id}`, {
headers: {
Authorization: `Bearer ${env.TRUTH_SOCIAL_ACCESS_TOKEN}`,
Accept: "application/json",
"User-Agent": env.EMBED_USER_AGENT,
},
});

if (!response.ok) throw { code: response.status, message: response.statusText };

// SAFETY: Truth Social's status endpoint follows its observed Mastodon-compatible shape.
return (await response.json()) as TruthSocialStatus;
},
async transform(raw, options) {
const depth = options?.depth ?? 0;
const includeContext = depth < MAX_CONTEXT_DEPTH;
const text = stripHtml(raw.content);

return {
platform: this.type,
author: {
name: raw.account.display_name,
handle: raw.account.username,
url: raw.account.url,
avatar: raw.account.avatar,
},
url: raw.url,
text: text || undefined,
timestamp: Math.floor(Date.parse(raw.created_at) / 1000),
stats: {
comments: raw.replies_count,
reposts: raw.reblogs_count,
likes: raw.favourites_count,
},
media: raw.media_attachments.map((media) => ({
url: media.url,
type: media.type,
description: media.description,
})),
quote:
includeContext && raw.quote
? await this.transform(raw.quote, { depth: depth + 1 })
: undefined,
reply_to:
includeContext && raw.in_reply_to
? await this.transform(raw.in_reply_to, { depth: depth + 1 })
: undefined,
};
},
} as const;
1 change: 1 addition & 0 deletions packages/platforms/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface TransformOptions {
interface FetchEnv {
EMBED_USER_AGENT: string;
FACEBOOK_MARKETPLACE_COOKIE?: string;
TRUTH_SOCIAL_ACCESS_TOKEN?: string;
REDDIT_CLIENT_ID?: string;
REDDIT_CLIENT_SECRET?: string;
}
Expand Down
Loading