Compare commits

...

3 Commits

Author SHA1 Message Date
e6a8ef5f9e added logic for auto-publish 2024-06-13 14:45:43 +02:00
8412cbc572 fix xml parser 2024-06-13 14:45:06 +02:00
b4c44e7622 fix createBlogFromCaptions function default values 2024-06-13 14:44:55 +02:00
5 changed files with 80 additions and 32 deletions

View File

@ -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", {

View File

@ -58,14 +58,10 @@ export const main = async () => {
callbackUri: `${env.PUBLIC_API_URL}/auth/google/callback`
});
server.addContentTypeParser(['text/xml', 'application/xml', 'application/atom+xml'], { parseAs: 'string' }, async (request, payload, done) => {
try {
let parsed = await xml2js.parseStringPromise(payload);
done(null, parsed)
} catch(e) {
console.log(e);
done(e, undefined)
}
server.addContentTypeParser(['text/xml', 'application/xml', 'application/atom+xml'], { parseAs: 'string' }, function (request, payload, done) {
xml2js.parseString(payload, function (err, body) {
done(err, body)
})
})
// Routes

View File

@ -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;

View File

@ -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') {
try {
// Parse the XML payload
const { feed } = body;
console.log(body)
const feed = body["feed"];
// 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}`);
});
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");
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
return reply.code(200).send();
} 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");

View File

@ -71,13 +71,14 @@ async function promptGPT(prompt, { model, is_json } = { model: "gpt-3.5-turbo",
export async function createBlogFromCaptions(captions, {
title,
description
}, {
}, options = {}) {
const {
length,
language,
format,
tone,
faq
} = { length: 700, language: "English", format: "summary", tone: "informal", faq: false }) {
} = Object.assign({ length: 700, language: "English", format: "summary", tone: "informal", faq: false }, options);
// const prompt = `Convert the following video transcript into a blog post. The approximate length should be around ${length || 500} characters, written in ${language || "English"}. The desired format of the blog post is a ${format || "summary"}. Please ensure the blog post has a ${tone || "informal"} tone throughout. Use markdown to format the article. You must always respond in the following json fromat: {"title": string, "content": string, "seo_friendly_slug": string}. \nHere is the transcript: `
// const prompt = `Convert the following video transcript into an engaging blog post. You must always respond in the following json fromat: {"title": string, "body": string, "seo_friendly_slug": string}. Do not, under any circumstance, include the title inside the body, it should only be reserved for the body of the article. Use markdown to format the article. Use "\\n" to add line-breaks. \nHere is the transcript: `;