diff --git a/.changeset/mighty-boxes-tease.md b/.changeset/mighty-boxes-tease.md new file mode 100644 index 0000000..532fefb --- /dev/null +++ b/.changeset/mighty-boxes-tease.md @@ -0,0 +1,5 @@ +--- +"@embedly/platforms": minor +--- + +✨ feat(platforms): add New York Times platform diff --git a/.changeset/wacky-teeth-ring.md b/.changeset/wacky-teeth-ring.md new file mode 100644 index 0000000..b6da585 --- /dev/null +++ b/.changeset/wacky-teeth-ring.md @@ -0,0 +1,5 @@ +--- +"@embedly/builder": patch +--- + +cap media gallery items at Discord's 10-item limit diff --git a/packages/platforms/src/NYTimes.ts b/packages/platforms/src/NYTimes.ts new file mode 100644 index 0000000..4b78790 --- /dev/null +++ b/packages/platforms/src/NYTimes.ts @@ -0,0 +1,130 @@ +import { Embed } from "@embedly/builder"; +import * as cheerio from "cheerio"; +import he from "he"; +import { CF_CACHE_OPTIONS } from "./constants.ts"; +import { type BaseEmbedData, EmbedlyPlatform } from "./Platform.ts"; +import { EmbedlyPlatformType } from "./types.ts"; +import { validateRegexMatch } from "./utils.ts"; + +export class NYTimes extends EmbedlyPlatform { + readonly color = [0, 0, 0] as const; + readonly emoji = ""; + readonly regex = + /nytimes\.com\/(?\d{4}\/\d{2}\/\d{2}\/[^?#]+\.html)/; + + constructor() { + super(EmbedlyPlatformType.NYTimes, "nyt"); + } + + async parsePostId(url: string): Promise { + const match = this.regex.exec(url); + validateRegexMatch( + match, + "Invalid NYTimes URL: could not extract path" + ); + const { path } = match.groups; + return path; + } + + async fetchPost(post_id: string): Promise { + const resp = await fetch(`https://www.nytimes.com/${post_id}`, { + method: "GET", + redirect: "follow", + headers: { + "User-Agent": "Twitterbot/1.0" + }, + ...CF_CACHE_OPTIONS + }); + + if (!resp.ok) { + throw { code: resp.status, message: resp.statusText }; + } + + const html = await resp.text(); + const $ = cheerio.load(html); + const script = $('script[type="application/ld+json"]').first(); + const scriptText = script.text(); + + if (!scriptText) { + throw { + code: 500, + message: "NYTimes page structure changed: missing JSON-LD data" + }; + } + + let data: any; + try { + data = JSON.parse(scriptText); + } catch { + throw { + code: 500, + message: "Failed to parse NYTimes JSON-LD data" + }; + } + + if ( + data["@type"] !== "NewsArticle" || + !data.headline || + !data.description + ) { + throw { + code: 500, + message: + "NYTimes page structure changed: missing required fields" + }; + } + + return data; + } + + transformRawData(raw_data: any): BaseEmbedData { + let description = he.decode(raw_data.description); + + if ( + raw_data.author && + Array.isArray(raw_data.author) && + raw_data.author.length > 0 + ) { + const byline = raw_data.author + .map((a: any) => a.name) + .join(" and "); + description = `By ${byline}\n\n${description}`; + } + + return { + platform: this.name, + color: [...this.color], + emoji: this.emoji, + name: he.decode(raw_data.headline), + avatar_url: + "https://static01.nyt.com/images/icons/t_logo_291_black.png", + timestamp: Math.floor( + new Date(raw_data.datePublished).getTime() / 1000 + ), + url: raw_data["@id"], + description + }; + } + + createEmbed(post_data: any): Embed { + const embed = new Embed(this.transformRawData(post_data)); + + if ( + post_data.image && + Array.isArray(post_data.image) && + post_data.image.length > 0 + ) { + const image = post_data.image[0]; + embed.setMedia([ + { + media: { + url: image.url + }, + description: image.caption + } + ]); + } + + return embed; + } +} diff --git a/packages/platforms/src/main.ts b/packages/platforms/src/main.ts index 807a94b..b251a54 100644 --- a/packages/platforms/src/main.ts +++ b/packages/platforms/src/main.ts @@ -1,5 +1,6 @@ import { CBC } from "./CBC.ts"; import { Instagram } from "./Instagram.ts"; +import { NYTimes } from "./NYTimes.ts"; import { Reddit } from "./Reddit.ts"; import { Threads } from "./Threads.ts"; import { TikTok } from "./TikTok.ts"; @@ -12,7 +13,8 @@ export const Platforms = { [EmbedlyPlatformType.TikTok]: new TikTok(), [EmbedlyPlatformType.CBC]: new CBC(), [EmbedlyPlatformType.Threads]: new Threads(), - [EmbedlyPlatformType.Reddit]: new Reddit() + [EmbedlyPlatformType.Reddit]: new Reddit(), + [EmbedlyPlatformType.NYTimes]: new NYTimes() } as const; export const EmbedlyPlatformColors = Object.fromEntries( diff --git a/packages/platforms/src/types.ts b/packages/platforms/src/types.ts index 25762dd..9c702fb 100644 --- a/packages/platforms/src/types.ts +++ b/packages/platforms/src/types.ts @@ -4,7 +4,8 @@ export const EmbedlyPlatformType = { TikTok: "TikTok", CBC: "cbc.ca", Threads: "Threads", - Reddit: "Reddit" + Reddit: "Reddit", + NYTimes: "NYTimes" } as const; export type EmbedlyPlatformType = @@ -39,5 +40,6 @@ export const emojis: Emojis = { [EmbedlyPlatformType.TikTok]: "<:tiktok:1386641825963708446>", [EmbedlyPlatformType.CBC]: "<:cbc:1409997044495683674>", [EmbedlyPlatformType.Threads]: "<:threads:1413343483929956446>", - [EmbedlyPlatformType.Reddit]: "<:reddit:1461320093240655922>" + [EmbedlyPlatformType.Reddit]: "<:reddit:1461320093240655922>", + [EmbedlyPlatformType.NYTimes]: "" };