aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tools
diff options
context:
space:
mode:
Diffstat (limited to 'server/tools')
-rw-r--r--server/tools/cli.ts49
-rw-r--r--server/tools/package.json2
-rw-r--r--server/tools/peertube-auth.ts15
-rw-r--r--server/tools/peertube-get-access-token.ts18
-rw-r--r--server/tools/peertube-import-videos.ts145
-rw-r--r--server/tools/peertube-repl.ts11
-rw-r--r--server/tools/peertube-upload.ts70
-rw-r--r--server/tools/peertube.ts7
-rw-r--r--server/tools/yarn.lock97
9 files changed, 283 insertions, 131 deletions
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
index e83a8a63c..6be558d7b 100644
--- a/server/tools/cli.ts
+++ b/server/tools/cli.ts
@@ -1,5 +1,12 @@
1const config = require('application-config')('PeerTube/CLI') 1import { Netrc } from 'netrc-parser'
2const netrc = require('netrc-parser').default 2import { isTestInstance, getAppNumber } from '../helpers/core-utils'
3import { join } from 'path'
4import { root } from '../../shared/extra-utils'
5
6let configName = 'PeerTube/CLI'
7if (isTestInstance()) configName += `-${getAppNumber()}`
8
9const config = require('application-config')(configName)
3 10
4const version = require('../../../package.json').version 11const version = require('../../../package.json').version
5 12
@@ -12,7 +19,7 @@ function getSettings () {
12 return new Promise<Settings>((res, rej) => { 19 return new Promise<Settings>((res, rej) => {
13 const defaultSettings = { 20 const defaultSettings = {
14 remotes: [], 21 remotes: [],
15 default: 0 22 default: -1
16 } 23 }
17 24
18 config.read((err, data) => { 25 config.read((err, data) => {
@@ -24,6 +31,12 @@ function getSettings () {
24} 31}
25 32
26async function getNetrc () { 33async function getNetrc () {
34 const Netrc = require('netrc-parser').Netrc
35
36 const netrc = isTestInstance()
37 ? new Netrc(join(root(), 'test' + getAppNumber(), 'netrc'))
38 : new Netrc()
39
27 await netrc.load() 40 await netrc.load()
28 41
29 return netrc 42 return netrc
@@ -31,7 +44,17 @@ async function getNetrc () {
31 44
32function writeSettings (settings) { 45function writeSettings (settings) {
33 return new Promise((res, rej) => { 46 return new Promise((res, rej) => {
34 config.write(settings, function (err) { 47 config.write(settings, err => {
48 if (err) return rej(err)
49
50 return res()
51 })
52 })
53}
54
55function deleteSettings () {
56 return new Promise((res, rej) => {
57 config.trash((err) => {
35 if (err) return rej(err) 58 if (err) return rej(err)
36 59
37 return res() 60 return res()
@@ -39,9 +62,9 @@ function writeSettings (settings) {
39 }) 62 })
40} 63}
41 64
42function getRemoteObjectOrDie (program: any, settings: Settings) { 65function getRemoteObjectOrDie (program: any, settings: Settings, netrc: Netrc) {
43 if (!program['url'] || !program['username'] || !program['password']) { 66 if (!program['url'] || !program['username'] || !program['password']) {
44 // No remote and we don't have program parameters: throw 67 // No remote and we don't have program parameters: quit
45 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { 68 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) {
46 if (!program[ 'url' ]) console.error('--url field is required.') 69 if (!program[ 'url' ]) console.error('--url field is required.')
47 if (!program[ 'username' ]) console.error('--username field is required.') 70 if (!program[ 'username' ]) console.error('--username field is required.')
@@ -54,15 +77,12 @@ function getRemoteObjectOrDie (program: any, settings: Settings) {
54 let username: string = program['username'] 77 let username: string = program['username']
55 let password: string = program['password'] 78 let password: string = program['password']
56 79
57 if (!url) { 80 if (!url && settings.default !== -1) url = settings.remotes[settings.default]
58 url = settings.default !== -1
59 ? settings.remotes[settings.default]
60 : settings.remotes[0]
61 }
62 81
63 const machine = netrc.machines[url] 82 const machine = netrc.machines[url]
64 if (!username) username = machine.login 83
65 if (!password) password = machine.password 84 if (!username && machine) username = machine.login
85 if (!password && machine) password = machine.password
66 86
67 return { url, username, password } 87 return { url, username, password }
68 } 88 }
@@ -82,5 +102,6 @@ export {
82 getSettings, 102 getSettings,
83 getNetrc, 103 getNetrc,
84 getRemoteObjectOrDie, 104 getRemoteObjectOrDie,
85 writeSettings 105 writeSettings,
106 deleteSettings
86} 107}
diff --git a/server/tools/package.json b/server/tools/package.json
index 2d13d41cc..22fb8d24c 100644
--- a/server/tools/package.json
+++ b/server/tools/package.json
@@ -4,6 +4,8 @@
4 "private": true, 4 "private": true,
5 "dependencies": { 5 "dependencies": {
6 "application-config": "^1.0.1", 6 "application-config": "^1.0.1",
7 "cli-table": "^0.3.1",
8 "netrc-parser": "^3.1.6",
7 "webtorrent-hybrid": "^2.1.0" 9 "webtorrent-hybrid": "^2.1.0"
8 }, 10 },
9 "summon": { 11 "summon": {
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts
index e53283fbe..ff5ffb60e 100644
--- a/server/tools/peertube-auth.ts
+++ b/server/tools/peertube-auth.ts
@@ -9,7 +9,11 @@ const Table = require('cli-table')
9async function delInstance (url: string) { 9async function delInstance (url: string) {
10 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 10 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
11 11
12 settings.remotes.splice(settings.remotes.indexOf(url)) 12 const index = settings.remotes.indexOf(url)
13 settings.remotes.splice(index)
14
15 if (settings.default === index) settings.default = -1
16
13 await writeSettings(settings) 17 await writeSettings(settings)
14 18
15 delete netrc.machines[url] 19 delete netrc.machines[url]
@@ -17,12 +21,17 @@ async function delInstance (url: string) {
17 await netrc.save() 21 await netrc.save()
18} 22}
19 23
20async function setInstance (url: string, username: string, password: string) { 24async function setInstance (url: string, username: string, password: string, isDefault: boolean) {
21 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 25 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
22 26
23 if (settings.remotes.indexOf(url) === -1) { 27 if (settings.remotes.indexOf(url) === -1) {
24 settings.remotes.push(url) 28 settings.remotes.push(url)
25 } 29 }
30
31 if (isDefault || settings.remotes.length === 1) {
32 settings.default = settings.remotes.length - 1
33 }
34
26 await writeSettings(settings) 35 await writeSettings(settings)
27 36
28 netrc.machines[url] = { login: username, password } 37 netrc.machines[url] = { login: username, password }
@@ -66,7 +75,7 @@ program
66 } 75 }
67 } 76 }
68 }, async (_, result) => { 77 }, async (_, result) => {
69 await setInstance(result.url, result.username, result.password) 78 await setInstance(result.url, result.username, result.password, program['default'])
70 79
71 process.exit(0) 80 process.exit(0)
72 }) 81 })
diff --git a/server/tools/peertube-get-access-token.ts b/server/tools/peertube-get-access-token.ts
index 85660de2c..103495347 100644
--- a/server/tools/peertube-get-access-token.ts
+++ b/server/tools/peertube-get-access-token.ts
@@ -1,12 +1,5 @@
1import * as program from 'commander' 1import * as program from 'commander'
2 2import { getClient, Server, serverLogin } from '../../shared/extra-utils'
3import {
4 getClient,
5 serverLogin,
6 Server,
7 Client,
8 User
9} from '../../shared/extra-utils'
10 3
11program 4program
12 .option('-u, --url <url>', 'Server url') 5 .option('-u, --url <url>', 'Server url')
@@ -22,6 +15,7 @@ if (
22 if (!program['url']) console.error('--url field is required.') 15 if (!program['url']) console.error('--url field is required.')
23 if (!program['username']) console.error('--username field is required.') 16 if (!program['username']) console.error('--username field is required.')
24 if (!program['password']) console.error('--password field is required.') 17 if (!program['password']) console.error('--password field is required.')
18
25 process.exit(-1) 19 process.exit(-1)
26} 20}
27 21
@@ -32,11 +26,11 @@ getClient(program.url)
32 user: { 26 user: {
33 username: program['username'], 27 username: program['username'],
34 password: program['password'] 28 password: program['password']
35 } as User, 29 },
36 client: { 30 client: {
37 id: res.body.client_id as string, 31 id: res.body.client_id,
38 secret: res.body.client_secret as string 32 secret: res.body.client_secret
39 } as Client 33 }
40 } as Server 34 } as Server
41 35
42 return serverLogin(server) 36 return serverLogin(server)
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index 9a366dbbd..f9cd3106a 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -14,8 +14,10 @@ import { sha256 } from '../helpers/core-utils'
14import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' 14import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
15import { getNetrc, getRemoteObjectOrDie, getSettings } from './cli' 15import { getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
16 16
17let accessToken: string 17type UserInfo = {
18let client: { id: string, secret: string } 18 username: string
19 password: string
20}
19 21
20const processOptions = { 22const processOptions = {
21 cwd: __dirname, 23 cwd: __dirname,
@@ -28,13 +30,14 @@ program
28 .option('-U, --username <username>', 'Username') 30 .option('-U, --username <username>', 'Username')
29 .option('-p, --password <token>', 'Password') 31 .option('-p, --password <token>', 'Password')
30 .option('-t, --target-url <targetUrl>', 'Video target URL') 32 .option('-t, --target-url <targetUrl>', 'Video target URL')
33 .option('-C, --channel-id <channel_id>', 'Channel ID')
31 .option('-l, --language <languageCode>', 'Language ISO 639 code (fr or en...)') 34 .option('-l, --language <languageCode>', 'Language ISO 639 code (fr or en...)')
32 .option('-v, --verbose', 'Verbose mode') 35 .option('-v, --verbose', 'Verbose mode')
33 .parse(process.argv) 36 .parse(process.argv)
34 37
35Promise.all([ getSettings(), getNetrc() ]) 38Promise.all([ getSettings(), getNetrc() ])
36 .then(([ settings, netrc ]) => { 39 .then(([ settings, netrc ]) => {
37 const { url, username, password } = getRemoteObjectOrDie(program, settings) 40 const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
38 41
39 if (!program[ 'targetUrl' ]) { 42 if (!program[ 'targetUrl' ]) {
40 console.error('--targetUrl field is required.') 43 console.error('--targetUrl field is required.')
@@ -45,56 +48,20 @@ Promise.all([ getSettings(), getNetrc() ])
45 removeEndSlashes(url) 48 removeEndSlashes(url)
46 removeEndSlashes(program[ 'targetUrl' ]) 49 removeEndSlashes(program[ 'targetUrl' ])
47 50
48 const user = { 51 const user = { username, password }
49 username: username,
50 password: password
51 }
52 52
53 run(user, url) 53 run(url, user)
54 .catch(err => { 54 .catch(err => {
55 console.error(err) 55 console.error(err)
56 process.exit(-1) 56 process.exit(-1)
57 }) 57 })
58 }) 58 })
59 59
60async function promptPassword () { 60async function run (url: string, user: UserInfo) {
61 return new Promise((res, rej) => {
62 prompt.start()
63 const schema = {
64 properties: {
65 password: {
66 hidden: true,
67 required: true
68 }
69 }
70 }
71 prompt.get(schema, function (err, result) {
72 if (err) {
73 return rej(err)
74 }
75 return res(result.password)
76 })
77 })
78}
79
80async function run (user, url: string) {
81 if (!user.password) { 61 if (!user.password) {
82 user.password = await promptPassword() 62 user.password = await promptPassword()
83 } 63 }
84 64
85 const res = await getClient(url)
86 client = {
87 id: res.body.client_id,
88 secret: res.body.client_secret
89 }
90
91 try {
92 const res = await login(program[ 'url' ], client, user)
93 accessToken = res.body.access_token
94 } catch (err) {
95 throw new Error('Cannot authenticate. Please check your username/password.')
96 }
97
98 const youtubeDL = await safeGetYoutubeDL() 65 const youtubeDL = await safeGetYoutubeDL()
99 66
100 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] 67 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
@@ -115,7 +82,12 @@ async function run (user, url: string) {
115 console.log('Will download and upload %d videos.\n', infoArray.length) 82 console.log('Will download and upload %d videos.\n', infoArray.length)
116 83
117 for (const info of infoArray) { 84 for (const info of infoArray) {
118 await processVideo(info, program[ 'language' ], processOptions.cwd, url, user) 85 await processVideo({
86 cwd: processOptions.cwd,
87 url,
88 user,
89 youtubeInfo: info
90 })
119 } 91 }
120 92
121 console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ]) 93 console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ])
@@ -123,11 +95,18 @@ async function run (user, url: string) {
123 }) 95 })
124} 96}
125 97
126function processVideo (info: any, languageCode: string, cwd: string, url: string, user) { 98function processVideo (parameters: {
99 cwd: string,
100 url: string,
101 user: { username: string, password: string },
102 youtubeInfo: any
103}) {
104 const { youtubeInfo, cwd, url, user } = parameters
105
127 return new Promise(async res => { 106 return new Promise(async res => {
128 if (program[ 'verbose' ]) console.log('Fetching object.', info) 107 if (program[ 'verbose' ]) console.log('Fetching object.', youtubeInfo)
129 108
130 const videoInfo = await fetchObject(info) 109 const videoInfo = await fetchObject(youtubeInfo)
131 if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo) 110 if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo)
132 111
133 const result = await searchVideoWithSort(url, videoInfo.title, '-match') 112 const result = await searchVideoWithSort(url, videoInfo.title, '-match')
@@ -153,7 +132,13 @@ function processVideo (info: any, languageCode: string, cwd: string, url: string
153 } 132 }
154 133
155 console.log(output.join('\n')) 134 console.log(output.join('\n'))
156 await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, cwd, url, user, languageCode) 135 await uploadVideoOnPeerTube({
136 cwd,
137 url,
138 user,
139 videoInfo: normalizeObject(videoInfo),
140 videoPath: path
141 })
157 return res() 142 return res()
158 }) 143 })
159 } catch (err) { 144 } catch (err) {
@@ -163,7 +148,15 @@ function processVideo (info: any, languageCode: string, cwd: string, url: string
163 }) 148 })
164} 149}
165 150
166async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: string, url: string, user, language?: string) { 151async function uploadVideoOnPeerTube (parameters: {
152 videoInfo: any,
153 videoPath: string,
154 cwd: string,
155 url: string,
156 user: { username: string; password: string }
157}) {
158 const { videoInfo, videoPath, cwd, url, user } = parameters
159
167 const category = await getCategory(videoInfo.categories, url) 160 const category = await getCategory(videoInfo.categories, url)
168 const licence = getLicence(videoInfo.license) 161 const licence = getLicence(videoInfo.license)
169 let tags = [] 162 let tags = []
@@ -194,7 +187,7 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st
194 }), 187 }),
195 category, 188 category,
196 licence, 189 licence,
197 language, 190 language: program[ 'language' ],
198 nsfw: isNSFW(videoInfo), 191 nsfw: isNSFW(videoInfo),
199 waitTranscoding: true, 192 waitTranscoding: true,
200 commentsEnabled: true, 193 commentsEnabled: true,
@@ -209,15 +202,21 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st
209 originallyPublishedAt: originallyPublishedAt ? originallyPublishedAt.toISOString() : null 202 originallyPublishedAt: originallyPublishedAt ? originallyPublishedAt.toISOString() : null
210 } 203 }
211 204
205 if (program[ 'channelId' ]) {
206 Object.assign(videoAttributes, { channelId: program['channelId'] })
207 }
208
212 console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) 209 console.log('\nUploading on PeerTube video "%s".', videoAttributes.name)
210
211 let accessToken = await getAccessTokenOrDie(url, user)
212
213 try { 213 try {
214 await uploadVideo(url, accessToken, videoAttributes) 214 await uploadVideo(url, accessToken, videoAttributes)
215 } catch (err) { 215 } catch (err) {
216 if (err.message.indexOf('401') !== -1) { 216 if (err.message.indexOf('401') !== -1) {
217 console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') 217 console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.')
218 218
219 const res = await login(url, client, user) 219 accessToken = await getAccessTokenOrDie(url, user)
220 accessToken = res.body.access_token
221 220
222 await uploadVideo(url, accessToken, videoAttributes) 221 await uploadVideo(url, accessToken, videoAttributes)
223 } else { 222 } else {
@@ -232,6 +231,8 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st
232 console.log('Uploaded video "%s"!\n', videoAttributes.name) 231 console.log('Uploaded video "%s"!\n', videoAttributes.name)
233} 232}
234 233
234/* ---------------------------------------------------------- */
235
235async function getCategory (categories: string[], url: string) { 236async function getCategory (categories: string[], url: string) {
236 if (!categories) return undefined 237 if (!categories) return undefined
237 238
@@ -250,8 +251,6 @@ async function getCategory (categories: string[], url: string) {
250 return undefined 251 return undefined
251} 252}
252 253
253/* ---------------------------------------------------------- */
254
255function getLicence (licence: string) { 254function getLicence (licence: string) {
256 if (!licence) return undefined 255 if (!licence) return undefined
257 256
@@ -305,9 +304,7 @@ function buildUrl (info: any) {
305} 304}
306 305
307function isNSFW (info: any) { 306function isNSFW (info: any) {
308 if (info.age_limit && info.age_limit >= 16) return true 307 return info.age_limit && info.age_limit >= 16
309
310 return false
311} 308}
312 309
313function removeEndSlashes (url: string) { 310function removeEndSlashes (url: string) {
@@ -315,3 +312,39 @@ function removeEndSlashes (url: string) {
315 url.slice(0, -1) 312 url.slice(0, -1)
316 } 313 }
317} 314}
315
316async function promptPassword () {
317 return new Promise<string>((res, rej) => {
318 prompt.start()
319 const schema = {
320 properties: {
321 password: {
322 hidden: true,
323 required: true
324 }
325 }
326 }
327 prompt.get(schema, function (err, result) {
328 if (err) {
329 return rej(err)
330 }
331 return res(result.password)
332 })
333 })
334}
335
336async function getAccessTokenOrDie (url: string, user: UserInfo) {
337 const resClient = await getClient(url)
338 const client = {
339 id: resClient.body.client_id,
340 secret: resClient.body.client_secret
341 }
342
343 try {
344 const res = await login(url, client, user)
345 return res.body.access_token
346 } catch (err) {
347 console.error('Cannot authenticate. Please check your username/password.')
348 process.exit(-1)
349 }
350}
diff --git a/server/tools/peertube-repl.ts b/server/tools/peertube-repl.ts
index 04d8b95a3..fbdec1613 100644
--- a/server/tools/peertube-repl.ts
+++ b/server/tools/peertube-repl.ts
@@ -43,7 +43,7 @@ const start = async () => {
43 Object.defineProperty(context, prop, { 43 Object.defineProperty(context, prop, {
44 configurable: false, 44 configurable: false,
45 enumerable: true, 45 enumerable: true,
46 value: properties[prop] 46 value: properties[ prop ]
47 }) 47 })
48 } 48 }
49 } 49 }
@@ -69,8 +69,7 @@ const start = async () => {
69 69
70} 70}
71 71
72start().then((data) => { 72start()
73 // do nothing 73 .catch((err) => {
74}).catch((err) => { 74 console.error(err)
75 console.error(err) 75 })
76})
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts
index bfce10e54..1da52da31 100644
--- a/server/tools/peertube-upload.ts
+++ b/server/tools/peertube-upload.ts
@@ -4,7 +4,7 @@ import { isAbsolute } from 'path'
4import { getClient, login } from '../../shared/extra-utils' 4import { getClient, login } from '../../shared/extra-utils'
5import { uploadVideo } from '../../shared/extra-utils/' 5import { uploadVideo } from '../../shared/extra-utils/'
6import { VideoPrivacy } from '../../shared/models/videos' 6import { VideoPrivacy } from '../../shared/models/videos'
7import { getRemoteObjectOrDie, getSettings } from './cli' 7import { getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
8 8
9program 9program
10 .name('upload') 10 .name('upload')
@@ -26,31 +26,31 @@ program
26 .option('-f, --file <file>', 'Video absolute file path') 26 .option('-f, --file <file>', 'Video absolute file path')
27 .parse(process.argv) 27 .parse(process.argv)
28 28
29getSettings() 29Promise.all([ getSettings(), getNetrc() ])
30 .then(settings => { 30 .then(([ settings, netrc ]) => {
31 const { url, username, password } = getRemoteObjectOrDie(program, settings) 31 const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
32 32
33 if (!program['videoName'] || !program['file'] || !program['channelId']) { 33 if (!program[ 'videoName' ] || !program[ 'file' ] || !program[ 'channelId' ]) {
34 if (!program['videoName']) console.error('--video-name is required.') 34 if (!program[ 'videoName' ]) console.error('--video-name is required.')
35 if (!program['file']) console.error('--file is required.') 35 if (!program[ 'file' ]) console.error('--file is required.')
36 if (!program['channelId']) console.error('--channel-id is required.') 36 if (!program[ 'channelId' ]) console.error('--channel-id is required.')
37 37
38 process.exit(-1) 38 process.exit(-1)
39 } 39 }
40 40
41 if (isAbsolute(program['file']) === false) { 41 if (isAbsolute(program[ 'file' ]) === false) {
42 console.error('File path should be absolute.') 42 console.error('File path should be absolute.')
43 process.exit(-1) 43 process.exit(-1)
44 } 44 }
45 45
46 run(url, username, password).catch(err => { 46 run(url, username, password).catch(err => {
47 console.error(err) 47 console.error(err)
48 process.exit(-1) 48 process.exit(-1)
49 }) 49 })
50 }) 50 })
51 51
52async function run (url: string, username: string, password: string) { 52async function run (url: string, username: string, password: string) {
53 const resClient = await getClient(program[ 'url' ]) 53 const resClient = await getClient(url)
54 const client = { 54 const client = {
55 id: resClient.body.client_id, 55 id: resClient.body.client_id,
56 secret: resClient.body.client_secret 56 secret: resClient.body.client_secret
@@ -71,27 +71,27 @@ async function run (url: string, username: string, password: string) {
71 console.log('Uploading %s video...', program[ 'videoName' ]) 71 console.log('Uploading %s video...', program[ 'videoName' ])
72 72
73 const videoAttributes = { 73 const videoAttributes = {
74 name: program['videoName'], 74 name: program[ 'videoName' ],
75 category: program['category'] || undefined, 75 category: program[ 'category' ] || undefined,
76 channelId: program['channelId'], 76 channelId: program[ 'channelId' ],
77 licence: program['licence'] || undefined, 77 licence: program[ 'licence' ] || undefined,
78 language: program['language'] || undefined, 78 language: program[ 'language' ] || undefined,
79 nsfw: program['nsfw'] !== undefined ? program['nsfw'] : false, 79 nsfw: program[ 'nsfw' ] !== undefined ? program[ 'nsfw' ] : false,
80 description: program['videoDescription'] || undefined, 80 description: program[ 'videoDescription' ] || undefined,
81 tags: program['tags'] || [], 81 tags: program[ 'tags' ] || [],
82 commentsEnabled: program['commentsEnabled'] !== undefined ? program['commentsEnabled'] : true, 82 commentsEnabled: program[ 'commentsEnabled' ] !== undefined ? program[ 'commentsEnabled' ] : true,
83 downloadEnabled: program['downloadEnabled'] !== undefined ? program['downloadEnabled'] : true, 83 downloadEnabled: program[ 'downloadEnabled' ] !== undefined ? program[ 'downloadEnabled' ] : true,
84 fixture: program['file'], 84 fixture: program[ 'file' ],
85 thumbnailfile: program['thumbnail'], 85 thumbnailfile: program[ 'thumbnail' ],
86 previewfile: program['preview'], 86 previewfile: program[ 'preview' ],
87 waitTranscoding: true, 87 waitTranscoding: true,
88 privacy: program['privacy'] || VideoPrivacy.PUBLIC, 88 privacy: program[ 'privacy' ] || VideoPrivacy.PUBLIC,
89 support: undefined 89 support: undefined
90 } 90 }
91 91
92 try { 92 try {
93 await uploadVideo(url, accessToken, videoAttributes) 93 await uploadVideo(url, accessToken, videoAttributes)
94 console.log(`Video ${program['videoName']} uploaded.`) 94 console.log(`Video ${program[ 'videoName' ]} uploaded.`)
95 process.exit(0) 95 process.exit(0)
96 } catch (err) { 96 } catch (err) {
97 console.error(require('util').inspect(err)) 97 console.error(require('util').inspect(err))
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
index 5d3ab2815..daa5586c3 100644
--- a/server/tools/peertube.ts
+++ b/server/tools/peertube.ts
@@ -63,9 +63,10 @@ if (!process.argv.slice(2).length) {
63 63
64getSettings() 64getSettings()
65 .then(settings => { 65 .then(settings => {
66 const state = (settings.default === undefined || settings.default === -1) ? 66 const state = (settings.default === undefined || settings.default === -1)
67 'no instance selected, commands will require explicit arguments' : 67 ? 'no instance selected, commands will require explicit arguments'
68 ('instance ' + settings.remotes[settings.default] + ' selected') 68 : 'instance ' + settings.remotes[settings.default] + ' selected'
69
69 program 70 program
70 .on('--help', function () { 71 .on('--help', function () {
71 console.log() 72 console.log()
diff --git a/server/tools/yarn.lock b/server/tools/yarn.lock
index 3c3778d3f..7ef68fc71 100644
--- a/server/tools/yarn.lock
+++ b/server/tools/yarn.lock
@@ -293,6 +293,13 @@ chunk-store-stream@^3.0.1:
293 block-stream2 "^1.0.0" 293 block-stream2 "^1.0.0"
294 readable-stream "^2.0.5" 294 readable-stream "^2.0.5"
295 295
296cli-table@^0.3.1:
297 version "0.3.1"
298 resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
299 integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM=
300 dependencies:
301 colors "1.0.3"
302
296cliui@^3.2.0: 303cliui@^3.2.0:
297 version "3.2.0" 304 version "3.2.0"
298 resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" 305 resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@@ -317,6 +324,11 @@ code-point-at@^1.0.0:
317 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 324 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
318 integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 325 integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
319 326
327colors@1.0.3:
328 version "1.0.3"
329 resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
330 integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
331
320colour@latest: 332colour@latest:
321 version "0.7.1" 333 version "0.7.1"
322 resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" 334 resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778"
@@ -373,6 +385,17 @@ create-torrent@^3.23.1, create-torrent@^3.33.0:
373 run-parallel "^1.0.0" 385 run-parallel "^1.0.0"
374 simple-sha1 "^2.0.0" 386 simple-sha1 "^2.0.0"
375 387
388cross-spawn@^6.0.0:
389 version "6.0.5"
390 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
391 integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
392 dependencies:
393 nice-try "^1.0.4"
394 path-key "^2.0.1"
395 semver "^5.5.0"
396 shebang-command "^1.2.0"
397 which "^1.2.9"
398
376debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0: 399debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0:
377 version "2.6.9" 400 version "2.6.9"
378 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 401 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -493,6 +516,19 @@ error-ex@^1.2.0:
493 dependencies: 516 dependencies:
494 is-arrayish "^0.2.1" 517 is-arrayish "^0.2.1"
495 518
519execa@^0.10.0:
520 version "0.10.0"
521 resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
522 integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
523 dependencies:
524 cross-spawn "^6.0.0"
525 get-stream "^3.0.0"
526 is-stream "^1.1.0"
527 npm-run-path "^2.0.0"
528 p-finally "^1.0.0"
529 signal-exit "^3.0.0"
530 strip-eof "^1.0.0"
531
496executable@^4.0.0: 532executable@^4.0.0:
497 version "4.1.1" 533 version "4.1.1"
498 resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" 534 resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
@@ -576,6 +612,11 @@ get-stdin@^6.0.0:
576 resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" 612 resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
577 integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== 613 integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
578 614
615get-stream@^3.0.0:
616 version "3.0.0"
617 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
618 integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
619
579glob@^7.1.3: 620glob@^7.1.3:
580 version "7.1.4" 621 version "7.1.4"
581 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" 622 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
@@ -694,6 +735,11 @@ is-fullwidth-code-point@^2.0.0:
694 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 735 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
695 integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 736 integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
696 737
738is-stream@^1.1.0:
739 version "1.1.0"
740 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
741 integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
742
697is-typedarray@^1.0.0: 743is-typedarray@^1.0.0:
698 version "1.0.0" 744 version "1.0.0"
699 resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 745 resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -963,6 +1009,14 @@ netmask@^1.0.6:
963 resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" 1009 resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
964 integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= 1010 integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=
965 1011
1012netrc-parser@^3.1.6:
1013 version "3.1.6"
1014 resolved "https://registry.yarnpkg.com/netrc-parser/-/netrc-parser-3.1.6.tgz#7243c9ec850b8e805b9bdc7eae7b1450d4a96e72"
1015 integrity sha512-lY+fmkqSwntAAjfP63jB4z5p5WbuZwyMCD3pInT7dpHU/Gc6Vv90SAC6A0aNiqaRGHiuZFBtiwu+pu8W/Eyotw==
1016 dependencies:
1017 debug "^3.1.0"
1018 execa "^0.10.0"
1019
966network-address@^1.0.0, network-address@^1.1.0: 1020network-address@^1.0.0, network-address@^1.1.0:
967 version "1.1.2" 1021 version "1.1.2"
968 resolved "https://registry.yarnpkg.com/network-address/-/network-address-1.1.2.tgz#4aa7bfd43f03f0b81c9702b13d6a858ddb326f3e" 1022 resolved "https://registry.yarnpkg.com/network-address/-/network-address-1.1.2.tgz#4aa7bfd43f03f0b81c9702b13d6a858ddb326f3e"
@@ -973,6 +1027,11 @@ next-event@^1.0.0:
973 resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8" 1027 resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8"
974 integrity sha1-53eKzeLlWALgrRh5w5z2917aYdg= 1028 integrity sha1-53eKzeLlWALgrRh5w5z2917aYdg=
975 1029
1030nice-try@^1.0.4:
1031 version "1.0.5"
1032 resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
1033 integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
1034
976node-cmake@2.3.2: 1035node-cmake@2.3.2:
977 version "2.3.2" 1036 version "2.3.2"
978 resolved "https://registry.yarnpkg.com/node-cmake/-/node-cmake-2.3.2.tgz#e0fbc54b11405b07705e4d6d41865ae95ad289d0" 1037 resolved "https://registry.yarnpkg.com/node-cmake/-/node-cmake-2.3.2.tgz#e0fbc54b11405b07705e4d6d41865ae95ad289d0"
@@ -1049,6 +1108,13 @@ npm-packlist@^1.1.6:
1049 ignore-walk "^3.0.1" 1108 ignore-walk "^3.0.1"
1050 npm-bundled "^1.0.1" 1109 npm-bundled "^1.0.1"
1051 1110
1111npm-run-path@^2.0.0:
1112 version "2.0.2"
1113 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
1114 integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
1115 dependencies:
1116 path-key "^2.0.0"
1117
1052npmlog@^4.0.2: 1118npmlog@^4.0.2:
1053 version "4.1.2" 1119 version "4.1.2"
1054 resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" 1120 resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
@@ -1111,6 +1177,11 @@ osenv@^0.1.4:
1111 os-homedir "^1.0.0" 1177 os-homedir "^1.0.0"
1112 os-tmpdir "^1.0.0" 1178 os-tmpdir "^1.0.0"
1113 1179
1180p-finally@^1.0.0:
1181 version "1.0.0"
1182 resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
1183 integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
1184
1114package-json-versionify@^1.0.2: 1185package-json-versionify@^1.0.2:
1115 version "1.0.4" 1186 version "1.0.4"
1116 resolved "https://registry.yarnpkg.com/package-json-versionify/-/package-json-versionify-1.0.4.tgz#5860587a944873a6b7e6d26e8e51ffb22315bf17" 1187 resolved "https://registry.yarnpkg.com/package-json-versionify/-/package-json-versionify-1.0.4.tgz#5860587a944873a6b7e6d26e8e51ffb22315bf17"
@@ -1155,6 +1226,11 @@ path-is-absolute@^1.0.0:
1155 resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1226 resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
1156 integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 1227 integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
1157 1228
1229path-key@^2.0.0, path-key@^2.0.1:
1230 version "2.0.1"
1231 resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
1232 integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
1233
1158path-parse@^1.0.6: 1234path-parse@^1.0.6:
1159 version "1.0.6" 1235 version "1.0.6"
1160 resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 1236 resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
@@ -1400,7 +1476,7 @@ sax@>=0.6.0, sax@^1.2.4:
1400 resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 1476 resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
1401 integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== 1477 integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
1402 1478
1403"semver@2 || 3 || 4 || 5", semver@^5.3.0: 1479"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0:
1404 version "5.7.0" 1480 version "5.7.0"
1405 resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" 1481 resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
1406 integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== 1482 integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
@@ -1415,6 +1491,18 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
1415 resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 1491 resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
1416 integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 1492 integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
1417 1493
1494shebang-command@^1.2.0:
1495 version "1.2.0"
1496 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
1497 integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
1498 dependencies:
1499 shebang-regex "^1.0.0"
1500
1501shebang-regex@^1.0.0:
1502 version "1.0.0"
1503 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
1504 integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
1505
1418signal-exit@^3.0.0: 1506signal-exit@^3.0.0:
1419 version "3.0.2" 1507 version "3.0.2"
1420 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 1508 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@@ -1591,6 +1679,11 @@ strip-bom@^2.0.0:
1591 dependencies: 1679 dependencies:
1592 is-utf8 "^0.2.0" 1680 is-utf8 "^0.2.0"
1593 1681
1682strip-eof@^1.0.0:
1683 version "1.0.0"
1684 resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
1685 integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
1686
1594strip-json-comments@~2.0.1: 1687strip-json-comments@~2.0.1:
1595 version "2.0.1" 1688 version "2.0.1"
1596 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1689 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -1855,7 +1948,7 @@ which-module@^1.0.0:
1855 resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" 1948 resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
1856 integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= 1949 integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=
1857 1950
1858which@^1.2.14: 1951which@^1.2.14, which@^1.2.9:
1859 version "1.3.1" 1952 version "1.3.1"
1860 resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 1953 resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
1861 integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 1954 integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==