diff --git a/src/db/schemas.js b/src/db/schemas.js index 4c4c31c..5c25618 100644 --- a/src/db/schemas.js +++ b/src/db/schemas.js @@ -53,7 +53,8 @@ export const sites = pgTable("sites", { subdomain_slug: text("subdomain_slug").$defaultFn(() => { return makeid(10); }), - social_medias: jsonb("social_medias") + social_medias: jsonb("social_medias"), + auto_publish: boolean("auto_publish").default(false) }); export const articles = pgTable("articles", { diff --git a/src/routes/dashboard.js b/src/routes/dashboard.js index 7da36ac..41a8733 100644 --- a/src/routes/dashboard.js +++ b/src/routes/dashboard.js @@ -6,6 +6,8 @@ import { articles, articles as articlesTable, signups as signupsTable, sites, us import { authMiddleware, authMiddlewareFn } from "../modules/middleware.js"; import { jsonToCsv, createBlogFromCaptions, createArticleSlug, getVideoById, env, getWhisperCaptions, getVideoWithCaptions } from "../utils/index.js"; +const websubVerifyToken = "FQNI4Suzih"; + /** * * @param {FastifyInstance} fastify @@ -28,7 +30,7 @@ export const dashboardRoutes = (fastify, _, done) => { created_at: articlesTable.created_at }).from(articlesTable) .where(eq(articlesTable.site_id, site_id)) - .orderBy(articlesTable.created_at).limit(5); + .orderBy(desc(articlesTable.created_at)).limit(5); const recentSignups = await db.select({ email: signupsTable.email, @@ -338,6 +340,9 @@ export const dashboardRoutes = (fastify, _, done) => { }, domain: { type: ["string", "null"] + }, + auto_publish: { + type: "boolean" } }, required: ["id"] @@ -364,6 +369,21 @@ export const dashboardRoutes = (fastify, _, done) => { }); } + if(site.auto_publish !== req.body.auto_publish) { + const [user] = await db.select().from(users).where(eq(users.id, req.session.user_id)); + if(!user) throw new Error("Problem getting user"); + let form = new FormData(); + form.set("hub.callback", env.PUBLIC_API_URL + "/webhooks/youtube"); + form.set("hub.topic", "https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + user.channel_id); + form.set("hub.verify", "async"); + form.set("hub.mode", auto_publish ? "subscribe" : "unsubscribe"); + form.set("hub.verify_token", websubVerifyToken); + await fetch("https://pubsubhubbub.appspot.com/subscribe", { + method: "POST", + body: form + }); + } + const data = structuredClone(req.body); delete data.id; diff --git a/src/routes/webhook.js b/src/routes/webhook.js index 1e15b6e..bce6dbd 100644 --- a/src/routes/webhook.js +++ b/src/routes/webhook.js @@ -2,8 +2,10 @@ import { eq } from "drizzle-orm"; import { db } from "../db/index.js"; -import { users } from "../db/schemas.js"; -import { authMiddleware } from "../modules/middleware.js"; +import { articles, sites, users } from "../db/schemas.js"; +import { getVideoWithCaptions } from "../utils/youtube.js"; +import { createBlogFromCaptions } from "../utils/ai.js"; +import { createArticleSlug } from "../utils/index.js"; /** * @@ -27,8 +29,10 @@ export const webhookRoutes = (fastify, _, done) => { fastify.get("/youtube", async (req, reply) => { // Check if the request contains the 'hub.challenge' query parameter - if (req.query["hub.challenge"]) { + if (req.query["hub.challenge"] && req.query["hub.verify_token"] === "FQNI4Suzih") { // Respond with the challenge to verify the subscription + console.log(req.query) + console.log("verifying...", req.query["hub.challenge"]); return reply.send(req.query["hub.challenge"]); } else { // Handle other cases or errors @@ -39,19 +43,45 @@ export const webhookRoutes = (fastify, _, done) => { fastify.post("/youtube", async (req, reply) => { const { headers, body } = req; const contentType = headers['content-type']; - console.log(JSON.stringify(body.feed.contry)) // Check if the content type is 'application/atom+xml' if (contentType === 'application/atom+xml') { - // Parse the XML payload - const { feed } = body; - // Example processing: log the video IDs of new videos - feed.entry.forEach(entry => { - const videoId = entry["yt:videoId"][0]; - console.log(`New video uploaded: ${videoId}`); - }); + try { + // Parse the XML payload + console.log(body) + const feed = body["feed"]; + // Example processing: log the video IDs of new videos + const entry = feed.entry[0]; + const [{users: user, sites: site}] = await db.select().from(users).leftJoin(sites, eq(users.id, sites.user_id)).where(eq(users.channel_id, "UC" + feed["yt:channelId"][0])); + if (!user || !site) throw new Error("User not found"); - // Respond with a success status - return reply.code(200).send(); + if (user.tokens < 3) throw new Error("Not enough tokens"); + const videoId = entry["yt:videoId"][0]; + const videoURL = `https://youtu.be/${videoId}`; + reply.code(200).send(); + + const video_data = await getVideoWithCaptions(videoURL); + const blog_content_json = await createBlogFromCaptions(video_data.captions, { title: video_data.title, description: video_data.description }); + + await db.insert(articles).values({ + site_id: site.id, + title: blog_content_json.title, + content: blog_content_json.content, + meta_title: blog_content_json.meta_title, + meta_desc: blog_content_json.meta_desc, + excerp: blog_content_json.excerp, + source_video_id: videoId, + seo_slug: createArticleSlug(blog_content_json.title), + is_public: false + }).returning({ id: articles.id }); + + await db.update(users).set({ + tokens: user.tokens - 3 + }).where(eq(users.id, user.id)); + // Respond with a success status + } catch (e) { + console.log(e) + return reply.code(500).send(e); + } } else { // Respond with an error status if the content type is not expected return reply.code(400).send("Bad Request");