youtube-data-cli 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ import { writeFileSync } from "fs";
2
+ import { loadCredentials } from "../auth.js";
3
+ import { callApi, uploadFile } from "../api.js";
4
+ import { output, fatal } from "../utils.js";
5
+ export function registerCaptionCommands(program) {
6
+ program
7
+ .command("captions")
8
+ .description("List caption tracks for a video")
9
+ .requiredOption("--video-id <id>", "Video ID")
10
+ .option("--id <id>", "Caption track ID(s), comma-separated")
11
+ .action(async (opts) => {
12
+ try {
13
+ const creds = loadCredentials(program.opts().credentials);
14
+ const params = {
15
+ part: "snippet",
16
+ videoId: opts.videoId,
17
+ };
18
+ if (opts.id)
19
+ params.id = opts.id;
20
+ const data = await callApi("/captions", { creds, params, requireOAuth: true });
21
+ output(data, program.opts().format);
22
+ }
23
+ catch (err) {
24
+ fatal(err.message);
25
+ }
26
+ });
27
+ program
28
+ .command("captions-insert")
29
+ .description("Upload a caption track (OAuth required)")
30
+ .requiredOption("--video-id <id>", "Video ID")
31
+ .requiredOption("--file <path>", "Path to caption file (SRT, VTT, etc.)")
32
+ .requiredOption("--language <lang>", "Caption language (BCP-47)")
33
+ .requiredOption("--name <name>", "Caption track name")
34
+ .option("--content-type <type>", "Caption file MIME type", "application/octet-stream")
35
+ .option("--is-draft", "Mark as draft")
36
+ .action(async (opts) => {
37
+ try {
38
+ const creds = loadCredentials(program.opts().credentials);
39
+ const snippet = {
40
+ videoId: opts.videoId,
41
+ language: opts.language,
42
+ name: opts.name,
43
+ };
44
+ if (opts.isDraft)
45
+ snippet.isDraft = true;
46
+ const data = await uploadFile({
47
+ creds,
48
+ endpoint: "/captions",
49
+ params: { part: "snippet" },
50
+ filePath: opts.file,
51
+ contentType: opts.contentType,
52
+ body: { snippet },
53
+ });
54
+ output(data, program.opts().format);
55
+ }
56
+ catch (err) {
57
+ fatal(err.message);
58
+ }
59
+ });
60
+ program
61
+ .command("captions-update")
62
+ .description("Update a caption track (OAuth required)")
63
+ .requiredOption("--id <id>", "Caption track ID")
64
+ .option("--file <path>", "Path to new caption file")
65
+ .option("--content-type <type>", "Caption file MIME type", "application/octet-stream")
66
+ .option("--is-draft <bool>", "Mark as draft (true/false)")
67
+ .action(async (opts) => {
68
+ try {
69
+ const creds = loadCredentials(program.opts().credentials);
70
+ const body = { id: opts.id };
71
+ if (opts.isDraft !== undefined) {
72
+ body.snippet = { isDraft: opts.isDraft === "true" };
73
+ }
74
+ if (opts.file) {
75
+ const data = await uploadFile({
76
+ creds,
77
+ endpoint: "/captions",
78
+ params: { part: "snippet" },
79
+ method: "PUT",
80
+ filePath: opts.file,
81
+ contentType: opts.contentType,
82
+ body,
83
+ });
84
+ output(data, program.opts().format);
85
+ }
86
+ else {
87
+ const data = await callApi("/captions", {
88
+ creds,
89
+ params: { part: "snippet" },
90
+ method: "PUT",
91
+ body,
92
+ });
93
+ output(data, program.opts().format);
94
+ }
95
+ }
96
+ catch (err) {
97
+ fatal(err.message);
98
+ }
99
+ });
100
+ program
101
+ .command("captions-download")
102
+ .description("Download a caption track (OAuth required)")
103
+ .requiredOption("--id <id>", "Caption track ID")
104
+ .option("--tfmt <format>", "Caption format (sbv, scc, srt, ttml, vtt)")
105
+ .option("--tlang <lang>", "Translation language (BCP-47)")
106
+ .option("--output <path>", "Output file path (default: stdout)")
107
+ .action(async (opts) => {
108
+ try {
109
+ const creds = loadCredentials(program.opts().credentials);
110
+ const params = {};
111
+ if (opts.tfmt)
112
+ params.tfmt = opts.tfmt;
113
+ if (opts.tlang)
114
+ params.tlang = opts.tlang;
115
+ const res = await callApi(`/captions/${opts.id}`, {
116
+ creds,
117
+ params,
118
+ requireOAuth: true,
119
+ rawResponse: true,
120
+ });
121
+ const text = await res.text();
122
+ if (opts.output) {
123
+ writeFileSync(opts.output, text);
124
+ output({ downloaded: opts.output }, program.opts().format);
125
+ }
126
+ else {
127
+ process.stdout.write(text);
128
+ }
129
+ }
130
+ catch (err) {
131
+ fatal(err.message);
132
+ }
133
+ });
134
+ program
135
+ .command("captions-delete")
136
+ .description("Delete a caption track (OAuth required)")
137
+ .requiredOption("--id <id>", "Caption track ID")
138
+ .action(async (opts) => {
139
+ try {
140
+ const creds = loadCredentials(program.opts().credentials);
141
+ const data = await callApi("/captions", {
142
+ creds,
143
+ params: { id: opts.id },
144
+ method: "DELETE",
145
+ });
146
+ output(data, program.opts().format);
147
+ }
148
+ catch (err) {
149
+ fatal(err.message);
150
+ }
151
+ });
152
+ }
@@ -0,0 +1,25 @@
1
+ import { loadCredentials } from "../auth.js";
2
+ import { uploadFileSimple } from "../api.js";
3
+ import { output, fatal } from "../utils.js";
4
+ export function registerChannelBannerCommands(program) {
5
+ program
6
+ .command("channel-banners-insert")
7
+ .description("Upload a channel banner image (OAuth required)")
8
+ .requiredOption("--file <path>", "Path to image file (JPEG, PNG, max 6MB)")
9
+ .option("--content-type <type>", "Image MIME type", "image/jpeg")
10
+ .action(async (opts) => {
11
+ try {
12
+ const creds = loadCredentials(program.opts().credentials);
13
+ const data = await uploadFileSimple({
14
+ creds,
15
+ endpoint: "/channelBanners/insert",
16
+ filePath: opts.file,
17
+ contentType: opts.contentType,
18
+ });
19
+ output(data, program.opts().format);
20
+ }
21
+ catch (err) {
22
+ fatal(err.message);
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,139 @@
1
+ import { loadCredentials } from "../auth.js";
2
+ import { callApi } from "../api.js";
3
+ import { output, fatal } from "../utils.js";
4
+ export function registerChannelSectionCommands(program) {
5
+ program
6
+ .command("channel-sections")
7
+ .description("List channel sections")
8
+ .option("--channel-id <id>", "Channel ID")
9
+ .option("--id <id>", "Section ID(s), comma-separated")
10
+ .option("--mine", "List authenticated user's sections (OAuth required)")
11
+ .option("--part <parts>", "Parts to include", "snippet,contentDetails")
12
+ .option("--hl <lang>", "Language for localized text (ISO 639-1)")
13
+ .action(async (opts) => {
14
+ try {
15
+ const creds = loadCredentials(program.opts().credentials);
16
+ const params = {
17
+ part: opts.part,
18
+ };
19
+ const requireOAuth = !!opts.mine || (!opts.id && !opts.channelId);
20
+ if (opts.id) {
21
+ params.id = opts.id;
22
+ }
23
+ else if (opts.channelId) {
24
+ params.channelId = opts.channelId;
25
+ }
26
+ else {
27
+ params.mine = "true";
28
+ }
29
+ if (opts.hl)
30
+ params.hl = opts.hl;
31
+ const data = await callApi("/channelSections", { creds, params, requireOAuth });
32
+ output(data, program.opts().format);
33
+ }
34
+ catch (err) {
35
+ fatal(err.message);
36
+ }
37
+ });
38
+ program
39
+ .command("channel-sections-insert")
40
+ .description("Add a channel section (OAuth required)")
41
+ .requiredOption("--type <type>", "Section type (singlePlaylist, multiplePlaylists, allPlaylists, recentActivity, etc.)")
42
+ .option("--title <title>", "Section title")
43
+ .option("--position <n>", "Section position (0-based)")
44
+ .option("--playlist-ids <ids>", "Playlist ID(s), comma-separated")
45
+ .option("--channel-ids <ids>", "Channel ID(s), comma-separated")
46
+ .action(async (opts) => {
47
+ try {
48
+ const creds = loadCredentials(program.opts().credentials);
49
+ const snippet = {
50
+ type: opts.type,
51
+ };
52
+ if (opts.title)
53
+ snippet.title = opts.title;
54
+ if (opts.position !== undefined)
55
+ snippet.position = parseInt(opts.position, 10);
56
+ const body = { snippet };
57
+ const parts = ["snippet"];
58
+ if (opts.playlistIds || opts.channelIds) {
59
+ const contentDetails = {};
60
+ if (opts.playlistIds)
61
+ contentDetails.playlists = opts.playlistIds.split(",");
62
+ if (opts.channelIds)
63
+ contentDetails.channels = opts.channelIds.split(",");
64
+ body.contentDetails = contentDetails;
65
+ parts.push("contentDetails");
66
+ }
67
+ const data = await callApi("/channelSections", {
68
+ creds,
69
+ params: { part: parts.join(",") },
70
+ method: "POST",
71
+ body,
72
+ });
73
+ output(data, program.opts().format);
74
+ }
75
+ catch (err) {
76
+ fatal(err.message);
77
+ }
78
+ });
79
+ program
80
+ .command("channel-sections-update")
81
+ .description("Update a channel section (OAuth required)")
82
+ .requiredOption("--id <id>", "Section ID")
83
+ .requiredOption("--type <type>", "Section type")
84
+ .option("--title <title>", "Section title")
85
+ .option("--position <n>", "Section position (0-based)")
86
+ .option("--playlist-ids <ids>", "Playlist ID(s), comma-separated")
87
+ .option("--channel-ids <ids>", "Channel ID(s), comma-separated")
88
+ .action(async (opts) => {
89
+ try {
90
+ const creds = loadCredentials(program.opts().credentials);
91
+ const snippet = {
92
+ type: opts.type,
93
+ };
94
+ if (opts.title)
95
+ snippet.title = opts.title;
96
+ if (opts.position !== undefined)
97
+ snippet.position = parseInt(opts.position, 10);
98
+ const body = { id: opts.id, snippet };
99
+ const parts = ["snippet"];
100
+ if (opts.playlistIds || opts.channelIds) {
101
+ const contentDetails = {};
102
+ if (opts.playlistIds)
103
+ contentDetails.playlists = opts.playlistIds.split(",");
104
+ if (opts.channelIds)
105
+ contentDetails.channels = opts.channelIds.split(",");
106
+ body.contentDetails = contentDetails;
107
+ parts.push("contentDetails");
108
+ }
109
+ const data = await callApi("/channelSections", {
110
+ creds,
111
+ params: { part: parts.join(",") },
112
+ method: "PUT",
113
+ body,
114
+ });
115
+ output(data, program.opts().format);
116
+ }
117
+ catch (err) {
118
+ fatal(err.message);
119
+ }
120
+ });
121
+ program
122
+ .command("channel-sections-delete")
123
+ .description("Delete a channel section (OAuth required)")
124
+ .requiredOption("--id <id>", "Section ID")
125
+ .action(async (opts) => {
126
+ try {
127
+ const creds = loadCredentials(program.opts().credentials);
128
+ const data = await callApi("/channelSections", {
129
+ creds,
130
+ params: { id: opts.id },
131
+ method: "DELETE",
132
+ });
133
+ output(data, program.opts().format);
134
+ }
135
+ catch (err) {
136
+ fatal(err.message);
137
+ }
138
+ });
139
+ }
@@ -26,4 +26,50 @@ export function registerChannelCommands(program) {
26
26
  fatal(err.message);
27
27
  }
28
28
  });
29
+ program
30
+ .command("channels-update")
31
+ .description("Update a channel's metadata (OAuth required)")
32
+ .requiredOption("--id <id>", "Channel ID")
33
+ .option("--description <desc>", "Channel description")
34
+ .option("--keywords <kw>", "Channel keywords")
35
+ .option("--default-language <lang>", "Default language (ISO 639-1)")
36
+ .option("--country <code>", "Channel country (ISO 3166-1 alpha-2)")
37
+ .option("--made-for-kids <bool>", "Self-declared made for kids (true/false)")
38
+ .action(async (opts) => {
39
+ try {
40
+ const creds = loadCredentials(program.opts().credentials);
41
+ const body = { id: opts.id };
42
+ const parts = [];
43
+ const brandingChannel = {};
44
+ if (opts.description !== undefined)
45
+ brandingChannel.description = opts.description;
46
+ if (opts.keywords !== undefined)
47
+ brandingChannel.keywords = opts.keywords;
48
+ if (opts.defaultLanguage)
49
+ brandingChannel.defaultLanguage = opts.defaultLanguage;
50
+ if (opts.country)
51
+ brandingChannel.country = opts.country;
52
+ if (Object.keys(brandingChannel).length > 0) {
53
+ body.brandingSettings = { channel: brandingChannel };
54
+ parts.push("brandingSettings");
55
+ }
56
+ if (opts.madeForKids !== undefined) {
57
+ body.status = { selfDeclaredMadeForKids: opts.madeForKids === "true" };
58
+ parts.push("status");
59
+ }
60
+ if (parts.length === 0) {
61
+ fatal("At least one field to update is required.");
62
+ }
63
+ const data = await callApi("/channels", {
64
+ creds,
65
+ params: { part: parts.join(",") },
66
+ method: "PUT",
67
+ body,
68
+ });
69
+ output(data, program.opts().format);
70
+ }
71
+ catch (err) {
72
+ fatal(err.message);
73
+ }
74
+ });
29
75
  }
@@ -24,7 +24,7 @@ export function registerCommentThreadCommands(program) {
24
24
  if (opts.videoId)
25
25
  params.videoId = opts.videoId;
26
26
  else if (opts.channelId)
27
- params.channelId = opts.channelId;
27
+ params.allThreadsRelatedToChannelId = opts.channelId;
28
28
  else if (opts.id)
29
29
  params.id = opts.id;
30
30
  else {
@@ -94,4 +94,31 @@ export function registerCommentCommands(program) {
94
94
  fatal(err.message);
95
95
  }
96
96
  });
97
+ program
98
+ .command("comments-set-moderation-status")
99
+ .description("Set moderation status of comments (OAuth required)")
100
+ .requiredOption("--id <ids>", "Comment ID(s), comma-separated")
101
+ .requiredOption("--moderation-status <status>", "Status: published, heldForReview, rejected")
102
+ .option("--ban-author", "Ban the comment author from making future comments")
103
+ .action(async (opts) => {
104
+ try {
105
+ const creds = loadCredentials(program.opts().credentials);
106
+ const params = {
107
+ id: opts.id,
108
+ moderationStatus: opts.moderationStatus,
109
+ };
110
+ if (opts.banAuthor)
111
+ params.banAuthor = "true";
112
+ const data = await callApi("/comments/setModerationStatus", {
113
+ creds,
114
+ params,
115
+ method: "POST",
116
+ requireOAuth: true,
117
+ });
118
+ output(data, program.opts().format);
119
+ }
120
+ catch (err) {
121
+ fatal(err.message);
122
+ }
123
+ });
97
124
  }
