feat: Add function to get author channel URL

This commit is contained in:
Peifan Li
2025-12-16 14:14:55 -05:00
parent 0ba6e207f3
commit a7a4eae713
3 changed files with 101 additions and 4 deletions

View File

@@ -207,3 +207,77 @@ export const updateVideoDetails = async (
video: updatedVideo,
});
};
/**
* Get author channel URL for a video
* Errors are automatically handled by asyncHandler middleware
*/
export const getAuthorChannelUrl = async (
req: Request,
res: Response
): Promise<void> => {
const { sourceUrl } = req.query;
if (!sourceUrl || typeof sourceUrl !== "string") {
throw new ValidationError("sourceUrl is required", "sourceUrl");
}
try {
// Check if it's a YouTube URL
if (sourceUrl.includes("youtube.com") || sourceUrl.includes("youtu.be")) {
const { executeYtDlpJson, getNetworkConfigFromUserConfig, getUserYtDlpConfig } = await import("../utils/ytDlpUtils");
const userConfig = getUserYtDlpConfig(sourceUrl);
const networkConfig = getNetworkConfigFromUserConfig(userConfig);
const info = await executeYtDlpJson(sourceUrl, {
...networkConfig,
noWarnings: true,
});
const channelUrl = info.channel_url || info.uploader_url || null;
if (channelUrl) {
res.status(200).json({ success: true, channelUrl });
return;
}
}
// Check if it's a Bilibili URL
if (sourceUrl.includes("bilibili.com") || sourceUrl.includes("b23.tv")) {
const axios = (await import("axios")).default;
const { extractBilibiliVideoId } = await import("../utils/helpers");
const videoId = extractBilibiliVideoId(sourceUrl);
if (videoId) {
try {
// Handle both BV and av IDs
const isBvId = videoId.startsWith("BV");
const apiUrl = isBvId
? `https://api.bilibili.com/x/web-interface/view?bvid=${videoId}`
: `https://api.bilibili.com/x/web-interface/view?aid=${videoId.replace("av", "")}`;
const response = await axios.get(apiUrl, {
headers: {
Referer: "https://www.bilibili.com",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
},
});
if (response.data && response.data.data && response.data.data.owner?.mid) {
const mid = response.data.data.owner.mid;
const spaceUrl = `https://space.bilibili.com/${mid}`;
res.status(200).json({ success: true, channelUrl: spaceUrl });
return;
}
} catch (error) {
logger.error("Error fetching Bilibili video info:", error);
}
}
}
// If we couldn't get the channel URL, return null
res.status(200).json({ success: true, channelUrl: null });
} catch (error) {
logger.error("Error getting author channel URL:", error);
res.status(200).json({ success: true, channelUrl: null });
}
};

View File

@@ -20,6 +20,10 @@ router.post(
asyncHandler(videoController.uploadVideo)
);
router.get("/videos", asyncHandler(videoController.getVideos));
router.get(
"/videos/author-channel-url",
asyncHandler(videoController.getAuthorChannelUrl)
);
router.get("/videos/:id", asyncHandler(videoController.getVideoById));
router.put("/videos/:id", asyncHandler(videoController.updateVideoDetails));
router.delete("/videos/:id", asyncHandler(videoController.deleteVideo));

View File

@@ -138,11 +138,30 @@ const VideoPlayer: React.FC = () => {
return [];
}, [collections, activeCollectionVideoId]);
// Handle navigation to author videos page
const handleAuthorClick = () => {
if (video) {
navigate(`/author/${encodeURIComponent(video.author)}`);
// Handle navigation to author videos page or external channel
const handleAuthorClick = async () => {
if (!video) return;
// If it's a YouTube or Bilibili video, try to get the channel URL
if (video.source === 'youtube' || video.source === 'bilibili') {
try {
const response = await axios.get(`${API_URL}/videos/author-channel-url`, {
params: { sourceUrl: video.sourceUrl }
});
if (response.data.success && response.data.channelUrl) {
// Open the channel URL in a new tab
window.open(response.data.channelUrl, '_blank', 'noopener,noreferrer');
return;
}
} catch (error) {
console.error('Error fetching author channel URL:', error);
// Fall through to default behavior
}
}
// Default behavior: navigate to author videos page
navigate(`/author/${encodeURIComponent(video.author)}`);
};
const handleCollectionClick = (collectionId: string) => {