import { google } from "googleapis"; import fs from "fs"; import { prisma } from "../db"; import { parseTags } from "../constants"; import type { JobItem } from "@prisma/client"; async function refreshAccessToken(refreshToken: string) { const oauth2Client = new google.auth.OAuth2( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, ); oauth2Client.setCredentials({ refresh_token: refreshToken }); const { credentials } = await oauth2Client.refreshAccessToken(); return credentials; } export async function getYouTubeClient(userId: string) { const connection = await prisma.youTubeConnection.findUnique({ where: { userId }, }); if (!connection) throw new Error("YouTube account not connected"); const oauth2Client = new google.auth.OAuth2( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, ); let accessToken = connection.accessToken; let expiresAt = connection.expiresAt; if (new Date() >= expiresAt) { const credentials = await refreshAccessToken(connection.refreshToken); if (!credentials.access_token) { throw new Error("Failed to refresh YouTube access token"); } accessToken = credentials.access_token; expiresAt = credentials.expiry_date ? new Date(credentials.expiry_date) : new Date(Date.now() + 3600 * 1000); await prisma.youTubeConnection.update({ where: { userId }, data: { accessToken, expiresAt }, }); } oauth2Client.setCredentials({ access_token: accessToken, refresh_token: connection.refreshToken, }); return google.youtube({ version: "v3", auth: oauth2Client }); } export async function uploadToYouTube( userId: string, videoPath: string, item: JobItem, ): Promise { const youtube = await getYouTubeClient(userId); const tags = parseTags(item.tags); const privacyStatus = item.privacy === "PUBLIC" ? "public" : item.privacy === "PRIVATE" ? "private" : "unlisted"; const response = await youtube.videos.insert({ part: ["snippet", "status"], notifySubscribers: item.notifySubscribers, requestBody: { snippet: { title: item.title, description: item.description, tags: tags.length > 0 ? tags : undefined, categoryId: item.categoryId, }, status: { privacyStatus, embeddable: item.embeddable, selfDeclaredMadeForKids: item.madeForKids, licenseId: item.creativeCommons ? "creativeCommon" : "youtube", }, }, media: { body: fs.createReadStream(videoPath), }, }); const videoId = response.data.id; if (!videoId) throw new Error("YouTube upload succeeded but no video ID returned"); return videoId; } export async function fetchYouTubeChannel(accessToken: string, refreshToken: string) { const oauth2Client = new google.auth.OAuth2( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, ); oauth2Client.setCredentials({ access_token: accessToken, refresh_token: refreshToken }); const youtube = google.youtube({ version: "v3", auth: oauth2Client }); const response = await youtube.channels.list({ part: ["snippet"], mine: true }); const channel = response.data.items?.[0]; if (!channel?.id) throw new Error("No YouTube channel found for this account"); return { channelId: channel.id, channelTitle: channel.snippet?.title || "Unknown Channel", }; }