Skip to content

Commit bc80408

Browse files
Copilotnzakas
andcommitted
feat: use <title> as fallback when og:title is absent in card preview
Co-authored-by: nzakas <38546+nzakas@users.noreply.github.com> Agent-Logs-Url: https://github.com/humanwhocodes/crosspost/sessions/e05f7b80-f4d0-4f40-85e6-4c04995e99ce
1 parent 43e82f6 commit bc80408

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

src/strategies/bluesky.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ function parseOpenGraphData(html) {
132132
}
133133
}
134134

135+
// Fall back to <title> tag if og:title is not present
136+
if (!ogData.title) {
137+
const titleMatch = /<title[^>]*>([^<]*)<\/title>/i.exec(html);
138+
if (titleMatch) {
139+
ogData.title = titleMatch[1].trim();
140+
}
141+
}
142+
135143
return {
136144
title: ogData.title ?? "",
137145
description: ogData.description ?? "",

tests/strategies/bluesky.test.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,89 @@ describe("BlueskyStrategy", function () {
10431043
const response = await strategy.post(text);
10441044
assert.deepStrictEqual(response, CREATE_RECORD_RESPONSE);
10451045
});
1046+
1047+
it("should use <title> as fallback when og:title is not present", async function () {
1048+
const text = "Check this out https://example.com/article";
1049+
1050+
// Mock the OG data fetch - page has <title> but no og:title
1051+
externalServer.get(
1052+
{ url: "/article" },
1053+
{
1054+
status: 200,
1055+
headers: { "content-type": "text/html" },
1056+
body: `<html><head>
1057+
<title>Page Title Fallback</title>
1058+
<meta property="og:description" content="An interesting article" />
1059+
</head><body></body></html>`,
1060+
},
1061+
);
1062+
1063+
// Mock Bluesky session
1064+
server.post(
1065+
{
1066+
url: CREATE_SESSION_URL,
1067+
headers: { "content-type": "application/json" },
1068+
body: {
1069+
identifier: options.identifier,
1070+
password: options.password,
1071+
},
1072+
},
1073+
{
1074+
status: 200,
1075+
headers: { "content-type": "application/json" },
1076+
body: CREATE_SESSION_RESPONSE,
1077+
},
1078+
);
1079+
1080+
// Mock post creation with embed using <title> as title
1081+
server.post(
1082+
{
1083+
url: CREATE_RECORD_URL,
1084+
headers: {
1085+
"content-type": "application/json",
1086+
authorization: `Bearer ${CREATE_SESSION_RESPONSE.accessJwt}`,
1087+
},
1088+
body: {
1089+
repo: CREATE_SESSION_RESPONSE.did,
1090+
collection: "app.bsky.feed.post",
1091+
record: {
1092+
$type: "app.bsky.feed.post",
1093+
text,
1094+
facets: [
1095+
{
1096+
index: {
1097+
byteStart: 15,
1098+
byteEnd: 42,
1099+
},
1100+
features: [
1101+
{
1102+
$type: "app.bsky.richtext.facet#link",
1103+
uri: "https://example.com/article",
1104+
},
1105+
],
1106+
},
1107+
],
1108+
embed: {
1109+
$type: "app.bsky.embed.external",
1110+
external: {
1111+
uri: "https://example.com/article",
1112+
title: "Page Title Fallback",
1113+
description: "An interesting article",
1114+
},
1115+
},
1116+
},
1117+
},
1118+
},
1119+
{
1120+
status: 200,
1121+
headers: { "content-type": "application/json" },
1122+
body: CREATE_RECORD_RESPONSE,
1123+
},
1124+
);
1125+
1126+
const response = await strategy.post(text);
1127+
assert.deepStrictEqual(response, CREATE_RECORD_RESPONSE);
1128+
});
10461129
});
10471130

10481131
describe("getUrlFromResponse", function () {

0 commit comments

Comments
 (0)