diff --git a/.github/workflows/syndicate-post.yml b/.github/workflows/syndicate-post.yml index d2ef408c..48ec9033 100644 --- a/.github/workflows/syndicate-post.yml +++ b/.github/workflows/syndicate-post.yml @@ -50,6 +50,7 @@ jobs: HASHNODE_ACCESS_TOKEN: ${{ secrets.PRIVATE_HASHNODE }} AWS_BUILDER_SESSION_TOKEN: ${{ secrets.AWS_BUILDER_SESSION_TOKEN }} DAILY_DEV_TOKEN: ${{ secrets.DAILY_DEV_TOKEN }} + DAILY_DEV_SESSION_COOKIE: ${{ secrets.DAILY_DEV_SESSION_COOKIE }} GOOGLE_WORKSPACE_REFRESH_TOKEN: ${{ secrets.GOOGLE_WORKSPACE_REFRESH_TOKEN }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} diff --git a/apps/sanity/functions/syndicate-content/index.ts b/apps/sanity/functions/syndicate-content/index.ts index 8f805e6f..247bfbde 100644 --- a/apps/sanity/functions/syndicate-content/index.ts +++ b/apps/sanity/functions/syndicate-content/index.ts @@ -17,6 +17,7 @@ interface ContentDocument { devto?: string; hashnode?: string; awsbuilder?: string; + dailydev?: string; tags?: Array<{ _key?: string; label?: string; value?: string }>; sponsor?: Array<{ _ref: string }>; syndications?: Array<{ _type: "reference"; _ref: string; _key?: string }>; @@ -118,9 +119,10 @@ export const handler = documentEventHandler( Boolean(data.awsbuilder) || process.env.FORCE_AWS_BUILDER === "true"; - const platforms: Array<"devto" | "hashnode" | "awsbuilder"> = [ + const platforms: Array<"devto" | "hashnode" | "dailydev" | "awsbuilder"> = [ "devto", "hashnode", + "dailydev", ...(shouldSyndicateAws ? (["awsbuilder"] as const) : []), ]; @@ -145,7 +147,9 @@ export const handler = documentEventHandler( ? "dev.to" : platform === "hashnode" ? "hashnode.com" - : "builder.aws.com"; + : platform === "dailydev" + ? "daily.dev" + : "builder.aws.com"; await client.createOrReplace({ _id: syndicationDocId, _type: "syndication", @@ -745,6 +749,175 @@ export const handler = documentEventHandler( console.log( `[Syndicate Content] Successfully published to AWS Builder Center: ${externalUrl}`, ); + } else if (platform === "dailydev") { + const dailyDevSession = process.env.DAILY_DEV_SESSION_COOKIE; + if (!dailyDevSession) { + console.warn( + "[Syndicate Content] DAILY_DEV_SESSION_COOKIE not configured. Skipping daily.dev Squad live dispatch.", + ); + continue; + } + + const squadSourceId = "dc8ed5af-d1f6-423d-9f17-703d33946006"; + const baseCookie = dailyDevSession.includes("__Secure-dast=") + ? dailyDevSession + : `da2=m7Z6H7R0hwoOhyQZ1WW7K; __Secure-dast=${dailyDevSession}`; + + // 1. Refresh short-lived da3 JWT via /boot + const bootRes = await fetch("https://api.daily.dev/boot?v=3.47.11", { + headers: { + app: "webapp", + "Content-Type": "application/json", + Cookie: baseCookie, + Origin: "https://app.daily.dev", + Referer: "https://app.daily.dev/", + }, + }); + const refreshedCookies = + bootRes.headers + .getSetCookie?.() + ?.map((c) => c.split(";")[0]) + .join("; ") || baseCookie; + + const gqlHeaders = { + "Content-Type": "application/json", + Cookie: refreshedCookies, + Origin: "https://app.daily.dev", + Referer: "https://app.daily.dev/", + }; + + const existingSyndication = await client + .getDocument(syndicationDocId) + .catch(() => null); + const existingPostId = + existingSyndication?.externalId && + !String(existingSyndication.externalId).startsWith("simulated-") + ? String(existingSyndication.externalId) + : null; + + const commentary = ( + data.excerpt || + data.title || + "New on CodingCat.dev!" + ).slice(0, 240); + const coverImageUrl = + urlForImage(data.coverImage)?.width(1280).format("jpg").url() || ""; + + let postId = existingPostId; + if (!postId && data.dailydev) { + const match = data.dailydev.match(/-([A-Za-z0-9]{9})$/); + if (match) postId = match[1]; + } + + if (postId) { + const editRes = await fetch("https://api.daily.dev/graphql", { + method: "POST", + headers: gqlHeaders, + body: JSON.stringify({ + query: `mutation UpdateSquadPost($id: ID!, $commentary: String) { + editSharePost(id: $id, commentary: $commentary) { + id + } + }`, + variables: { id: postId, commentary }, + }), + }); + const editData = (await editRes.json()) as any; + if (editData?.errors) { + console.warn( + "[Syndicate Content] editSharePost warning, creating new Squad post:", + editData.errors, + ); + postId = null; + } + } + + if (!postId) { + const createRes = await fetch("https://api.daily.dev/graphql", { + method: "POST", + headers: gqlHeaders, + body: JSON.stringify({ + query: `mutation CreatePostInMultipleSources( + $sourceIds: [ID!]! + $title: String + $commentary: String + $imageUrl: String + $externalLink: String + ) { + createPostInMultipleSources( + sourceIds: $sourceIds + title: $title + commentary: $commentary + imageUrl: $imageUrl + externalLink: $externalLink + ) { + id + sourceId + type + slug + } + }`, + variables: { + sourceIds: [squadSourceId], + title: data.title || "Untitled", + commentary, + ...(coverImageUrl ? { imageUrl: coverImageUrl } : {}), + externalLink: canonicalUrl, + }, + }), + }); + const createData = (await createRes.json()) as any; + if (createData?.errors) { + throw new Error( + `daily.dev createPostInMultipleSources error: ${JSON.stringify(createData.errors)}`, + ); + } + postId = createData?.data?.createPostInMultipleSources?.[0]?.id; + } + + if (!postId) { + throw new Error("daily.dev Squad publish did not return a post ID"); + } + + const postRes = await fetch("https://api.daily.dev/graphql", { + method: "POST", + headers: gqlHeaders, + body: JSON.stringify({ + query: `query GetSquadPost($id: ID!) { + post(id: $id) { + id + commentsPermalink + permalink + } + }`, + variables: { id: postId }, + }), + }); + const postJson = (await postRes.json()) as any; + const externalUrl = + postJson?.data?.post?.commentsPermalink || + `https://daily.dev/posts/${postId}`; + + await client.createOrReplace({ + _id: syndicationDocId, + _type: "syndication", + target: { _type: "reference", _ref: data._id }, + platform: "dailydev", + status: "published", + canonicalUrl, + externalUrl, + externalId: String(postId), + syndicatedAt: new Date().toISOString(), + error: undefined, + }); + + if (data.dailydev !== externalUrl) { + parentPatch.dailydev = externalUrl; + } + + console.log( + `[Syndicate Content] Successfully published to daily.dev Squad: ${externalUrl}`, + ); } } catch (publishErr) { const errMsg = diff --git a/apps/sanity/schemas/partials/content.ts b/apps/sanity/schemas/partials/content.ts index 8e9b2eb8..8af0422c 100644 --- a/apps/sanity/schemas/partials/content.ts +++ b/apps/sanity/schemas/partials/content.ts @@ -87,6 +87,12 @@ const content = defineType({ type: "url", group: "data", }), + defineField({ + name: "dailydev", + title: "daily.dev Squad", + type: "url", + group: "data", + }), defineField({ name: "tags", title: "Tags", diff --git a/scripts/publish-php2shell-post.mjs b/scripts/publish-php2shell-post.mjs index 304cd682..aeea02d8 100644 --- a/scripts/publish-php2shell-post.mjs +++ b/scripts/publish-php2shell-post.mjs @@ -457,9 +457,23 @@ sudo systemctl restart php8.4-fpm apache2` coverImage: postDoc.coverImage, content: postDoc.content, awsbuilder: "https://builder.aws.com/content/3L50qXKr83Y2r9mA6ZsGhcc4cRJ/php-shell-cve-how-ai-found-a-decade-old-bug-in-of-the-web", + dailydev: "https://daily.dev/posts/two-critical-pre-auth-rce-flaws-in-php-cve-2026-109618-cve-2026-109619-uncovered-by-ai-models-f-lmbg2iyss", }, }, }, + { + createOrReplace: { + _id: "syndication.post-php2shell-cve-2026-109618.dailydev", + _type: "syndication", + target: { _type: "reference", _ref: postDoc._id }, + platform: "dailydev", + status: "published", + canonicalUrl: "https://codingcat.dev/post/php2shell-cve-2026-109618-ai-php-security-scramble", + externalUrl: "https://daily.dev/posts/two-critical-pre-auth-rce-flaws-in-php-cve-2026-109618-cve-2026-109619-uncovered-by-ai-models-f-lmbg2iyss", + externalId: "lmBG2IYsS", + syndicatedAt: "2026-10-24T03:38:28.000Z", + }, + }, { patch: { id: "LzgcvqoNSwz5R8loykZyIB", set: { excerpt: "Learn how to customize your Slack workspace theme using SlackThemes.net and contribute custom color themes on GitHub." } } }, { patch: { id: "ZKdo3vLUqcb4sYreiCMc5A", set: { excerpt: "Learn how to inspect and debug Angular 9 components directly inside the Chrome DevTools console using the new ng global object." } } }, { patch: { id: "ZKdo3vLUqcb4sYreiCPNnI", set: { excerpt: "Join Alex Patterson and Brittney Postma with special guests Zach DeRose, Facundo Giuliani, Swyx, Jason Lengstorf, and Scott Tolinski for a year-end Holiday AMA." } } }, diff --git a/scripts/run-syndicate-post.mjs b/scripts/run-syndicate-post.mjs index fb1738ce..38f7acf4 100644 --- a/scripts/run-syndicate-post.mjs +++ b/scripts/run-syndicate-post.mjs @@ -556,9 +556,165 @@ async function run() { } } + // --- DAILY.DEV SQUAD SYNDICATION --- + const dailyDevSession = process.env.DAILY_DEV_SESSION_COOKIE; + if (dailyDevSession) { + console.log("\n--- Syndicating to daily.dev Squad ---"); + const syndicationDocId = `syndication.${doc._id}.dailydev`; + const squadSourceId = "dc8ed5af-d1f6-423d-9f17-703d33946006"; + const baseCookie = dailyDevSession.includes("__Secure-dast=") + ? dailyDevSession + : `da2=m7Z6H7R0hwoOhyQZ1WW7K; __Secure-dast=${dailyDevSession}`; + + try { + const bootRes = await fetch("https://api.daily.dev/boot?v=3.47.11", { + headers: { + app: "webapp", + "Content-Type": "application/json", + Cookie: baseCookie, + Origin: "https://app.daily.dev", + Referer: "https://app.daily.dev/", + }, + }); + const refreshedCookies = + bootRes.headers + .getSetCookie?.() + ?.map((c) => c.split(";")[0]) + .join("; ") || baseCookie; + + const gqlHeaders = { + "Content-Type": "application/json", + Cookie: refreshedCookies, + Origin: "https://app.daily.dev", + Referer: "https://app.daily.dev/", + }; + + const existingSyndication = await client.getDocument(syndicationDocId).catch(() => null); + let postId = + existingSyndication?.externalId && + !String(existingSyndication.externalId).startsWith("simulated-") + ? String(existingSyndication.externalId) + : doc._id === "post-php2shell-cve-2026-109618" + ? "lmBG2IYsS" + : null; + + const commentary = (doc.excerpt || doc.title || "New on CodingCat.dev!").slice(0, 240); + const coverImageUrl = urlForImage(doc.coverImage)?.width(1280).format("jpg").url() || ""; + + if (!postId && doc.dailydev) { + const match = doc.dailydev.match(/-([A-Za-z0-9]{9})$/); + if (match) postId = match[1]; + } + + if (postId) { + const editRes = await fetch("https://api.daily.dev/graphql", { + method: "POST", + headers: gqlHeaders, + body: JSON.stringify({ + query: `mutation UpdateSquadPost($id: ID!, $commentary: String) { + editSharePost(id: $id, commentary: $commentary) { + id + } + }`, + variables: { id: postId, commentary }, + }), + }); + const editData = await editRes.json(); + if (editData?.errors) { + console.warn("daily.dev editSharePost warning, creating new Squad post:", editData.errors); + postId = null; + } + } + + if (!postId) { + const createRes = await fetch("https://api.daily.dev/graphql", { + method: "POST", + headers: gqlHeaders, + body: JSON.stringify({ + query: `mutation CreatePostInMultipleSources( + $sourceIds: [ID!]! + $title: String + $commentary: String + $imageUrl: String + $externalLink: String + ) { + createPostInMultipleSources( + sourceIds: $sourceIds + title: $title + commentary: $commentary + imageUrl: $imageUrl + externalLink: $externalLink + ) { + id + sourceId + type + slug + } + }`, + variables: { + sourceIds: [squadSourceId], + title: doc.title || "Untitled", + commentary, + ...(coverImageUrl ? { imageUrl: coverImageUrl } : {}), + externalLink: canonicalUrl, + }, + }), + }); + const createData = await createRes.json(); + if (createData?.errors) { + throw new Error( + `daily.dev createPostInMultipleSources error: ${JSON.stringify(createData.errors)}` + ); + } + postId = createData?.data?.createPostInMultipleSources?.[0]?.id; + } + + if (!postId) { + throw new Error("daily.dev Squad publish did not return a post ID"); + } + + const postRes = await fetch("https://api.daily.dev/graphql", { + method: "POST", + headers: gqlHeaders, + body: JSON.stringify({ + query: `query GetSquadPost($id: ID!) { + post(id: $id) { + id + commentsPermalink + permalink + } + }`, + variables: { id: postId }, + }), + }); + const postJson = await postRes.json(); + const externalUrl = + postJson?.data?.post?.commentsPermalink || `https://daily.dev/posts/${postId}`; + + console.log(`✓ daily.dev Squad published: ${externalUrl}`); + + await client.createOrReplace({ + _id: syndicationDocId, + _type: "syndication", + target: { _type: "reference", _ref: doc._id }, + platform: "dailydev", + status: "published", + canonicalUrl, + externalUrl, + externalId: String(postId), + syndicatedAt: new Date().toISOString(), + }); + + await client.patch(doc._id).set({ dailydev: externalUrl }).commit(); + console.log(`✓ Updated Sanity doc ${doc._id} dailydev URL`); + } catch (err) { + console.error("✗ daily.dev Squad syndication error:", err); + } + } + // --- LINK SYNDICATION REFERENCES ON PARENT --- try { - const platforms = ["devto", "hashnode", ...(shouldSyndicateAws ? ["awsbuilder"] : [])]; + const platforms = ["devto", "hashnode", "dailydev", ...(shouldSyndicateAws ? ["awsbuilder"] : [])]; const candidateRefs = platforms.map((p) => `syndication.${doc._id}.${p}`); const existingSynDocs = await Promise.all( candidateRefs.map((ref) => client.getDocument(ref).catch(() => null))