返回 AiToEarn
youtube.service.ts
根目录 / project / aitoearn-electron / server / src / modules / plat / youtube / youtube.service.ts
1 /*
2 * @Author: zhangwei
3 * @Date: 2025-05-15 20:59:55
4 * @LastEditTime: 2025-04-27 17:58:21
5 * @LastEditors: zhangwei
6 * @Description: youtube
7 */
8 import { Injectable, BadRequestException } from '@nestjs/common';
9 import { InjectModel } from '@nestjs/mongoose';
10 import axios from 'axios';
11 import { v4 as uuidv4 } from 'uuid';
12
13 const multer = require('multer');
14 const readline = require('readline');
15 const { google } = require('googleapis');
16 const OAuth2 = google.auth.OAuth2;
17 import { GoogleService } from '../google/google.service';
18 import { Readable } from 'stream';
19 import { Model } from 'mongoose';
20 import { PubType, PubStatus, PubRecord } from 'src/db/schema/pubRecord.schema'
21
22 // 配置 multer 存储设置(内存存储或本地存储)
23 const storage = multer.memoryStorage(); // 存储在内存中
24 const upload = multer({ storage: storage });
25
26 @Injectable()
27 export class YoutubeService {
28 private youtubeService = google.youtube('v3');
29
30 constructor(
31 private oauth2Service: GoogleService,
32 @InjectModel(PubRecord.name)
33 private readonly PubRecordModel: Model<PubRecord>,
34
35 ) {}
36
37 /**
38 * 获取频道列表
39 * @param userId 用户ID
40 * @param handle 频道handle
41 * @param userName 用户名
42 * @param id 频道ID
43 * @param mine 是否查询自己的频道
44 * @returns 频道列表
45 */
46 async getChannelsList(accessToken, handle, userName, id, mine) {
47 // 根据传入的参数来选择一个有效的请求参数
48 let requestParams: any = {
49 access_token: accessToken, // 使用授权的 access token
50 part: 'contentOwnerDetails, snippet, contentDetails, statistics, status, topicDetails',
51 };
52
53 // 根据参数选择 `id` 或 `forUsername`
54 if (id) {
55 requestParams.id = id; // 如果提供了 id, 使用 id
56 } else if (handle) {
57 requestParams.forHandle = handle; // 如果提供了 handle, 使用 handle
58 } else if (userName) {
59 requestParams.forUsername = userName; // 如果提供了 userName, 使用 userName
60 } else if (mine !== undefined) {
61 // 如果 mine 被传递且是布尔值, 可以检查是否为 `true`
62 if (mine) {
63 requestParams.mine = true; // 请求当前登录用户的频道
64 }
65 }
66
67 try {
68 const response = await this.youtubeService.channels.list(requestParams);
69
70 const channels = response.data;
71 console.log(channels);
72 if (channels.length === 0) {
73 console.log('No channel found.');
74 return [];
75 } else {
76 console.log(`This channel's ID is ${channels}`);
77 return channels;
78 }
79 } catch (err) {
80 console.log('The API returned an error: ' + err);
81 return err;
82 }
83 }
84
85 /**
86 * 更新频道
87 * @param accessToken
88 * @param ChannelId 频道ID
89 * @param brandingSettings 品牌设置
90 * @param status 状态
91 * @returns 更新结果
92 */
93 async updateChannels(accessToken, ChannelId, brandingSettings, status) {
94 try {
95 // 设置 OAuth2 客户端凭证
96 this.oauth2Service.setCredentials(accessToken);
97 const oauth2Client = this.oauth2Service.getClient();
98
99 // 构造请求体
100 const requestBody: any = {
101 id: ChannelId,
102 // snippet: {
103 // playlistId: playlistId,
104 // resourceId: resourceId,
105 // },
106 // contentDetails: {},
107 };
108
109 // 如果传递了 note,则添加到请求体
110 if (brandingSettings !== undefined) {
111 requestBody.brandingSettings = brandingSettings;
112 }
113 if (status !== undefined) {
114 requestBody.status = status;
115 }
116
117 console.log(requestBody);
118
119 // 调用 YouTube API 上传视频
120 const response = await this.youtubeService.channelSections.update(
121 {
122 auth: oauth2Client,
123 part: 'brandingSettings',
124 requestBody
125 }
126 );
127
128 // 返回上传的视频 ID
129 if (response.data) {
130 console.log('Channels update successfully:', response.data);
131 return response.data;
132 } else {
133 return 'Channels updated failed';
134 }
135 } catch (error) {
136 console.error('Error Channels update:', error);
137 return error;
138 }
139
140 }
141
142 /**
143 * 获取频道板块列表
144 * @param accessToken
145 * @param channelId 频道ID
146 * @param id 板块ID
147 * @param mine 是否查询自己的板块
148 * @param maxResults 最大结果数
149 * @param pageToken 分页令牌
150 * @returns 频道板块列表
151 */
152 async getChannelSectionsList(accessToken, channelId, id, mine, maxResults, pageToken) {
153 // 根据传入的参数来选择一个有效的请求参数
154 let requestParams: any = {
155 access_token: accessToken, // 使用授权的 access token
156 part: 'contentDetails, id, snippet',
157 };
158
159 // 根据参数选择 `id` 或 `forUsername`
160 if (id) {
161 requestParams.id = id; // 如果提供了 id, 使用 id
162 } else if (channelId) {
163 requestParams.channelId = channelId; // 如果提供了 handle, 使用 handle
164 } else if (mine !== undefined) {
165 // 如果 mine 被传递且是布尔值, 可以检查是否为 `true`
166 if (mine) {
167 requestParams.mine = true; // 请求当前登录用户的频道
168 }
169 } else if (maxResults) {
170 requestParams.maxResults = maxResults; // 如果提供了 handle, 使用 handle
171 } else if (pageToken) {
172 requestParams.pageToken = pageToken; // 如果提供了 handle, 使用 handle
173 }
174
175 try {
176 const response = await this.youtubeService.channelSections.list(requestParams);
177 const sections = response.data;
178 console.log(sections);
179 if (sections.length === 0) {
180 console.log('No sections found.');
181 return [];
182 } else {
183 console.log(`This sections's ID is ${sections}.`);
184 return sections;
185 }
186 } catch (err) {
187 console.log('The API returned an error: ' + err);
188 return err;
189 }
190 }
191
192 /**
193 * 创建频道板块。
194 *
195 * @param snippet 元数据
196 * @param contentDetails 内容详情
197 * @returns 创建结果
198 */
199 async insertChannelSection(accessToken, snippet, contentDetails) {
200 try {
201 // 设置 OAuth2 客户端凭证
202 this.oauth2Service.setCredentials(accessToken);
203 const oauth2Client = this.oauth2Service.getClient();
204
205 // 构造请求体
206 const requestBody = {
207 snippet: snippet,
208 contentDetails: contentDetails,
209 };
210
211 console.log(requestBody);
212
213 // 调用 YouTube API 上传视频
214 const response = await this.youtubeService.channelSections.insert(
215 {
216 auth: oauth2Client,
217 part: 'snippet,id, contentDetails',
218 requestBody
219 }
220 );
221 // 返回上传的视频 ID
222 if (response.data) {
223 console.log('Channel Section insert successfully:', response.data);
224 return response.data;
225 } else {
226 return 'Channel Section insert failed';
227 }
228 } catch (error) {
229 console.error('Error Channel Section insert:', error);
230 return error;
231 }
232 }
233
234 /**
235 * 更新频道板块。
236 * @param snippet 元数据
237 * @param contentDetails 内容详情
238 * @returns 创建结果
239 */
240 async updateChannelSection(accessToken, snippet, contentDetails) {
241 try {
242 // 设置 OAuth2 客户端凭证
243 this.oauth2Service.setCredentials(accessToken);
244 const oauth2Client = this.oauth2Service.getClient();
245
246 // 构造请求体
247 const requestBody = {
248 snippet: snippet,
249 contentDetails: contentDetails,
250 };
251
252 console.log(requestBody);
253
254 // 调用 YouTube API 上传视频
255 const response = await this.youtubeService.channelSections.update(
256 {
257 auth: oauth2Client,
258 part: 'snippet,id, contentDetails',
259 requestBody
260 }
261 );
262 // 返回上传的视频 ID
263 if (response.data) {
264 console.log('Playlist insert successfully:', response.data);
265 return response.data;
266 } else {
267 return 'Video upload failed';
268 }
269 } catch (error) {
270 console.error('Error uploading video:', error);
271 return error;
272 }
273 }
274
275 /**
276 * 删除频道板块
277 * @param channelSectionId 频道板块ID
278 * @returns 删除结果
279 */
280 async deleteChannelsSections(accessToken, channelSectionId) {
281 // 设置 OAuth2 客户端凭证
282 this.oauth2Service.setCredentials(accessToken);
283 const oauth2Client = this.oauth2Service.getClient();
284
285 try {
286 const response = await this.youtubeService.channelSections.delete({
287 auth: oauth2Client,
288 id: channelSectionId,
289 });
290 console.log('Video deleted:', response.data);
291 } catch (error) {
292 console.error('Error deleting video:', error);
293 return error
294 }
295 }
296
297 /**
298 * 获取频道板块列表。
299 * @param parentId 父评论ID
300 * @param id 评论ID
301 * @param maxResults 最大结果数
302 * @param pageToken 分页令牌
303 * @returns 评论列表
304 */
305 async getCommentsList(accessToken, parentId, id, maxResults, pageToken) {
306 // 根据传入的参数来选择一个有效的请求参数
307 let requestParams: any = {
308 access_token: accessToken, // 使用授权的 access token
309 part: 'id, snippet',
310 };
311
312 // 根据参数选择 `id` 或 `forUsername`
313 if (id) {
314 requestParams.id = id; // 如果提供了 id, 使用 id
315 } else if (parentId) {
316 requestParams.channelId = parentId; // 如果提供了 handle, 使用 handle
317 } else if (maxResults) {
318 requestParams.maxResults = maxResults; // 如果提供了 handle, 使用 handle
319 } else if (pageToken) {
320 requestParams.pageToken = pageToken; // 如果提供了 handle, 使用 handle
321 }
322
323 try {
324 const response = await this.youtubeService.comments.list(requestParams);
325 const sections = response.data;
326 console.log(sections);
327 if (sections.length === 0) {
328 console.log('No sections found.');
329 return [];
330 } else {
331 console.log(`This sections's ID is ${sections}.`);
332 return sections;
333 }
334 } catch (err) {
335 console.log('The API returned an error: ' + err);
336 return err;
337 }
338 }
339
340 /**
341 * 创建对现有评论的回复
342 * @param snippet 元数据
343 * @returns 创建结果
344 */
345 async insertComment(accessToken, snippet) {
346 try {
347 // 设置 OAuth2 客户端凭证
348 this.oauth2Service.setCredentials(accessToken);
349 const oauth2Client = this.oauth2Service.getClient();
350
351 // 构造请求体
352 const requestBody = {
353 snippet: snippet
354 };
355
356 console.log(requestBody);
357
358 // 调用 YouTube API 上传视频
359 const response = await this.youtubeService.comments.insert(
360 {
361 auth: oauth2Client,
362 part: 'snippet,id',
363 requestBody
364 }
365 );
366 // 返回上传的视频 ID
367 if (response.data) {
368 console.log('Playlist insert successfully:', response.data);
369 return response.data;
370 } else {
371 return 'Video upload failed';
372 }
373 } catch (error) {
374 console.error('Error uploading video:', error);
375 return error;
376 }
377 }
378
379 /**
380 * 更新评论。
381 * @param snippet 元数据
382 * @returns 创建结果
383 */
384 async updateComments(accessToken, snippet) {
385 try {
386 // 设置 OAuth2 客户端凭证
387 this.oauth2Service.setCredentials(accessToken);
388 const oauth2Client = this.oauth2Service.getClient();
389
390 // 构造请求体
391 const requestBody = {
392 snippet: snippet
393 };
394
395 console.log(requestBody);
396
397 // 调用 YouTube API 上传视频
398 const response = await this.youtubeService.comments.update(
399 {
400 auth: oauth2Client,
401 part: 'snippet,id',
402 requestBody
403 }
404 );
405 // 返回上传的视频 ID
406 if (response.data) {
407 console.log('Playlist insert successfully:', response.data);
408 return response.data;
409 } else {
410 return 'Video upload failed';
411 }
412 } catch (error) {
413 console.error('Error uploading video:', error);
414 return error;
415 }
416 }
417
418 /**
419 * 设置一条或多条评论的审核状态。
420 * @param id 评论ID
421 * @param moderationStatus 审核状态
422 * @param banAuthor 是否禁止作者
423 * @returns 设置结果
424 */
425 async setModerationStatusComments(accessToken, id, moderationStatus, banAuthor) {
426 try {
427 // 设置 OAuth2 客户端凭证
428 this.oauth2Service.setCredentials(accessToken);
429 const oauth2Client = this.oauth2Service.getClient();
430
431 // 构造请求体
432 const requestBody = {
433 id: id,
434 moderationStatus:moderationStatus, // heldForReview 等待管理员审核 published - 清除要公开显示的评论。 rejected - 不显示该评论
435 banAuthor: banAuthor // 自动拒绝评论作者撰写的任何其他评论 将作者加入黑名单
436 };
437
438 console.log(requestBody);
439
440 // 调用 YouTube API 上传视频
441 const response = await this.youtubeService.comments.setModerationStatus(
442 {
443 auth: oauth2Client,
444 part: 'snippet,id',
445 requestBody
446 }
447 );
448 // 返回上传的视频 ID
449 if (response.data) {
450 console.log('Playlist insert successfully:', response.data);
451 return response.data;
452 } else {
453 return 'Video upload failed';
454 }
455 } catch (error) {
456 console.error('Error uploading video:', error);
457 return error;
458 }
459 }
460
461 /**
462 * 删除评论
463 * @param id 评论ID
464 * @returns 删除结果
465 */
466 async deleteComments(accessToken, id) {
467 // 设置 OAuth2 客户端凭证
468 this.oauth2Service.setCredentials(accessToken);
469 const oauth2Client = this.oauth2Service.getClient();
470
471 try {
472 const response = await this.youtubeService.comments.delete({
473 auth: oauth2Client,
474 id: id,
475 });
476 console.log('Video deleted:', response.data);
477 } catch (error) {
478 console.error('Error deleting video:', error);
479 return error
480 }
481 }
482
483
484 /**
485 * 获取评论会话列表。
486 */
487 async getCommentThreadsList(accessToken, allThreadsRelatedToChannelId, id, videoId, maxResults, pageToken, order, searchTerms) {
488 // 根据传入的参数来选择一个有效的请求参数
489 let requestParams: any = {
490 access_token: accessToken, // 使用授权的 access token
491 part: 'id, snippet',
492 };
493
494 // 根据参数选择 `id` 或 `forUsername`
495 if (id) {
496 requestParams.id = id; // 如果提供了 id, 使用 id
497 } else if (allThreadsRelatedToChannelId) {
498 requestParams.allThreadsRelatedToChannelId = allThreadsRelatedToChannelId; // 如果提供了 handle, 使用 handle
499 } else if (maxResults) {
500 requestParams.maxResults = maxResults; // 如果提供了 handle, 使用 handle
501 } else if (pageToken) {
502 requestParams.pageToken = pageToken; // 如果提供了 handle, 使用 handle
503 }
504 else if (videoId) {
505 requestParams.videoId = videoId; // 如果提供了 handle, 使用 handle
506 }
507 else if (order) {
508 requestParams.order = order; // 如果提供了 handle, 使用 handle
509 }
510 else if (searchTerms) {
511 requestParams.searchTerms = searchTerms; // 如果提供了 handle, 使用 handle
512 }
513
514 try {
515 const response = await this.youtubeService.commentThreads.list(requestParams);
516 const sections = response.data;
517 console.log(sections);
518 if (sections.length === 0) {
519 console.log('No sections found.');
520 return [];
521 } else {
522 console.log(`This sections's ID is ${sections}.`);
523 return sections;
524 }
525 } catch (err) {
526 console.log('The API returned an error: ' + err);
527 return err;
528 }
529 }
530
531
532 /**
533 * 创建顶级评论
534 */
535 async insertCommentThreads(accessToken, snippet) {
536 try {
537 // 设置 OAuth2 客户端凭证
538 this.oauth2Service.setCredentials(accessToken);
539 const oauth2Client = this.oauth2Service.getClient();
540
541 // 构造请求体
542 const requestBody = {
543 snippet: snippet
544 };
545
546 console.log(requestBody);
547
548 // 调用 YouTube API 上传视频
549 const response = await this.youtubeService.commentThreads.insert(
550 {
551 auth: oauth2Client,
552 part: 'snippet,id',
553 requestBody
554 }
555 );
556 // 返回上传的视频 ID
557 if (response.data) {
558 console.log('Playlist insert successfully:', response.data);
559 return response.data;
560 } else {
561 return 'Video upload failed';
562 }
563 } catch (error) {
564 console.error('Error uploading video:', error);
565 return error;
566 }
567 }
568
569 /**
570 * 获取视频类别列表。
571 */
572 async getVideoCategoriesList(accessToken, id, regionCode) {
573
574 // 根据传入的参数来选择一个有效的请求参数
575 let requestParams: any = {
576 access_token: accessToken, // 使用授权的 access token
577 part: 'snippet',
578 };
579
580 // 根据参数选择 `id` 或 `forUsername`
581 if (id) {
582 requestParams.id = id; // 如果提供了 id, 使用 id
583 } else if (regionCode) {
584 requestParams.regionCode = regionCode; // 如果提供了 handle, 使用 handle
585 }
586
587 try {
588 const response = await this.youtubeService.videoCategories.list(requestParams);
589
590 const categories = response.data;
591 console.log(categories);
592 if (categories.length === 0) {
593 console.log('No categories found.');
594 return [];
595 } else {
596 console.log(`This categories's ID is ${categories}.`);
597 return categories;
598 }
599 } catch (err) {
600 console.log('The API returned an error: ' + err);
601 return err;
602 }
603 }
604
605 /**
606 * 获取视频列表。
607 * @param id 视频ID
608 * @param chart 图表类型
609 * @param maxResults 最大结果数
610 * @param pageToken 分页令牌
611 * @returns 视频列表
612 */
613 async getVideosList(accessToken, id, myRating, maxResults, pageToken) {
614 // 设置 OAuth2 客户端凭证
615 this.oauth2Service.setCredentials(accessToken);
616 const oauth2Client = this.oauth2Service.getClient();
617
618 // 根据传入的参数来选择一个有效的请求参数
619 let requestParams: any = {
620 auth: oauth2Client,
621 part: 'snippet,contentDetails,statistics, id, status, topicDetails',
622 // id: ids
623 };
624
625 // 根据参数选择 `id` 或 `forUsername`
626 if (id) {
627 requestParams.id = id; // 如果提供了 id, 使用 id
628 } else if (myRating !== undefined) {
629 // 如果 mine 被传递且是布尔值, 可以检查是否为 `true`
630 if (myRating) {
631 requestParams.myRating = myRating; // 请求当前登录用户的频道
632 }
633 } else if (maxResults) {
634 requestParams.maxResults = maxResults; // 如果提供了 handle, 使用 handle
635 } else if (pageToken) {
636 requestParams.pageToken = pageToken; // 如果提供了 handle, 使用 handle
637 }
638
639 try {
640 const response = await this.youtubeService.videos.list(requestParams);
641
642 const infos = response.data;
643 console.log(infos);
644 if (infos.length === 0) {
645 console.log('No categories found.');
646 return [];
647 } else {
648 console.log(`This infos's ID is ${infos}.`);
649 return infos;
650 }
651 } catch (err) {
652 console.log('The API returned an error: ' + err);
653 return err;
654 }
655 }
656
657 /**
658 * 上传视频。
659 * @param file 视频文件
660 * @param accountId 账号ID
661 * @param title 标题
662 * @param description 描述
663 * @param keywords 关键词
664 * @param categoryId 分类ID
665 * @param privacyStatus 状态(公开?私密)
666 * @returns 视频ID
667 */
668 async uploadVideo(userId, accountId, accessToken, file, title, description, keywords, categoryId, privacyStatus, publishAt) {
669 // 获取当前最大的 id
670 const maxRecord = await this.PubRecordModel.findOne().sort({ id: -1 });
671 const newId = maxRecord ? maxRecord.id + 1 : 1;
672 try {
673 // 设置 OAuth2 客户端凭证
674 this.oauth2Service.setCredentials(accessToken);
675 const oauth2Client = this.oauth2Service.getClient();
676
677 try {
678 const channelInfo = await this.youtubeService.channels.list({
679 part: ['snippet'],
680 mine: true,
681 auth: oauth2Client,
682 });
683
684 if (!channelInfo.data.items || channelInfo.data.items.length === 0) {
685 throw new Error('未检测到可用的 YouTube 频道,请先创建频道');
686 }
687
688 // 可以上传
689 } catch (err) {
690 if (err.errors?.[0]?.reason === 'youtubeSignupRequired') {
691 throw new Error('当前账号未启用 YouTube,请先创建频道');
692 }
693 }
694
695
696 // 准备视频的元数据
697 const fileStream = Readable.from(file.buffer); // 使用文件的 Buffer 转为可读取流
698 const fileSize = file.size; // 获取文件大小
699
700 // 构造请求体
701 let requestBody: any = {
702 snippet: {
703 title: title,
704 description: description,
705 // tags: keywords ? keywords.split(',') : [],
706 tags: keywords ? keywords : [],
707 categoryId: categoryId || '22', // 默认 categoryId 为 '22',如果没有指定
708 },
709 status: {
710 privacyStatus: privacyStatus, // 可以是 'public', 'private', 'unlisted'
711 },
712 };
713
714
715 // 创建发布记录
716 let newData: any = {
717 userId: userId,
718 type: PubType.VIDEO,
719 title: title,
720 desc: description,
721 accountId: accountId,
722 status:PubStatus.UNPUBLISH,
723 timingTime: publishAt,
724 publishTime: new Date()
725 }
726
727 if (publishAt) {
728 requestBody.status.publishAt = publishAt; // 如果提供了 publishAt 则使用 publishAt
729 }
730
731 console.log(requestBody);
732
733 await this.PubRecordModel.create({
734 ...newData,
735 id: newId,
736 });
737
738 // 调用 YouTube API 上传视频
739 const response = await this.youtubeService.videos.insert(
740 {
741 auth: oauth2Client,
742 part: 'snippet,status, id, contentDetails',
743 requestBody,
744 media: {
745 body: fileStream, // 上传的文件流
746 },
747 },
748 {
749 onUploadProgress: (e) => {
750 const progress = Math.round((e.bytesRead / fileSize) * 100);
751 console.log(`Uploading... ${progress}%`);
752 },
753 }
754 );
755
756 // const response = { "data": {
757 // "id": "7RckZHFBu7A"
758 // },}
759 // 返回上传的视频 ID
760 if (response.data.id) {
761 console.log('Video uploaded successfully, video ID:', response.data);
762 // return { videoId: response.data.id };
763 // 更新发布记录
764 await this.PubRecordModel.updateOne({ id:newId }, {
765 status: PubStatus.RELEASED,
766 publishTime: response.data.snippet.publishedAt,
767 coverPath: response.data.snippet.thumbnails.url
768 });
769
770 return response.data
771 } else {
772 await this.PubRecordModel.updateOne({ id:newId }, { status: PubStatus.FAIL });
773 return 'Video upload failed';
774 }
775 } catch (error) {
776 await this.PubRecordModel.updateOne({ id:newId }, { status: PubStatus.FAIL });
777 console.error('Error uploading video:', error);
778 return error;
779 }
780
781 }
782
783 /**
784 * 删除视频
785 */
786 async deleteVideo(accessToken, videoId) {
787 // 设置 OAuth2 客户端凭证
788 this.oauth2Service.setCredentials(accessToken);
789 const oauth2Client = this.oauth2Service.getClient();
790
791 try {
792 const response = await this.youtubeService.videos.delete({
793 auth: oauth2Client,
794 id: videoId,
795 });
796 // await this.PubRecordModel.updateOne({ id:videoId }, { status: PubStatus.FAIL });
797 console.log('Video deleted:', response.data);
798 } catch (error) {
799 console.error('Error deleting video:', error);
800 return error
801 }
802 }
803
804 /**
805 * 更新视频。
806 */
807 async updateVideo(accessToken, videoId, snippet, status, recordingDetails) {
808 try {
809 // 设置 OAuth2 客户端凭证
810 this.oauth2Service.setCredentials(accessToken);
811 const oauth2Client = this.oauth2Service.getClient();
812
813 const requestBody: any = {
814 id: videoId,
815 snippet: snippet,
816 status: status,
817 recordingDetails: recordingDetails
818 };
819 console.log(requestBody);
820
821 // 调用 YouTube API 上传视频
822 const response = await this.youtubeService.videos.update(
823 {
824 auth: oauth2Client,
825 part: 'snippet,status,id',
826 requestBody
827 }
828 );
829
830 // 返回上传的视频 ID
831 if (response.data) {
832 console.log('Playlist insert successfully:', response.data);
833 return response.data;
834 } else {
835 return 'Video upload failed';
836 }
837 } catch (error) {
838 console.error('Error uploading video:', error);
839 return error;
840 }
841
842 }
843
844
845 /**
846 * 创建播放列表。
847 */
848 async insertPlayList(accessToken, snippet, status) {
849 try {
850 // 设置 OAuth2 客户端凭证
851 this.oauth2Service.setCredentials(accessToken);
852 const oauth2Client = this.oauth2Service.getClient();
853
854 // 构造请求体
855 // const requestBody = {
856 // snippet: {
857 // title: title,
858 // description: description
859 // },
860 // status: {
861 // privacyStatus: privacyStatus, // 可以是 'public', 'private', 'unlisted'
862 // },
863 // };
864 const requestBody = {
865 snippet: snippet,
866 status: status,
867 };
868
869 console.log(requestBody);
870
871 // 调用 YouTube API 上传视频
872 const response = await this.youtubeService.playlists.insert(
873 {
874 auth: oauth2Client,
875 part: 'snippet,status, id, contentDetails',
876 requestBody
877 }
878 );
879 // 返回上传的视频 ID
880 if (response.data) {
881 console.log('Playlist insert successfully:', response.data);
882 return response.data;
883 } else {
884 return 'Video upload failed';
885 }
886 } catch (error) {
887 console.error('Error uploading video:', error);
888 return error;
889 }
890 }
891
892 /**
893 * 获取播放列表。
894 */
895 async getPlayList(accessToken, channelId, playListIds, mine, maxResults, pageToken) {
896 // 设置 OAuth2 客户端凭证
897 this.oauth2Service.setCredentials(accessToken);
898 const oauth2Client = this.oauth2Service.getClient();
899
900 // 根据传入的参数来选择一个有效的请求参数
901 let requestParams: any = {
902 auth: oauth2Client,
903 part: 'snippet,contentDetails, id, status, topicDetails, player',
904 // id: ids
905 };
906
907 // 根据参数选择 `id` 或 `forUsername`
908 if (playListIds) {
909 requestParams.ids = playListIds; // 如果提供了 id, 使用 id
910 } else if (channelId) {
911 requestParams.channelId = channelId; // 如果提供了 handle, 使用 handle
912 } else if (mine !== undefined) {
913 // 如果 mine 被传递且是布尔值, 可以检查是否为 `true`
914 if (mine) {
915 requestParams.mine = true; // 请求当前登录用户的频道
916 }
917 } else if (maxResults) {
918 requestParams.maxResults = maxResults; // 如果提供了 handle, 使用 handle
919 } else if (pageToken) {
920 requestParams.pageToken = pageToken; // 如果提供了 handle, 使用 handle
921 }
922
923 try {
924 const response = await this.youtubeService.playlist.list(requestParams);
925
926 const infos = response.data;
927 console.log(infos);
928 if (infos.length === 0) {
929 console.log('No categories found.');
930 return [];
931 } else {
932 console.log(`This infos's ID is ${infos}.`);
933 return infos;
934 }
935 } catch (err) {
936 console.log('The API returned an error: ' + err);
937 return err;
938 }
939 }
940
941 /**
942 * 更新播放列表。
943 */
944 async updatePlayList(accessToken, playListId, snippet, status) {
945 try {
946 // 设置 OAuth2 客户端凭证
947 this.oauth2Service.setCredentials(accessToken);
948 const oauth2Client = this.oauth2Service.getClient();
949
950 // 构造请求体
951 const requestBody: any = {
952 id: playListId, // 必填
953 snippet: snippet, // 类型断言
954 status: status // 类型断言
955 };
956
957 // 根据参数选择 `title`、`description`、`privacyStatus` 或 `podcastStatus`
958
959 // if (description) {
960 // requestBody.snippet.description = description; // 如果提供了 id, 使用 id
961 // }
962
963 // if (privacyStatus || podcastStatus) {
964 // if (privacyStatus) {
965 // requestBody.status.privacyStatus = privacyStatus; // 如果提供了 id, 使用 id
966 // }
967 // if (podcastStatus) {
968 // requestBody.status.podcastStatus = podcastStatus; // 如果提供了 id, 使用 id
969 // }
970 // }
971 console.log(requestBody);
972
973 // 调用 YouTube API 上传视频
974 const response = await this.youtubeService.playlists.update(
975 {
976 auth: oauth2Client,
977 part: 'snippet,status,id',
978 requestBody
979 }
980 );
981
982 // 返回上传的视频 ID
983 if (response.data) {
984 console.log('Playlist insert successfully:', response.data);
985 return response.data;
986 } else {
987 return 'Video upload failed';
988 }
989 } catch (error) {
990 console.error('Error uploading video:', error);
991 return error;
992 }
993
994 }
995
996 /**
997 * 删除播放列表
998 */
999 async deletePlaylist(accessToken, playListId) {
1000 // 设置 OAuth2 客户端凭证
1001 this.oauth2Service.setCredentials(accessToken);
1002 const oauth2Client = this.oauth2Service.getClient();
1003
1004 try {
1005 const response = await this.youtubeService.playlists.delete({
1006 auth: oauth2Client,
1007 id: playListId,
1008 });
1009 console.log('Video deleted:', response.data);
1010 } catch (error) {
1011 console.error('Error deleting video:', error);
1012 return error
1013 }
1014 }
1015
1016 /**
1017 * 将视频添加到播放列表中
1018 */
1019 async addVideoToPlaylist(accessToken, videoId, playlistId) {
1020 try {
1021 // 设置 OAuth2 客户端凭证
1022 this.oauth2Service.setCredentials(accessToken);
1023 const oauth2Client = this.oauth2Service.getClient();
1024
1025 // 构造请求体
1026 const requestBody = {
1027 snippet: {
1028 playlistId: playlistId,
1029 resourceId: {
1030 kind: 'youtube#video',
1031 videoId: videoId,
1032 },
1033 }
1034 };
1035
1036 console.log(requestBody);
1037
1038 // 调用 YouTube API 上传视频
1039 const response = await this.youtubeService.playlistItems.insert(
1040 {
1041 auth: oauth2Client,
1042 part: 'snippet,status, id, contentDetails',
1043 requestBody
1044 }
1045 );
1046
1047 // const response = { "data": {
1048 // "id": "7RckZHFBu7A"
1049 // },}
1050 // 返回上传的视频 ID
1051 if (response.data) {
1052 console.log('Playlist insert successfully:', response.data);
1053 return response.data;
1054 } else {
1055 throw new Error('Video upload failed');
1056 }
1057 } catch (error) {
1058 console.error('Error uploading video:', error);
1059 throw error;
1060 }
1061
1062 }
1063
1064
1065 /**
1066 * 获取播放列表项。
1067 */
1068 async getPlayItemsList(accessToken, playlistId, itemsIds, maxResults, pageToken) {
1069 // 设置 OAuth2 客户端凭证
1070 this.oauth2Service.setCredentials(accessToken);
1071 const oauth2Client = this.oauth2Service.getClient();
1072
1073 // 根据传入的参数来选择一个有效的请求参数
1074 let requestParams: any = {
1075 auth: oauth2Client,
1076 part: 'snippet, contentDetails, id, status',
1077 // id: ids
1078 };
1079
1080 // 根据参数选择 `id` 或 `forUsername`
1081 if (itemsIds) {
1082 requestParams.ids = itemsIds; // 如果提供了 id, 使用 id
1083 } else if (playlistId) {
1084 requestParams.playlistId = playlistId; // 如果提供了 handle, 使用 handle
1085 } else if (maxResults) {
1086 requestParams.maxResults = maxResults; // 如果提供了 handle, 使用 handle
1087 } else if (pageToken) {
1088 requestParams.pageToken = pageToken; // 如果提供了 handle, 使用 handle
1089 }
1090
1091 try {
1092 const response = await this.youtubeService.playlistItems.list(requestParams);
1093
1094 const infos = response.data;
1095 console.log(infos);
1096 if (infos.length === 0) {
1097 console.log('No categories found.');
1098 return [];
1099 } else {
1100 console.log(`This infos's ID is ${infos}.`);
1101 return infos;
1102 }
1103 } catch (err) {
1104 console.log('The API returned an error: ' + err);
1105 return err;
1106 }
1107 }
1108
1109 /**
1110 * 插入播放列表项。
1111 */
1112 async insertPlayItems(accessToken, snippet, contentDetails) {
1113 try {
1114 // 设置 OAuth2 客户端凭证
1115 this.oauth2Service.setCredentials(accessToken);
1116 const oauth2Client = this.oauth2Service.getClient();
1117
1118 // // 构造请求体
1119 // const requestBody: any = {
1120 // snippet: {
1121 // playlistId: playlistId,
1122 // resourceId: resourceId,
1123 // },
1124 // contentDetails: {},
1125 // };
1126
1127 // // 如果传递了 position,则添加到请求体
1128 // if (position !== undefined) {
1129 // requestBody.snippet.position = position;
1130 // }
1131
1132 // // 如果传递了 note,则添加到请求体
1133 // if (note !== undefined) {
1134 // requestBody.contentDetails.note = note;
1135 // }
1136 const requestBody: any = {
1137 snippet: snippet,
1138 contentDetails: contentDetails,
1139 };
1140 console.log(requestBody);
1141
1142 // 调用 YouTube API 上传视频
1143 const response = await this.youtubeService.playlistItems.insert(
1144 {
1145 auth: oauth2Client,
1146 part: 'snippet,status, id, contentDetails',
1147 requestBody
1148 }
1149 );
1150
1151 // 返回上传的视频 ID
1152 if (response.data) {
1153 console.log('Playlist insert successfully:', response.data);
1154 return response.data;
1155 } else {
1156 return 'Video upload failed';
1157 }
1158 } catch (error) {
1159 console.error('Error uploading video:', error);
1160 return error;
1161 }
1162 }
1163
1164 /**
1165 * 更新播放列表项。
1166 */
1167 async updatePlayItems(accessToken, playlistItemsId, snippet, contentDetails) {
1168 try {
1169 // 设置 OAuth2 客户端凭证
1170 this.oauth2Service.setCredentials(accessToken);
1171 const oauth2Client = this.oauth2Service.getClient();
1172
1173 const requestBody: any = {
1174 id: playlistItemsId,
1175 snippet: snippet,
1176 contentDetails: contentDetails,
1177 };
1178 console.log(requestBody);
1179
1180 // 调用 YouTube API 上传视频
1181 const response = await this.youtubeService.playlistItems.update(
1182 {
1183 auth: oauth2Client,
1184 part: 'snippet,status,id',
1185 requestBody
1186 }
1187 );
1188
1189 // 返回上传的视频 ID
1190 if (response.data) {
1191 console.log('Playlist insert successfully:', response.data);
1192 return response.data;
1193 } else {
1194 return 'Video upload failed';
1195 }
1196 } catch (error) {
1197 console.error('Error uploading video:', error);
1198 return error;
1199 }
1200
1201 }
1202
1203 /**
1204 * 删除播放列表项
1205 */
1206 async deletePlayItems(accessToken, playlistItemsId) {
1207 // 设置 OAuth2 客户端凭证
1208 this.oauth2Service.setCredentials(accessToken);
1209 const oauth2Client = this.oauth2Service.getClient();
1210
1211 try {
1212 const response = await this.youtubeService.playlistItems.delete({
1213 auth: oauth2Client,
1214 id: playlistItemsId,
1215 });
1216 console.log('Video deleted:', response.data);
1217 } catch (error) {
1218 console.error('Error deleting video:', error);
1219 return error
1220 }
1221 }
1222
1223 /**
1224 * 对视频的点赞、踩。
1225 */
1226 async videosRate(accessToken, videoId, rating) {
1227 try {
1228 // 设置 OAuth2 客户端凭证
1229 this.oauth2Service.setCredentials(accessToken);
1230 const oauth2Client = this.oauth2Service.getClient();
1231
1232 // 构造请求体
1233 const requestBody: any = {
1234 id: videoId,
1235 rating: rating // like | dislike | none
1236 };
1237
1238 console.log(requestBody);
1239
1240 // 调用 API 进行点赞或踩
1241 const response = await this.youtubeService.videos.rate(
1242 {
1243 auth: oauth2Client,
1244 requestBody
1245 }
1246 );
1247
1248 // 返回上传的视频 ID
1249 if (response.data) {
1250 console.log('Playlist insert successfully:', response.data);
1251 return response.data;
1252 } else {
1253 return 'Video rating failed';
1254 }
1255 } catch (error) {
1256 console.error('Error rating video:', error);
1257 this.handleApiError(error);
1258 }
1259
1260 }
1261
1262 /**
1263 * 获取视频的点赞、踩。
1264 */
1265 async getVideosRating(accessToken, videoIds) {
1266 // 设置 OAuth2 客户端凭证
1267 this.oauth2Service.setCredentials(accessToken);
1268 const oauth2Client = this.oauth2Service.getClient();
1269
1270 // 根据传入的参数来选择一个有效的请求参数
1271 let requestParams: any = {
1272 auth: oauth2Client,
1273 id: videoIds
1274 };
1275
1276 try {
1277 const response = await this.youtubeService.videos.getRating(requestParams);
1278
1279 const infos = response.data;
1280 console.log(infos);
1281 if (infos.length === 0) {
1282 console.log('No categories found.');
1283 return [];
1284 } else {
1285 console.log(`This infos's ID is ${infos}.`);
1286 return infos;
1287 }
1288 } catch (err) {
1289 this.handleApiError(err);
1290 }
1291 }
1292
1293 /**
1294 * 处理API错误
1295 * @param error 错误对象
1296 */
1297 private handleApiError(error: any) {
1298 console.error('YouTube API Error:', error);
1299
1300 if (error.response) {
1301 // API响应错误
1302 const { status, data } = error.response;
1303 if (status === 401) {
1304 throw new BadRequestException('授权已过期,请重新授权');
1305 } else if (status === 403) {
1306 throw new BadRequestException('权限不足,无法执行此操作');
1307 } else if (data && data.error && data.error.message) {
1308 throw new BadRequestException(`YouTube API错误: ${data.error.message}`);
1309 }
1310 }
1311
1312 throw new BadRequestException('YouTube API请求失败');
1313 }
1314
1315 }
1316
1317
1317 lines TYPESCRIPT