@@ -0,0 +1,39 @@
1
+ import { loadCredentials } from "../auth.js";
2
+ import { callApi } from "../api.js";
3
+ import { output, fatal } from "../utils.js";
4
+ export function registerI18nCommands(program) {
5
+ program
6
+ .command("i18n-languages")
7
+ .description("List supported languages")
8
+ .option("--hl <lang>", "Language for localized text (ISO 639-1)")
9
+ .action(async (opts) => {
10
+ try {
11
+ const creds = loadCredentials(program.opts().credentials);
12
+ const params = { part: "snippet" };
13
+ if (opts.hl)
14
+ params.hl = opts.hl;
15
+ const data = await callApi("/i18nLanguages", { creds, params });
16
+ output(data, program.opts().format);
17
+ }
18
+ catch (err) {
19
+ fatal(err.message);
20
+ }
21
+ });
22
+ program
23
+ .command("i18n-regions")
24
+ .description("List supported regions")
25
+ .option("--hl <lang>", "Language for localized text (ISO 639-1)")
26
+ .action(async (opts) => {
27
+ try {
28
+ const creds = loadCredentials(program.opts().credentials);
29
+ const params = { part: "snippet" };
30
+ if (opts.hl)
31
+ params.hl = opts.hl;
32
+ const data = await callApi("/i18nRegions", { creds, params });
33
+ output(data, program.opts().format);
34
+ }
35
+ catch (err) {
36
+ fatal(err.message);
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,51 @@
1
+ import { loadCredentials } from "../auth.js";
2
+ import { callApi } from "../api.js";
3
+ import { output, fatal } from "../utils.js";
4
+ export function registerMemberCommands(program) {
5
+ program
6
+ .command("members")
7
+ .description("List channel members (OAuth required)")
8
+ .option("--mode <mode>", "Filter mode (all_current, updates)", "all_current")
9
+ .option("--max-results <n>", "Max results (1-1000)", "25")
10
+ .option("--page-token <token>", "Pagination token")
11
+ .option("--has-access-to-level <id>", "Filter by membership level ID")
12
+ .option("--filter-by-member-channel-id <ids>", "Filter by member channel ID(s)")
13
+ .action(async (opts) => {
14
+ try {
15
+ const creds = loadCredentials(program.opts().credentials);
16
+ const params = {
17
+ part: "snippet",
18
+ mode: opts.mode,
19
+ maxResults: opts.maxResults,
20
+ };
21
+ if (opts.pageToken)
22
+ params.pageToken = opts.pageToken;
23
+ if (opts.hasAccessToLevel)
24
+ params.hasAccessToLevel = opts.hasAccessToLevel;
25
+ if (opts.filterByMemberChannelId)
26
+ params.filterByMemberChannelId = opts.filterByMemberChannelId;
27
+ const data = await callApi("/members", { creds, params, requireOAuth: true });
28
+ output(data, program.opts().format);
29
+ }
30
+ catch (err) {
31
+ fatal(err.message);
32
+ }
33
+ });
34
+ program
35
+ .command("memberships-levels")
36
+ .description("List channel membership levels (OAuth required)")
37
+ .action(async () => {
38
+ try {
39
+ const creds = loadCredentials(program.opts().credentials);
40
+ const data = await callApi("/membershipsLevels", {
41
+ creds,
42
+ params: { part: "snippet,id" },
43
+ requireOAuth: true,
44
+ });
45
+ output(data, program.opts().format);
46
+ }
47
+ catch (err) {
48
+ fatal(err.message);
49
+ }
50
+ });
51
+ }
@@ -0,0 +1,107 @@
1
+ import { loadCredentials } from "../auth.js";
2
+ import { callApi, uploadFile } from "../api.js";
3
+ import { output, fatal } from "../utils.js";
4
+ export function registerPlaylistImageCommands(program) {
5
+ program
6
+ .command("playlist-images")
7
+ .description("List playlist images")
8
+ .requiredOption("--parent <id>", "Playlist ID")
9
+ .option("--max-results <n>", "Max results", "25")
10
+ .option("--page-token <token>", "Pagination token")
11
+ .action(async (opts) => {
12
+ try {
13
+ const creds = loadCredentials(program.opts().credentials);
14
+ const params = {
15
+ part: "snippet",
16
+ parent: opts.parent,
17
+ maxResults: opts.maxResults,
18
+ };
19
+ if (opts.pageToken)
20
+ params.pageToken = opts.pageToken;
21
+ const data = await callApi("/playlistImages", { creds, params, requireOAuth: true });
22
+ output(data, program.opts().format);
23
+ }
24
+ catch (err) {
25
+ fatal(err.message);
26
+ }
27
+ });
28
+ program
29
+ .command("playlist-images-insert")
30
+ .description("Upload a playlist image (OAuth required)")
31
+ .requiredOption("--playlist-id <id>", "Playlist ID")
32
+ .requiredOption("--file <path>", "Path to image file")
33
+ .option("--content-type <type>", "Image MIME type", "image/jpeg")
34
+ .option("--type <type>", "Image type", "hero")
35
+ .action(async (opts) => {
36
+ try {
37
+ const creds = loadCredentials(program.opts().credentials);
38
+ const data = await uploadFile({
39
+ creds,
40
+ endpoint: "/playlistImages",
41
+ params: { part: "snippet" },
42
+ filePath: opts.file,
43
+ contentType: opts.contentType,
44
+ body: {
45
+ snippet: {
46
+ playlistId: opts.playlistId,
47
+ type: opts.type,
48
+ },
49
+ },
50
+ });
51
+ output(data, program.opts().format);
52
+ }
53
+ catch (err) {
54
+ fatal(err.message);
55
+ }
56
+ });
57
+ program
58
+ .command("playlist-images-update")
59
+ .description("Update a playlist image (OAuth required)")
60
+ .requiredOption("--id <id>", "Playlist image ID")
61
+ .requiredOption("--playlist-id <id>", "Playlist ID")
62
+ .requiredOption("--file <path>", "Path to image file")
63
+ .option("--content-type <type>", "Image MIME type", "image/jpeg")
64
+ .option("--type <type>", "Image type", "hero")
65
+ .action(async (opts) => {
66
+ try {
67
+ const creds = loadCredentials(program.opts().credentials);
68
+ const data = await uploadFile({
69
+ creds,
70
+ endpoint: "/playlistImages",
71
+ params: { part: "snippet" },
72
+ method: "PUT",
73
+ filePath: opts.file,
74
+ contentType: opts.contentType,
75
+ body: {
76
+ id: opts.id,
77
+ snippet: {
78
+ playlistId: opts.playlistId,
79
+ type: opts.type,
80
+ },
81
+ },
82
+ });
83
+ output(data, program.opts().format);
84
+ }
85
+ catch (err) {
86
+ fatal(err.message);
87
+ }
88
+ });
89
+ program
90
+ .command("playlist-images-delete")
91
+ .description("Delete a playlist image (OAuth required)")
92
+ .requiredOption("--id <id>", "Playlist image ID")
93
+ .action(async (opts) => {
94
+ try {
95
+ const creds = loadCredentials(program.opts().credentials);
96
+ const data = await callApi("/playlistImages", {
97
+ creds,
98
+ params: { id: opts.id },
99
+ method: "DELETE",
100
+ });
101
+ output(data, program.opts().format);
102
+ }
103
+ catch (err) {
104
+ fatal(err.message);
105
+ }
106
+ });
107
+ }
@@ -18,7 +18,7 @@ export function registerPlaylistCommands(program) {
18
18
  part: opts.part,
19
19
  maxResults: opts.maxResults,
20
20
  };
21
- const requireOAuth = !!opts.mine;
21
+ const requireOAuth = !!opts.mine || (!opts.id && !opts.channelId);
22
22
  if (opts.id) {
23
23
  params.id = opts.id;
24
24
  }
@@ -0,0 +1,27 @@
1
+ import { loadCredentials } from "../auth.js";
2
+ import { uploadFileSimple } from "../api.js";
3
+ import { output, fatal } from "../utils.js";
4
+ export function registerThumbnailCommands(program) {
5
+ program
6
+ .command("thumbnails-set")
7
+ .description("Upload a custom thumbnail for a video (OAuth required)")
8
+ .requiredOption("--video-id <id>", "Video ID")
9
+ .requiredOption("--file <path>", "Path to image file (JPEG, PNG, GIF, BMP, WebP)")
10
+ .option("--content-type <type>", "Image MIME type", "image/jpeg")
11
+ .action(async (opts) => {
12
+ try {
13
+ const creds = loadCredentials(program.opts().credentials);
14
+ const data = await uploadFileSimple({
15
+ creds,
16
+ endpoint: "/thumbnails/set",
17
+ params: { videoId: opts.videoId },
18
+ filePath: opts.file,
19
+ contentType: opts.contentType,
20
+ });
21
+ output(data, program.opts().format);
22
+ }
23
+ catch (err) {
24
+ fatal(err.message);
25
+ }
26
+ });
27
+ }
@@ -0,0 +1,22 @@
1
+ import { loadCredentials } from "../auth.js";
2
+ import { callApi } from "../api.js";
3
+ import { output, fatal } from "../utils.js";
4
+ export function registerVideoAbuseReportReasonCommands(program) {
5
+ program
6
+ .command("video-abuse-report-reasons")
7
+ .description("List video abuse report reasons")
8
+ .option("--hl <lang>", "Language for localized text (ISO 639-1)")
9
+ .action(async (opts) => {
10
+ try {
11
+ const creds = loadCredentials(program.opts().credentials);
12
+ const params = { part: "snippet" };
13
+ if (opts.hl)
14
+ params.hl = opts.hl;
15
+ const data = await callApi("/videoAbuseReportReasons", { creds, params });
16
+ output(data, program.opts().format);
17
+ }
18
+ catch (err) {
19
+ fatal(err.message);
20
+ }
21
+ });
22
+ }