Files
songs2vid/lib/youtube/upload.ts
T

112 lines
3.4 KiB
TypeScript

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<string> {
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",
};
}