diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2018-09-13 14:27:44 +0200 |
---|---|---|
committer | Rigel Kent <sendmemail@rigelk.eu> | 2018-09-14 11:08:55 +0200 |
commit | 8704acf49efc770d73bf07c10468ed8c74d28a83 (patch) | |
tree | ffd46289fcf9a13ac4412b167e9f71dfb35753c5 /server | |
parent | 1d9d9cfdcf3983e3fd89026bc4b5633a8abf5752 (diff) | |
download | PeerTube-8704acf49efc770d73bf07c10468ed8c74d28a83.tar.gz PeerTube-8704acf49efc770d73bf07c10468ed8c74d28a83.tar.zst PeerTube-8704acf49efc770d73bf07c10468ed8c74d28a83.zip |
one cli to unite them all
Ash nazg thrakatulûk agh burzum-ishi krimpatul
- refactor import-videos to use the youtubeDL helper
- add very basic tests for the cli
Diffstat (limited to 'server')
-rw-r--r-- | server/helpers/youtube-dl.ts | 23 | ||||
-rw-r--r-- | server/tests/cli/index.ts | 1 | ||||
-rw-r--r-- | server/tests/cli/peertube.ts | 51 | ||||
-rw-r--r-- | server/tools/cli.ts | 63 | ||||
-rw-r--r-- | server/tools/peertube-auth.ts | 140 | ||||
-rw-r--r-- | server/tools/peertube-get-access-token.ts (renamed from server/tools/get-access-token.ts) | 5 | ||||
-rw-r--r-- | server/tools/peertube-import-videos.ts (renamed from server/tools/import-videos.ts) | 109 | ||||
-rw-r--r-- | server/tools/peertube-upload.ts (renamed from server/tools/upload.ts) | 74 | ||||
-rw-r--r-- | server/tools/peertube-watch.ts | 61 | ||||
-rwxr-xr-x | server/tools/peertube.ts | 81 |
10 files changed, 537 insertions, 71 deletions
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index 6738090f3..8b2bc1782 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts | |||
@@ -14,9 +14,9 @@ export type YoutubeDLInfo = { | |||
14 | thumbnailUrl?: string | 14 | thumbnailUrl?: string |
15 | } | 15 | } |
16 | 16 | ||
17 | function getYoutubeDLInfo (url: string): Promise<YoutubeDLInfo> { | 17 | function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> { |
18 | return new Promise<YoutubeDLInfo>(async (res, rej) => { | 18 | return new Promise<YoutubeDLInfo>(async (res, rej) => { |
19 | const options = [ '-j', '--flat-playlist' ] | 19 | const options = opts || [ '-j', '--flat-playlist' ] |
20 | 20 | ||
21 | const youtubeDL = await safeGetYoutubeDL() | 21 | const youtubeDL = await safeGetYoutubeDL() |
22 | youtubeDL.getInfo(url, options, (err, info) => { | 22 | youtubeDL.getInfo(url, options, (err, info) => { |
@@ -48,15 +48,6 @@ function downloadYoutubeDLVideo (url: string) { | |||
48 | }) | 48 | }) |
49 | } | 49 | } |
50 | 50 | ||
51 | // --------------------------------------------------------------------------- | ||
52 | |||
53 | export { | ||
54 | downloadYoutubeDLVideo, | ||
55 | getYoutubeDLInfo | ||
56 | } | ||
57 | |||
58 | // --------------------------------------------------------------------------- | ||
59 | |||
60 | async function safeGetYoutubeDL () { | 51 | async function safeGetYoutubeDL () { |
61 | let youtubeDL | 52 | let youtubeDL |
62 | 53 | ||
@@ -71,6 +62,16 @@ async function safeGetYoutubeDL () { | |||
71 | return youtubeDL | 62 | return youtubeDL |
72 | } | 63 | } |
73 | 64 | ||
65 | // --------------------------------------------------------------------------- | ||
66 | |||
67 | export { | ||
68 | downloadYoutubeDLVideo, | ||
69 | getYoutubeDLInfo, | ||
70 | safeGetYoutubeDL | ||
71 | } | ||
72 | |||
73 | // --------------------------------------------------------------------------- | ||
74 | |||
74 | function normalizeObject (obj: any) { | 75 | function normalizeObject (obj: any) { |
75 | const newObj: any = {} | 76 | const newObj: any = {} |
76 | 77 | ||
diff --git a/server/tests/cli/index.ts b/server/tests/cli/index.ts index f99eafe03..33e33a070 100644 --- a/server/tests/cli/index.ts +++ b/server/tests/cli/index.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | // Order of the tests we want to execute | 1 | // Order of the tests we want to execute |
2 | import './create-transcoding-job' | 2 | import './create-transcoding-job' |
3 | import './create-import-video-file-job' | 3 | import './create-import-video-file-job' |
4 | import './peertube' | ||
4 | import './reset-password' | 5 | import './reset-password' |
5 | import './update-host' | 6 | import './update-host' |
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts new file mode 100644 index 000000000..548fd1257 --- /dev/null +++ b/server/tests/cli/peertube.ts | |||
@@ -0,0 +1,51 @@ | |||
1 | import 'mocha' | ||
2 | import { | ||
3 | expect | ||
4 | } from 'chai' | ||
5 | import { | ||
6 | createUser, | ||
7 | execCLI, | ||
8 | flushTests, | ||
9 | getEnvCli, | ||
10 | killallServers, | ||
11 | runServer, | ||
12 | ServerInfo, | ||
13 | setAccessTokensToServers | ||
14 | } from '../utils' | ||
15 | |||
16 | describe('Test CLI wrapper', function () { | ||
17 | let server: ServerInfo | ||
18 | const cmd = 'node ./dist/server/tools/peertube.js' | ||
19 | |||
20 | before(async function () { | ||
21 | this.timeout(30000) | ||
22 | |||
23 | await flushTests() | ||
24 | server = await runServer(1) | ||
25 | await setAccessTokensToServers([ server ]) | ||
26 | |||
27 | await createUser(server.url, server.accessToken, 'user_1', 'super password') | ||
28 | }) | ||
29 | |||
30 | it('Should display no selected instance', async function () { | ||
31 | this.timeout(60000) | ||
32 | |||
33 | const env = getEnvCli(server) | ||
34 | const stdout = await execCLI(`${env} ${cmd} --help`) | ||
35 | |||
36 | expect(stdout).to.contain('selected') | ||
37 | }) | ||
38 | |||
39 | it('Should remember the authentifying material of the user', async function () { | ||
40 | this.timeout(60000) | ||
41 | |||
42 | const env = getEnvCli(server) | ||
43 | const stdout = await execCLI(`${env} ` + cmd + ` auth add --url ${server.url} -U user_1 -p "super password"`) | ||
44 | }) | ||
45 | |||
46 | after(async function () { | ||
47 | await execCLI(cmd + ` auth del ${server.url}`) | ||
48 | |||
49 | killallServers([ server ]) | ||
50 | }) | ||
51 | }) | ||
diff --git a/server/tools/cli.ts b/server/tools/cli.ts new file mode 100644 index 000000000..9a170d4da --- /dev/null +++ b/server/tools/cli.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | const config = require('application-config')('PeerTube/CLI') | ||
2 | const netrc = require('netrc-parser').default | ||
3 | |||
4 | const version = () => { | ||
5 | const tag = require('child_process') | ||
6 | .execSync('[[ ! -d .git ]] || git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null || true', { stdio: [0,1,2] }) | ||
7 | if (tag) return tag | ||
8 | |||
9 | const version = require('child_process') | ||
10 | .execSync('[[ ! -d .git ]] || git rev-parse --short HEAD').toString().trim() | ||
11 | if (version) return version | ||
12 | |||
13 | return require('../../../package.json').version | ||
14 | } | ||
15 | |||
16 | let settings = { | ||
17 | remotes: [], | ||
18 | default: 0 | ||
19 | } | ||
20 | |||
21 | interface Settings { | ||
22 | remotes: any[], | ||
23 | default: number | ||
24 | } | ||
25 | |||
26 | async function getSettings () { | ||
27 | return new Promise<Settings>((res, rej) => { | ||
28 | let settings = { | ||
29 | remotes: [], | ||
30 | default: 0 | ||
31 | } as Settings | ||
32 | config.read((err, data) => { | ||
33 | if (err) { | ||
34 | return rej(err) | ||
35 | } | ||
36 | return res(data || settings) | ||
37 | }) | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | async function writeSettings (settings) { | ||
42 | return new Promise((res, rej) => { | ||
43 | config.write(settings, function (err) { | ||
44 | if (err) { | ||
45 | return rej(err) | ||
46 | } | ||
47 | return res() | ||
48 | }) | ||
49 | }) | ||
50 | } | ||
51 | |||
52 | netrc.loadSync() | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | export { | ||
57 | version, | ||
58 | config, | ||
59 | settings, | ||
60 | getSettings, | ||
61 | writeSettings, | ||
62 | netrc | ||
63 | } | ||
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts new file mode 100644 index 000000000..33438811e --- /dev/null +++ b/server/tools/peertube-auth.ts | |||
@@ -0,0 +1,140 @@ | |||
1 | import * as program from 'commander' | ||
2 | import * as prompt from 'prompt' | ||
3 | const Table = require('cli-table') | ||
4 | import { getSettings, writeSettings, netrc } from './cli' | ||
5 | import { isHostValid } from '../helpers/custom-validators/servers' | ||
6 | import { isUserUsernameValid } from '../helpers/custom-validators/users' | ||
7 | |||
8 | function delInstance (url: string) { | ||
9 | return new Promise((res, rej): void => { | ||
10 | getSettings() | ||
11 | .then(async (settings) => { | ||
12 | settings.remotes.splice(settings.remotes.indexOf(url)) | ||
13 | await writeSettings(settings) | ||
14 | delete netrc.machines[url] | ||
15 | netrc.save() | ||
16 | res() | ||
17 | }) | ||
18 | .catch(err => rej(err)) | ||
19 | }) | ||
20 | } | ||
21 | |||
22 | async function setInstance (url: string, username: string, password: string) { | ||
23 | return new Promise((res, rej): void => { | ||
24 | getSettings() | ||
25 | .then(async settings => { | ||
26 | if (settings.remotes.indexOf(url) === -1) { | ||
27 | settings.remotes.push(url) | ||
28 | } | ||
29 | await writeSettings(settings) | ||
30 | netrc.machines[url] = { login: username, password } | ||
31 | netrc.save() | ||
32 | res() | ||
33 | }) | ||
34 | .catch(err => rej(err)) | ||
35 | }) | ||
36 | } | ||
37 | |||
38 | function isURLaPeerTubeInstance (url: string) { | ||
39 | return isHostValid(url) || (url.includes('localhost')) | ||
40 | } | ||
41 | |||
42 | program | ||
43 | .name('auth') | ||
44 | .usage('[command] [options]') | ||
45 | |||
46 | program | ||
47 | .command('add') | ||
48 | .description('remember your accounts on remote instances for easier use') | ||
49 | .option('-u, --url <url>', 'Server url') | ||
50 | .option('-U, --username <username>', 'Username') | ||
51 | .option('-p, --password <token>', 'Password') | ||
52 | .option('--default', 'add the entry as the new default') | ||
53 | .action(options => { | ||
54 | prompt.override = options | ||
55 | prompt.start() | ||
56 | prompt.get({ | ||
57 | properties: { | ||
58 | url: { | ||
59 | description: 'instance url', | ||
60 | conform: (value) => isURLaPeerTubeInstance(value), | ||
61 | required: true | ||
62 | }, | ||
63 | username: { | ||
64 | conform: (value) => isUserUsernameValid(value), | ||
65 | message: 'Name must be only letters, spaces, or dashes', | ||
66 | required: true | ||
67 | }, | ||
68 | password: { | ||
69 | hidden: true, | ||
70 | replace: '*', | ||
71 | required: true | ||
72 | } | ||
73 | } | ||
74 | }, (_, result) => { | ||
75 | setInstance(result.url, result.username, result.password) | ||
76 | }) | ||
77 | }) | ||
78 | |||
79 | program | ||
80 | .command('del <url>') | ||
81 | .description('unregisters a remote instance') | ||
82 | .action((url) => { | ||
83 | delInstance(url) | ||
84 | }) | ||
85 | |||
86 | program | ||
87 | .command('list') | ||
88 | .description('lists registered remote instances') | ||
89 | .action(() => { | ||
90 | getSettings() | ||
91 | .then(settings => { | ||
92 | const table = new Table({ | ||
93 | head: ['instance', 'login'], | ||
94 | colWidths: [30, 30] | ||
95 | }) | ||
96 | netrc.loadSync() | ||
97 | settings.remotes.forEach(element => { | ||
98 | table.push([ | ||
99 | element, | ||
100 | netrc.machines[element].login | ||
101 | ]) | ||
102 | }) | ||
103 | |||
104 | console.log(table.toString()) | ||
105 | }) | ||
106 | }) | ||
107 | |||
108 | program | ||
109 | .command('set-default <url>') | ||
110 | .description('set an existing entry as default') | ||
111 | .action((url) => { | ||
112 | getSettings() | ||
113 | .then(settings => { | ||
114 | const instanceExists = settings.remotes.indexOf(url) !== -1 | ||
115 | |||
116 | if (instanceExists) { | ||
117 | settings.default = settings.remotes.indexOf(url) | ||
118 | writeSettings(settings) | ||
119 | } else { | ||
120 | console.log('<url> is not a registered instance.') | ||
121 | process.exit(-1) | ||
122 | } | ||
123 | }) | ||
124 | }) | ||
125 | |||
126 | program.on('--help', function () { | ||
127 | console.log(' Examples:') | ||
128 | console.log() | ||
129 | console.log(' $ peertube add -u peertube.cpy.re -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"') | ||
130 | console.log(' $ peertube add -u peertube.cpy.re -U root') | ||
131 | console.log(' $ peertube list') | ||
132 | console.log(' $ peertube del peertube.cpy.re') | ||
133 | console.log() | ||
134 | }) | ||
135 | |||
136 | if (!process.argv.slice(2).length) { | ||
137 | program.outputHelp() | ||
138 | } | ||
139 | |||
140 | program.parse(process.argv) | ||
diff --git a/server/tools/get-access-token.ts b/server/tools/peertube-get-access-token.ts index d86c84c8d..eb2571a03 100644 --- a/server/tools/get-access-token.ts +++ b/server/tools/peertube-get-access-token.ts | |||
@@ -19,7 +19,10 @@ if ( | |||
19 | !program['username'] || | 19 | !program['username'] || |
20 | !program['password'] | 20 | !program['password'] |
21 | ) { | 21 | ) { |
22 | throw new Error('All arguments are required.') | 22 | if (!program['url']) console.error('--url field is required.') |
23 | if (!program['username']) console.error('--username field is required.') | ||
24 | if (!program['password']) console.error('--password field is required.') | ||
25 | process.exit(-1) | ||
23 | } | 26 | } |
24 | 27 | ||
25 | getClient(program.url) | 28 | getClient(program.url) |
diff --git a/server/tools/import-videos.ts b/server/tools/peertube-import-videos.ts index 3ff194c83..13090a028 100644 --- a/server/tools/import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -3,7 +3,6 @@ require('tls').DEFAULT_ECDH_CURVE = 'auto' | |||
3 | 3 | ||
4 | import * as program from 'commander' | 4 | import * as program from 'commander' |
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import * as youtubeDL from 'youtube-dl' | ||
7 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../shared/models/videos' |
8 | import { doRequestAndSaveToFile } from '../helpers/requests' | 7 | import { doRequestAndSaveToFile } from '../helpers/requests' |
9 | import { CONSTRAINTS_FIELDS } from '../initializers' | 8 | import { CONSTRAINTS_FIELDS } from '../initializers' |
@@ -11,8 +10,19 @@ import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo | |||
11 | import { truncate } from 'lodash' | 10 | import { truncate } from 'lodash' |
12 | import * as prompt from 'prompt' | 11 | import * as prompt from 'prompt' |
13 | import { remove } from 'fs-extra' | 12 | import { remove } from 'fs-extra' |
13 | import { safeGetYoutubeDL } from '../helpers/youtube-dl' | ||
14 | import { getSettings, netrc } from './cli' | ||
15 | |||
16 | let accessToken: string | ||
17 | let client: { id: string, secret: string } | ||
18 | |||
19 | const processOptions = { | ||
20 | cwd: __dirname, | ||
21 | maxBuffer: Infinity | ||
22 | } | ||
14 | 23 | ||
15 | program | 24 | program |
25 | .name('import-videos') | ||
16 | .option('-u, --url <url>', 'Server url') | 26 | .option('-u, --url <url>', 'Server url') |
17 | .option('-U, --username <username>', 'Username') | 27 | .option('-U, --username <username>', 'Username') |
18 | .option('-p, --password <token>', 'Password') | 28 | .option('-p, --password <token>', 'Password') |
@@ -21,29 +31,50 @@ program | |||
21 | .option('-v, --verbose', 'Verbose mode') | 31 | .option('-v, --verbose', 'Verbose mode') |
22 | .parse(process.argv) | 32 | .parse(process.argv) |
23 | 33 | ||
24 | if ( | 34 | getSettings() |
25 | !program['url'] || | 35 | .then(settings => { |
26 | !program['username'] || | 36 | if ( |
27 | !program['targetUrl'] | 37 | (!program['url'] || |
28 | ) { | 38 | !program['username'] || |
29 | console.error('All arguments are required.') | 39 | !program['password']) && |
30 | process.exit(-1) | 40 | (settings.remotes.length === 0) |
31 | } | 41 | ) { |
42 | if (!program['url']) console.error('--url field is required.') | ||
43 | if (!program['username']) console.error('--username field is required.') | ||
44 | if (!program['password']) console.error('--password field is required.') | ||
45 | if (!program['targetUrl']) console.error('--targetUrl field is required.') | ||
46 | process.exit(-1) | ||
47 | } | ||
32 | 48 | ||
33 | const user = { | 49 | if ( |
34 | username: program['username'], | 50 | (!program['url'] || |
35 | password: program['password'] | 51 | !program['username'] || |
36 | } | 52 | !program['password']) && |
53 | (settings.remotes.length > 0) | ||
54 | ) { | ||
55 | if (!program['url']) { | ||
56 | program['url'] = (settings.default !== -1) ? | ||
57 | settings.remotes[settings.default] : | ||
58 | settings.remotes[0] | ||
59 | } | ||
60 | if (!program['username']) program['username'] = netrc.machines[program['url']].login | ||
61 | if (!program['password']) program['password'] = netrc.machines[program['url']].password | ||
62 | } | ||
37 | 63 | ||
38 | run().catch(err => console.error(err)) | 64 | if ( |
65 | !program['targetUrl'] | ||
66 | ) { | ||
67 | if (!program['targetUrl']) console.error('--targetUrl field is required.') | ||
68 | process.exit(-1) | ||
69 | } | ||
39 | 70 | ||
40 | let accessToken: string | 71 | const user = { |
41 | let client: { id: string, secret: string } | 72 | username: program['username'], |
73 | password: program['password'] | ||
74 | } | ||
42 | 75 | ||
43 | const processOptions = { | 76 | run(user, program['url']).catch(err => console.error(err)) |
44 | cwd: __dirname, | 77 | }) |
45 | maxBuffer: Infinity | ||
46 | } | ||
47 | 78 | ||
48 | async function promptPassword () { | 79 | async function promptPassword () { |
49 | return new Promise((res, rej) => { | 80 | return new Promise((res, rej) => { |
@@ -65,20 +96,22 @@ async function promptPassword () { | |||
65 | }) | 96 | }) |
66 | } | 97 | } |
67 | 98 | ||
68 | async function run () { | 99 | async function run (user, url: string) { |
69 | if (!user.password) { | 100 | if (!user.password) { |
70 | user.password = await promptPassword() | 101 | user.password = await promptPassword() |
71 | } | 102 | } |
72 | 103 | ||
73 | const res = await getClient(program['url']) | 104 | const res = await getClient(url) |
74 | client = { | 105 | client = { |
75 | id: res.body.client_id, | 106 | id: res.body.client_id, |
76 | secret: res.body.client_secret | 107 | secret: res.body.client_secret |
77 | } | 108 | } |
78 | 109 | ||
79 | const res2 = await login(program['url'], client, user) | 110 | const res2 = await login(url, client, user) |
80 | accessToken = res2.body.access_token | 111 | accessToken = res2.body.access_token |
81 | 112 | ||
113 | const youtubeDL = await safeGetYoutubeDL() | ||
114 | |||
82 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] | 115 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] |
83 | youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => { | 116 | youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => { |
84 | if (err) { | 117 | if (err) { |
@@ -97,7 +130,7 @@ async function run () { | |||
97 | console.log('Will download and upload %d videos.\n', infoArray.length) | 130 | console.log('Will download and upload %d videos.\n', infoArray.length) |
98 | 131 | ||
99 | for (const info of infoArray) { | 132 | for (const info of infoArray) { |
100 | await processVideo(info, program['language']) | 133 | await processVideo(info, program['language'], processOptions.cwd, url, user) |
101 | } | 134 | } |
102 | 135 | ||
103 | // https://www.youtube.com/watch?v=2Upx39TBc1s | 136 | // https://www.youtube.com/watch?v=2Upx39TBc1s |
@@ -106,14 +139,14 @@ async function run () { | |||
106 | }) | 139 | }) |
107 | } | 140 | } |
108 | 141 | ||
109 | function processVideo (info: any, languageCode: string) { | 142 | function processVideo (info: any, languageCode: string, cwd: string, url: string, user) { |
110 | return new Promise(async res => { | 143 | return new Promise(async res => { |
111 | if (program['verbose']) console.log('Fetching object.', info) | 144 | if (program['verbose']) console.log('Fetching object.', info) |
112 | 145 | ||
113 | const videoInfo = await fetchObject(info) | 146 | const videoInfo = await fetchObject(info) |
114 | if (program['verbose']) console.log('Fetched object.', videoInfo) | 147 | if (program['verbose']) console.log('Fetched object.', videoInfo) |
115 | 148 | ||
116 | const result = await searchVideoWithSort(program['url'], videoInfo.title, '-match') | 149 | const result = await searchVideoWithSort(url, videoInfo.title, '-match') |
117 | 150 | ||
118 | console.log('############################################################\n') | 151 | console.log('############################################################\n') |
119 | 152 | ||
@@ -122,12 +155,13 @@ function processVideo (info: any, languageCode: string) { | |||
122 | return res() | 155 | return res() |
123 | } | 156 | } |
124 | 157 | ||
125 | const path = join(__dirname, new Date().getTime() + '.mp4') | 158 | const path = join(cwd, new Date().getTime() + '.mp4') |
126 | 159 | ||
127 | console.log('Downloading video "%s"...', videoInfo.title) | 160 | console.log('Downloading video "%s"...', videoInfo.title) |
128 | 161 | ||
129 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] | 162 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] |
130 | try { | 163 | try { |
164 | const youtubeDL = await safeGetYoutubeDL() | ||
131 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { | 165 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { |
132 | if (err) { | 166 | if (err) { |
133 | console.error(err) | 167 | console.error(err) |
@@ -135,7 +169,7 @@ function processVideo (info: any, languageCode: string) { | |||
135 | } | 169 | } |
136 | 170 | ||
137 | console.log(output.join('\n')) | 171 | console.log(output.join('\n')) |
138 | await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, languageCode) | 172 | await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, cwd, url, user, languageCode) |
139 | return res() | 173 | return res() |
140 | }) | 174 | }) |
141 | } catch (err) { | 175 | } catch (err) { |
@@ -145,8 +179,8 @@ function processVideo (info: any, languageCode: string) { | |||
145 | }) | 179 | }) |
146 | } | 180 | } |
147 | 181 | ||
148 | async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, language?: string) { | 182 | async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: string, url: string, user, language?: string) { |
149 | const category = await getCategory(videoInfo.categories) | 183 | const category = await getCategory(videoInfo.categories, url) |
150 | const licence = getLicence(videoInfo.license) | 184 | const licence = getLicence(videoInfo.license) |
151 | let tags = [] | 185 | let tags = [] |
152 | if (Array.isArray(videoInfo.tags)) { | 186 | if (Array.isArray(videoInfo.tags)) { |
@@ -158,7 +192,7 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, languag | |||
158 | 192 | ||
159 | let thumbnailfile | 193 | let thumbnailfile |
160 | if (videoInfo.thumbnail) { | 194 | if (videoInfo.thumbnail) { |
161 | thumbnailfile = join(__dirname, 'thumbnail.jpg') | 195 | thumbnailfile = join(cwd, 'thumbnail.jpg') |
162 | 196 | ||
163 | await doRequestAndSaveToFile({ | 197 | await doRequestAndSaveToFile({ |
164 | method: 'GET', | 198 | method: 'GET', |
@@ -189,15 +223,15 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, languag | |||
189 | 223 | ||
190 | console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) | 224 | console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) |
191 | try { | 225 | try { |
192 | await uploadVideo(program['url'], accessToken, videoAttributes) | 226 | await uploadVideo(url, accessToken, videoAttributes) |
193 | } catch (err) { | 227 | } catch (err) { |
194 | if (err.message.indexOf('401') !== -1) { | 228 | if (err.message.indexOf('401') !== -1) { |
195 | console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') | 229 | console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') |
196 | 230 | ||
197 | const res = await login(program['url'], client, user) | 231 | const res = await login(url, client, user) |
198 | accessToken = res.body.access_token | 232 | accessToken = res.body.access_token |
199 | 233 | ||
200 | await uploadVideo(program['url'], accessToken, videoAttributes) | 234 | await uploadVideo(url, accessToken, videoAttributes) |
201 | } else { | 235 | } else { |
202 | console.log(err.message) | 236 | console.log(err.message) |
203 | process.exit(1) | 237 | process.exit(1) |
@@ -210,14 +244,14 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, languag | |||
210 | console.log('Uploaded video "%s"!\n', videoAttributes.name) | 244 | console.log('Uploaded video "%s"!\n', videoAttributes.name) |
211 | } | 245 | } |
212 | 246 | ||
213 | async function getCategory (categories: string[]) { | 247 | async function getCategory (categories: string[], url: string) { |
214 | if (!categories) return undefined | 248 | if (!categories) return undefined |
215 | 249 | ||
216 | const categoryString = categories[0] | 250 | const categoryString = categories[0] |
217 | 251 | ||
218 | if (categoryString === 'News & Politics') return 11 | 252 | if (categoryString === 'News & Politics') return 11 |
219 | 253 | ||
220 | const res = await getVideoCategories(program['url']) | 254 | const res = await getVideoCategories(url) |
221 | const categoriesServer = res.body | 255 | const categoriesServer = res.body |
222 | 256 | ||
223 | for (const key of Object.keys(categoriesServer)) { | 257 | for (const key of Object.keys(categoriesServer)) { |
@@ -228,6 +262,8 @@ async function getCategory (categories: string[]) { | |||
228 | return undefined | 262 | return undefined |
229 | } | 263 | } |
230 | 264 | ||
265 | /* ---------------------------------------------------------- */ | ||
266 | |||
231 | function getLicence (licence: string) { | 267 | function getLicence (licence: string) { |
232 | if (!licence) return undefined | 268 | if (!licence) return undefined |
233 | 269 | ||
@@ -259,6 +295,7 @@ function fetchObject (info: any) { | |||
259 | const url = buildUrl(info) | 295 | const url = buildUrl(info) |
260 | 296 | ||
261 | return new Promise<any>(async (res, rej) => { | 297 | return new Promise<any>(async (res, rej) => { |
298 | const youtubeDL = await safeGetYoutubeDL() | ||
262 | youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => { | 299 | youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => { |
263 | if (err) return rej(err) | 300 | if (err) return rej(err) |
264 | 301 | ||
diff --git a/server/tools/upload.ts b/server/tools/peertube-upload.ts index 9b104d308..1f871e660 100644 --- a/server/tools/upload.ts +++ b/server/tools/peertube-upload.ts | |||
@@ -4,18 +4,20 @@ import { isAbsolute } from 'path' | |||
4 | import { getClient, login } from '../tests/utils' | 4 | import { getClient, login } from '../tests/utils' |
5 | import { uploadVideo } from '../tests/utils/index' | 5 | import { uploadVideo } from '../tests/utils/index' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../shared/models/videos' |
7 | import { netrc, getSettings } from './cli' | ||
7 | 8 | ||
8 | program | 9 | program |
10 | .name('upload') | ||
9 | .option('-u, --url <url>', 'Server url') | 11 | .option('-u, --url <url>', 'Server url') |
10 | .option('-U, --username <username>', 'Username') | 12 | .option('-U, --username <username>', 'Username') |
11 | .option('-p, --password <token>', 'Password') | 13 | .option('-p, --password <token>', 'Password') |
12 | .option('-n, --video-name <name>', 'Video name') | 14 | .option('-n, --video-name <name>', 'Video name') |
13 | .option('-P, --privacy <privacy number>', 'Privacy') | 15 | .option('-P, --privacy <privacy_number>', 'Privacy') |
14 | .option('-N, --nsfw', 'Video is Not Safe For Work') | 16 | .option('-N, --nsfw', 'Video is Not Safe For Work') |
15 | .option('-c, --category <category number>', 'Category number') | 17 | .option('-c, --category <category_number>', 'Category number') |
16 | .option('-m, --comments-enabled', 'Enable comments') | 18 | .option('-m, --comments-enabled', 'Enable comments') |
17 | .option('-l, --licence <licence number>', 'Licence number') | 19 | .option('-l, --licence <licence_number>', 'Licence number') |
18 | .option('-L, --language <language code>', 'Language ISO 639 code (fr or en...)') | 20 | .option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)') |
19 | .option('-d, --video-description <description>', 'Video description') | 21 | .option('-d, --video-description <description>', 'Video description') |
20 | .option('-t, --tags <tags>', 'Video tags', list) | 22 | .option('-t, --tags <tags>', 'Video tags', list) |
21 | .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path') | 23 | .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path') |
@@ -28,27 +30,53 @@ if (!program['nsfw']) program['nsfw'] = false | |||
28 | if (!program['privacy']) program['privacy'] = VideoPrivacy.PUBLIC | 30 | if (!program['privacy']) program['privacy'] = VideoPrivacy.PUBLIC |
29 | if (!program['commentsEnabled']) program['commentsEnabled'] = false | 31 | if (!program['commentsEnabled']) program['commentsEnabled'] = false |
30 | 32 | ||
31 | if ( | 33 | getSettings() |
32 | !program['url'] || | 34 | .then(settings => { |
33 | !program['username'] || | 35 | if ( |
34 | !program['password'] || | 36 | (!program['url'] || |
35 | !program['videoName'] || | 37 | !program['username'] || |
36 | !program['file'] | 38 | !program['password']) && |
37 | ) { | 39 | (settings.remotes.length === 0) |
38 | if (!program['url']) console.error('--url field is required.') | 40 | ) { |
39 | if (!program['username']) console.error('--username field is required.') | 41 | if (!program['url']) console.error('--url field is required.') |
40 | if (!program['password']) console.error('--password field is required.') | 42 | if (!program['username']) console.error('--username field is required.') |
41 | if (!program['videoName']) console.error('--video-name field is required.') | 43 | if (!program['password']) console.error('--password field is required.') |
42 | if (!program['file']) console.error('--file field is required.') | 44 | if (!program['videoName']) console.error('--video-name field is required.') |
43 | process.exit(-1) | 45 | if (!program['file']) console.error('--file field is required.') |
44 | } | 46 | process.exit(-1) |
47 | } | ||
45 | 48 | ||
46 | if (isAbsolute(program['file']) === false) { | 49 | if ( |
47 | console.error('File path should be absolute.') | 50 | (!program['url'] || |
48 | process.exit(-1) | 51 | !program['username'] || |
49 | } | 52 | !program['password']) && |
53 | (settings.remotes.length > 0) | ||
54 | ) { | ||
55 | if (!program['url']) { | ||
56 | program['url'] = (settings.default !== -1) ? | ||
57 | settings.remotes[settings.default] : | ||
58 | settings.remotes[0] | ||
59 | } | ||
60 | if (!program['username']) program['username'] = netrc.machines[program['url']].login | ||
61 | if (!program['password']) program['password'] = netrc.machines[program['url']].password | ||
62 | } | ||
63 | |||
64 | if ( | ||
65 | !program['videoName'] || | ||
66 | !program['file'] | ||
67 | ) { | ||
68 | if (!program['videoName']) console.error('--video-name field is required.') | ||
69 | if (!program['file']) console.error('--file field is required.') | ||
70 | process.exit(-1) | ||
71 | } | ||
72 | |||
73 | if (isAbsolute(program['file']) === false) { | ||
74 | console.error('File path should be absolute.') | ||
75 | process.exit(-1) | ||
76 | } | ||
50 | 77 | ||
51 | run().catch(err => console.error(err)) | 78 | run().catch(err => console.error(err)) |
79 | }) | ||
52 | 80 | ||
53 | async function run () { | 81 | async function run () { |
54 | const res = await getClient(program[ 'url' ]) | 82 | const res = await getClient(program[ 'url' ]) |
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts new file mode 100644 index 000000000..bf7274aab --- /dev/null +++ b/server/tools/peertube-watch.ts | |||
@@ -0,0 +1,61 @@ | |||
1 | import * as program from 'commander' | ||
2 | import * as summon from 'summon-install' | ||
3 | import { join } from 'path' | ||
4 | import { execSync } from 'child_process' | ||
5 | import { root } from '../helpers/core-utils' | ||
6 | |||
7 | let videoURL | ||
8 | |||
9 | program | ||
10 | .name('watch') | ||
11 | .arguments('<url>') | ||
12 | .option('-g, --gui <player>', 'player type', /^(airplay|stdout|chromecast|mpv|vlc|mplayer|ascii|xbmc)$/i, 'ascii') | ||
13 | .option('-i, --invert', 'invert colors (ascii player only)', true) | ||
14 | .option('-r, --resolution <res>', 'video resolution', /^(240|360|720|1080)$/i, '720') | ||
15 | .on('--help', function () { | ||
16 | console.log(' Available Players:') | ||
17 | console.log() | ||
18 | console.log(' - ascii') | ||
19 | console.log(' - mpv') | ||
20 | console.log(' - mplayer') | ||
21 | console.log(' - vlc') | ||
22 | console.log(' - stdout') | ||
23 | console.log(' - xbmc') | ||
24 | console.log(' - airplay') | ||
25 | console.log(' - chromecast') | ||
26 | console.log() | ||
27 | console.log(' Note: \'ascii\' is the only option not using WebTorrent and not seeding back the video.') | ||
28 | console.log() | ||
29 | console.log(' Examples:') | ||
30 | console.log() | ||
31 | console.log(' $ peertube watch -g mpv https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') | ||
32 | console.log(' $ peertube watch --gui stdout https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') | ||
33 | console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') | ||
34 | console.log() | ||
35 | }) | ||
36 | .action((url) => { | ||
37 | videoURL = url | ||
38 | }) | ||
39 | .parse(process.argv) | ||
40 | |||
41 | if (!videoURL) { | ||
42 | console.error('<url> positional argument is required.') | ||
43 | process.exit(-1) | ||
44 | } else { program['url'] = videoURL } | ||
45 | |||
46 | handler(program) | ||
47 | |||
48 | function handler (argv) { | ||
49 | if (argv['gui'] === 'ascii') { | ||
50 | summon('peerterminal') | ||
51 | const peerterminal = summon('peerterminal') | ||
52 | peerterminal([ '--link', videoURL, '--invert', argv['invert'] ]) | ||
53 | } else { | ||
54 | summon('webtorrent-hybrid') | ||
55 | const CMD = 'node ' + join(root(), 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js') | ||
56 | const CMDargs = ` --${argv.gui} ` + | ||
57 | argv['url'].replace('videos/watch', 'download/torrents') + | ||
58 | `-${argv.resolution}.torrent` | ||
59 | execSync(CMD + CMDargs) | ||
60 | } | ||
61 | } | ||
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts new file mode 100755 index 000000000..7441161b1 --- /dev/null +++ b/server/tools/peertube.ts | |||
@@ -0,0 +1,81 @@ | |||
1 | #!/usr/bin/env node | ||
2 | |||
3 | import * as program from 'commander' | ||
4 | import { | ||
5 | version, | ||
6 | getSettings | ||
7 | } from './cli' | ||
8 | |||
9 | program | ||
10 | .version(version(), '-v, --version') | ||
11 | .usage('[command] [options]') | ||
12 | |||
13 | /* Subcommands automatically loaded in the directory and beginning by peertube-* */ | ||
14 | program | ||
15 | .command('auth [action]', 'register your accounts on remote instances to use them with other commands') | ||
16 | .command('upload', 'upload a video').alias('up') | ||
17 | .command('import-videos', 'import a video from a streaming platform').alias('import') | ||
18 | .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token') | ||
19 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') | ||
20 | |||
21 | /* Not Yet Implemented */ | ||
22 | program | ||
23 | .command('plugins [action]', | ||
24 | 'manage plugins on a local instance', | ||
25 | { noHelp: true } as program.CommandOptions | ||
26 | ).alias('p') | ||
27 | .command('diagnostic [action]', | ||
28 | 'like couple therapy, but for your instance', | ||
29 | { noHelp: true } as program.CommandOptions | ||
30 | ).alias('d') | ||
31 | .command('admin', | ||
32 | 'manage an instance where you have elevated rights', | ||
33 | { noHelp: true } as program.CommandOptions | ||
34 | ).alias('a') | ||
35 | |||
36 | // help on no command | ||
37 | if (!process.argv.slice(2).length) { | ||
38 | const logo = '░P░e░e░r░T░u░b░e░' | ||
39 | console.log(` | ||
40 | ___/),.._ ` + logo + ` | ||
41 | /' ,. ."'._ | ||
42 | ( "' '-.__"-._ ,- | ||
43 | \\'='='), "\\ -._-"-. -"/ | ||
44 | / ""/"\\,_\\,__"" _" /,- | ||
45 | / / -" _/"/ | ||
46 | / | ._\\\\ |\\ |_.".-" / | ||
47 | / | __\\)|)|),/|_." _,." | ||
48 | / \_." " ") | ).-""---''-- | ||
49 | ( "/.""7__-""'' | ||
50 | | " ."._--._ | ||
51 | \\ \\ (_ __ "" ".,_ | ||
52 | \\.,. \\ "" -"".-" | ||
53 | ".,_, (",_-,,,-".- | ||
54 | "'-,\\_ __,-" | ||
55 | ",)" ") | ||
56 | /"\\-" | ||
57 | ,"\\/ | ||
58 | _,.__/"\\/_ (the CLI for red chocobos) | ||
59 | / \\) "./, ". | ||
60 | --/---"---" "-) )---- by Chocobozzz et al.`) | ||
61 | } | ||
62 | |||
63 | getSettings() | ||
64 | .then(settings => { | ||
65 | const state = (settings.default === -1) ? | ||
66 | 'no instance selected, commands will require explicit arguments' : | ||
67 | ('instance ' + settings.remotes[settings.default] + ' selected') | ||
68 | program | ||
69 | .on('--help', function () { | ||
70 | console.log() | ||
71 | console.log(' State: ' + state) | ||
72 | console.log() | ||
73 | console.log(' Examples:') | ||
74 | console.log() | ||
75 | console.log(' $ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"') | ||
76 | console.log(' $ peertube up <videoFile>') | ||
77 | console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') | ||
78 | console.log() | ||
79 | }) | ||
80 | .parse(process.argv) | ||
81 | }) | ||