From f7c618bc9ada96489a04cb12096fb323fe2a6296 Mon Sep 17 00:00:00 2001 From: abishek77s Date: Thu, 2 Oct 2025 20:34:01 +0530 Subject: [PATCH] Substack image support prototype, have to make changes to ensure consistent code practices. --- api/src/processing/match-action.js | 1 + api/src/processing/match.js | 13 +++++-- api/src/processing/service-config.js | 7 ++++ api/src/processing/service-patterns.js | 4 +++ api/src/processing/services/substack.js | 45 +++++++++++++++++++++++++ api/src/processing/url.js | 3 ++ 6 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 api/src/processing/services/substack.js diff --git a/api/src/processing/match-action.js b/api/src/processing/match-action.js index 5852b19d7..c641157a7 100644 --- a/api/src/processing/match-action.js +++ b/api/src/processing/match-action.js @@ -99,6 +99,7 @@ export default function({ case "picker": responseType = "picker"; switch (host) { + case "substack": case "instagram": case "twitter": case "snapchat": diff --git a/api/src/processing/match.js b/api/src/processing/match.js index 1265297cd..a3effc2c8 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -30,10 +30,12 @@ import facebook from "./services/facebook.js"; import bluesky from "./services/bluesky.js"; import xiaohongshu from "./services/xiaohongshu.js"; import newgrounds from "./services/newgrounds.js"; +import substack from "./services/substack.js"; + let freebind; -export default async function({ host, patternMatch, params, authType }) { +export default async function({ host, patternMatch, params, authType }) { const { url } = params; assert(url instanceof URL); let dispatcher, requestIP; @@ -74,8 +76,7 @@ export default async function({ host, patternMatch, params, authType }) { youtubeHLS = false; } - const subtitleLang = - params.subtitleLang !== "none" ? params.subtitleLang : undefined; + const subtitleLang =params.subtitleLang !== "none" ? params.subtitleLang : undefined; switch (host) { case "twitter": @@ -276,6 +277,12 @@ export default async function({ host, patternMatch, params, authType }) { }); break; + case "substack": + console.log(url); + + r = await substack({ url }); + break; + default: return createResponse("error", { code: "error.api.service.unsupported" diff --git a/api/src/processing/service-config.js b/api/src/processing/service-config.js index 0a35838bd..21ec72e13 100644 --- a/api/src/processing/service-config.js +++ b/api/src/processing/service-config.js @@ -163,6 +163,13 @@ export const services = { ], subdomains: "*", }, + substack: { + patterns: [ + "@:user/note/:noteId", + ":user/p/:noteId" + ], + subdomains: "*", +}, twitch: { patterns: [":channel/clip/:clip"], tld: "tv", diff --git a/api/src/processing/service-patterns.js b/api/src/processing/service-patterns.js index 6dc3ccbd0..dfc457895 100644 --- a/api/src/processing/service-patterns.js +++ b/api/src/processing/service-patterns.js @@ -87,4 +87,8 @@ export const testers = { "youtube": pattern => pattern.id?.length <= 11, + + "substack": pattern => + pattern.user?.length <= 30 && pattern.noteId?.length <= 20, + } diff --git a/api/src/processing/services/substack.js b/api/src/processing/services/substack.js new file mode 100644 index 000000000..8e6d8307e --- /dev/null +++ b/api/src/processing/services/substack.js @@ -0,0 +1,45 @@ +import { createStream } from "../../stream/manage.js"; + + +export default async function({url}) { + try { + const m = url.href.match(/\/c-(\d+)(?=\b|$|[?\/#])/); + const id = m ? m[1] : null; + const response = await fetch("https://substack.com/api/v1/reader/comment/" + id); + + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + + const imageAttachments = (data.item?.comment?.attachments || []).filter(att => att.type === "image"); + + const picker = imageAttachments.map((att, i) => { + const imgUrl = att.imageUrl; + const imageId = att.imageUrl.split("/").pop(); // extract image id for thumbnail url + const thumbUrl = `https://substackcdn.com/image/fetch/$s_!xQMH!,w_200,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F${imageId}`; + + return { + type: "photo", + url: imgUrl, + thumb: createStream({ + service: "substack", + type: "proxy", + url: thumbUrl, + filename: `substack_${i + 1}.jpg` + }) + }; + }); + + return { + picker + }; + + } catch (error) { + return { + picker: [], + error: error.message + }; + } +} \ No newline at end of file diff --git a/api/src/processing/url.js b/api/src/processing/url.js index 9ac8a3ee9..d742c9a9a 100644 --- a/api/src/processing/url.js +++ b/api/src/processing/url.js @@ -117,6 +117,9 @@ function aliasURL(url) { url = new URL(`https://www.reddit.com/video/${parts[1]}`); } break; + case "substack": + url = new URL(url.href) + break; } return url;