Verified Commit 078f17e6 authored by Chocobozzz's avatar Chocobozzz
Browse files

Fix CLI tools

parent 12edc149
......@@ -12,6 +12,7 @@ import {
doubleFollow,
flushAndRunServer,
getLocalIdByUUID,
getMyUserInformation,
getVideo,
getVideosList,
ImportsCommand,
......@@ -52,6 +53,14 @@ describe('Test CLI wrapper', function () {
describe('Authentication and instance selection', function () {
it('Should get an access token', async function () {
const stdout = await cliCommand.execWithEnv(`${cmd} token --url ${server.url} --username user_1 --password super_password`)
const token = stdout.trim()
const res = await getMyUserInformation(server.url, token)
expect(res.body.username).to.equal('user_1')
})
it('Should display no selected instance', async function () {
this.timeout(60000)
......
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import 'mocha'
import { expect } from 'chai'
import {
cleanupTests,
createUser,
......@@ -18,8 +19,6 @@ import {
} from '@shared/extra-utils'
import { VideoDetails } from '@shared/models'
const expect = chai.expect
describe('Test update host scripts', function () {
let server: ServerInfo
......
import { Command } from 'commander'
import { Netrc } from 'netrc-parser'
import { getAppNumber, isTestInstance } from '../helpers/core-utils'
import { join } from 'path'
import { root } from '../../shared/extra-utils/miscs/miscs'
import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels'
import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
import { createLogger, format, transports } from 'winston'
import { assignCommands, ServerInfo } from '@shared/extra-utils'
import { getAccessToken } from '@shared/extra-utils/users/login'
import { getMyUserInformation } from '@shared/extra-utils/users/users'
import { User, UserRole } from '@shared/models'
import { getAccessToken } from '@shared/extra-utils/users/login'
import { Command } from 'commander'
import { root } from '../../shared/extra-utils/miscs/miscs'
import { VideoPrivacy } from '../../shared/models/videos'
import { getAppNumber, isTestInstance } from '../helpers/core-utils'
let configName = 'PeerTube/CLI'
if (isTestInstance()) configName += `-${getAppNumber()}`
......@@ -30,6 +30,10 @@ async function getAdminTokenOrDie (url: string, username: string, password: stri
return accessToken
}
async function getAccessTokenOrDie (url: string, username: string, password: string) {
return getAccessToken(url, username, password)
}
interface Settings {
remotes: any[]
default: number
......@@ -128,7 +132,7 @@ function buildCommonVideoOptions (command: Command) {
.option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info')
}
async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) {
async function buildVideoAttributesFromCommander (server: ServerInfo, command: Command, defaultAttributes: any = {}) {
const options = command.opts()
const defaultBooleanAttributes = {
......@@ -164,8 +168,7 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
Object.assign(videoAttributes, booleanAttributes)
if (options.channelName) {
const res = await getVideoChannel(url, options.channelName)
const videoChannel: VideoChannel = res.body
const videoChannel = await server.channelsCommand.get({ channelName: options.channelName })
Object.assign(videoAttributes, { channelId: videoChannel.id })
......@@ -184,6 +187,13 @@ function getServerCredentials (program: Command) {
})
}
function buildServer (url: string, accessToken?: string): ServerInfo {
const server = { url, accessToken, internalServerNumber: undefined }
assignCommands(server)
return server
}
function getLogger (logLevel = 'info') {
const logLevels = {
0: 0,
......@@ -230,5 +240,7 @@ export {
buildCommonVideoOptions,
buildVideoAttributesFromCommander,
getAdminTokenOrDie
getAdminTokenOrDie,
getAccessTokenOrDie,
buildServer
}
......@@ -2,7 +2,7 @@ import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
import { program } from 'commander'
import { getClient, Server, serverLogin } from '../../shared/extra-utils'
import { getAccessToken } from '../../shared/extra-utils'
program
.option('-u, --url <url>', 'Server url')
......@@ -24,22 +24,7 @@ if (
process.exit(-1)
}
getClient(options.url)
.then(res => {
const server = {
url: options.url,
user: {
username: options.username,
password: options.password
},
client: {
id: res.body.client_id,
secret: res.body.client_secret
}
} as Server
return serverLogin(server)
})
getAccessToken(options.url, options.username, options.password)
.then(accessToken => {
console.log(accessToken)
process.exit(0)
......
......@@ -8,17 +8,19 @@ import { truncate } from 'lodash'
import { join } from 'path'
import * as prompt from 'prompt'
import { promisify } from 'util'
import { advancedVideosSearch, getClient, getVideoCategories, login, uploadVideo } from '../../shared/extra-utils/index'
import { YoutubeDL } from '@server/helpers/youtube-dl'
import { getVideoCategories, uploadVideo } from '../../shared/extra-utils/index'
import { sha256 } from '../helpers/core-utils'
import { doRequestAndSaveToFile } from '../helpers/requests'
import { CONSTRAINTS_FIELDS } from '../initializers/constants'
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli'
import { YoutubeDL } from '@server/helpers/youtube-dl'
type UserInfo = {
username: string
password: string
}
import {
buildCommonVideoOptions,
buildServer,
buildVideoAttributesFromCommander,
getAccessTokenOrDie,
getLogger,
getServerCredentials
} from './cli'
const processOptions = {
maxBuffer: Infinity
......@@ -62,17 +64,13 @@ getServerCredentials(command)
url = normalizeTargetUrl(url)
options.targetUrl = normalizeTargetUrl(options.targetUrl)
const user = { username, password }
run(url, user)
run(url, username, password)
.catch(err => exitError(err))
})
.catch(err => console.error(err))
async function run (url: string, user: UserInfo) {
if (!user.password) {
user.password = await promptPassword()
}
async function run (url: string, username: string, password: string) {
if (!password) password = await promptPassword()
const youtubeDLBinary = await YoutubeDL.safeGetYoutubeDL()
......@@ -111,7 +109,8 @@ async function run (url: string, user: UserInfo) {
await processVideo({
cwd: options.tmpdir,
url,
user,
username,
password,
youtubeInfo: info
})
} catch (err) {
......@@ -119,17 +118,18 @@ async function run (url: string, user: UserInfo) {
}
}
log.info('Video/s for user %s imported: %s', user.username, options.targetUrl)
log.info('Video/s for user %s imported: %s', username, options.targetUrl)
process.exit(0)
}
async function processVideo (parameters: {
cwd: string
url: string
user: { username: string, password: string }
username: string
password: string
youtubeInfo: any
}) {
const { youtubeInfo, cwd, url, user } = parameters
const { youtubeInfo, cwd, url, username, password } = parameters
const youtubeDL = new YoutubeDL('', [])
log.debug('Fetching object.', youtubeInfo)
......@@ -138,22 +138,29 @@ async function processVideo (parameters: {
log.debug('Fetched object.', videoInfo)
const originallyPublishedAt = youtubeDL.buildOriginallyPublishedAt(videoInfo)
if (options.since && originallyPublishedAt && originallyPublishedAt.getTime() < options.since.getTime()) {
log.info('Video "%s" has been published before "%s", don\'t upload it.\n',
videoInfo.title, formatDate(options.since))
log.info('Video "%s" has been published before "%s", don\'t upload it.\n', videoInfo.title, formatDate(options.since))
return
}
if (options.until && originallyPublishedAt && originallyPublishedAt.getTime() > options.until.getTime()) {
log.info('Video "%s" has been published after "%s", don\'t upload it.\n',
videoInfo.title, formatDate(options.until))
log.info('Video "%s" has been published after "%s", don\'t upload it.\n', videoInfo.title, formatDate(options.until))
return
}
const result = await advancedVideosSearch(url, { search: videoInfo.title, sort: '-match', searchTarget: 'local' })
const server = buildServer(url)
const { data } = await server.searchCommand.advancedVideoSearch({
search: {
search: videoInfo.title,
sort: '-match',
searchTarget: 'local'
}
})
log.info('############################################################\n')
if (result.body.data.find(v => v.name === videoInfo.title)) {
if (data.find(v => v.name === videoInfo.title)) {
log.info('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title)
return
}
......@@ -172,7 +179,8 @@ async function processVideo (parameters: {
youtubeDL,
cwd,
url,
user,
username,
password,
videoInfo: normalizeObject(videoInfo),
videoPath: path
})
......@@ -187,9 +195,10 @@ async function uploadVideoOnPeerTube (parameters: {
videoPath: string
cwd: string
url: string
user: { username: string, password: string }
username: string
password: string
}) {
const { youtubeDL, videoInfo, videoPath, cwd, url, user } = parameters
const { youtubeDL, videoInfo, videoPath, cwd, url, username, password } = parameters
const category = await getCategory(videoInfo.categories, url)
const licence = getLicence(videoInfo.license)
......@@ -223,7 +232,10 @@ async function uploadVideoOnPeerTube (parameters: {
tags
}
const videoAttributes = await buildVideoAttributesFromCommander(url, program, defaultAttributes)
let accessToken = await getAccessTokenOrDie(url, username, password)
const server = buildServer(url, accessToken)
const videoAttributes = await buildVideoAttributesFromCommander(server, program, defaultAttributes)
Object.assign(videoAttributes, {
originallyPublishedAt: originallyPublishedAt ? originallyPublishedAt.toISOString() : null,
......@@ -234,15 +246,13 @@ async function uploadVideoOnPeerTube (parameters: {
log.info('\nUploading on PeerTube video "%s".', videoAttributes.name)
let accessToken = await getAccessTokenOrDie(url, user)
try {
await uploadVideo(url, accessToken, videoAttributes)
} catch (err) {
if (err.message.indexOf('401') !== -1) {
log.info('Got 401 Unauthorized, token may have expired, renewing token and retry.')
accessToken = await getAccessTokenOrDie(url, user)
accessToken = await getAccessTokenOrDie(url, username, password)
await uploadVideo(url, accessToken, videoAttributes)
} else {
......@@ -362,21 +372,6 @@ async function promptPassword () {
})
}
async function getAccessTokenOrDie (url: string, user: UserInfo) {
const resClient = await getClient(url)
const client = {
id: resClient.body.client_id,
secret: resClient.body.client_secret
}
try {
const res = await login(url, client, user)
return res.body.access_token
} catch (err) {
exitError('Cannot authenticate. Please check your username/password.')
}
}
function parseDate (dateAsStr: string): Date {
if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) {
exitError(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`)
......
......@@ -4,9 +4,8 @@ import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
import { program, Command, OptionValues } from 'commander'
import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins'
import { getAdminTokenOrDie, getServerCredentials } from './cli'
import { PeerTubePlugin, PluginType } from '../../shared/models'
import { buildServer, getAdminTokenOrDie, getServerCredentials } from './cli'
import { PluginType } from '../../shared/models'
import { isAbsolute } from 'path'
import * as CliTable3 from 'cli-table3'
......@@ -63,28 +62,21 @@ program.parse(process.argv)
async function pluginsListCLI (command: Command, options: OptionValues) {
const { url, username, password } = await getServerCredentials(command)
const accessToken = await getAdminTokenOrDie(url, username, password)
const token = await getAdminTokenOrDie(url, username, password)
const server = buildServer(url, token)
let pluginType: PluginType
if (options.onlyThemes) pluginType = PluginType.THEME
if (options.onlyPlugins) pluginType = PluginType.PLUGIN
const res = await listPlugins({
url,
accessToken,
start: 0,
count: 100,
sort: 'name',
pluginType
})
const plugins: PeerTubePlugin[] = res.body.data
const { data } = await server.pluginsCommand.list({ start: 0, count: 100, sort: 'name', pluginType })
const table = new CliTable3({
head: [ 'name', 'version', 'homepage' ],
colWidths: [ 50, 10, 50 ]
}) as any
for (const plugin of plugins) {
for (const plugin of data) {
const npmName = plugin.type === PluginType.PLUGIN
? 'peertube-plugin-' + plugin.name
: 'peertube-theme-' + plugin.name
......@@ -113,15 +105,11 @@ async function installPluginCLI (command: Command, options: OptionValues) {
}
const { url, username, password } = await getServerCredentials(command)
const accessToken = await getAdminTokenOrDie(url, username, password)
const token = await getAdminTokenOrDie(url, username, password)
const server = buildServer(url, token)
try {
await installPlugin({
url,
accessToken,
npmName: options.npmName,
path: options.path
})
await server.pluginsCommand.install({ npmName: options.npmName, path: options.path })
} catch (err) {
console.error('Cannot install plugin.', err)
process.exit(-1)
......@@ -144,15 +132,11 @@ async function updatePluginCLI (command: Command, options: OptionValues) {
}
const { url, username, password } = await getServerCredentials(command)
const accessToken = await getAdminTokenOrDie(url, username, password)
const token = await getAdminTokenOrDie(url, username, password)
const server = buildServer(url, token)
try {
await updatePlugin({
url,
accessToken,
npmName: options.npmName,
path: options.path
})
await server.pluginsCommand.update({ npmName: options.npmName, path: options.path })
} catch (err) {
console.error('Cannot update plugin.', err)
process.exit(-1)
......@@ -170,14 +154,11 @@ async function uninstallPluginCLI (command: Command, options: OptionValues) {
}
const { url, username, password } = await getServerCredentials(command)
const accessToken = await getAdminTokenOrDie(url, username, password)
const token = await getAdminTokenOrDie(url, username, password)
const server = buildServer(url, token)
try {
await uninstallPlugin({
url,
accessToken,
npmName: options.npmName
})
await server.pluginsCommand.uninstall({ npmName: options.npmName })
} catch (err) {
console.error('Cannot uninstall plugin.', err)
process.exit(-1)
......
// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
import { program, Command } from 'commander'
import { getAdminTokenOrDie, getServerCredentials } from './cli'
import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import validator from 'validator'
import * as CliTable3 from 'cli-table3'
import { URL } from 'url'
import { Command, program } from 'commander'
import { uniq } from 'lodash'
import { URL } from 'url'
import validator from 'validator'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import { VideoRedundanciesTarget } from '@shared/models'
import { buildServer, getAdminTokenOrDie, getServerCredentials } from './cli'
import bytes = require('bytes')
......@@ -63,15 +60,16 @@ program.parse(process.argv)
async function listRedundanciesCLI (target: VideoRedundanciesTarget) {
const { url, username, password } = await getServerCredentials(program)
const accessToken = await getAdminTokenOrDie(url, username, password)
const token = await getAdminTokenOrDie(url, username, password)
const server = buildServer(url, token)
const redundancies = await listVideoRedundanciesData(url, accessToken, target)
const { data } = await server.redundancyCommand.listVideos({ start: 0, count: 100, sort: 'name', target })
const table = new CliTable3({
head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ]
}) as any
for (const redundancy of redundancies) {
for (const redundancy of data) {
const webtorrentFiles = redundancy.redundancies.files
const streamingPlaylists = redundancy.redundancies.streamingPlaylists
......@@ -106,7 +104,8 @@ async function listRedundanciesCLI (target: VideoRedundanciesTarget) {
async function addRedundancyCLI (options: { video: number }, command: Command) {
const { url, username, password } = await getServerCredentials(command)
const accessToken = await getAdminTokenOrDie(url, username, password)
const token = await getAdminTokenOrDie(url, username, password)
const server = buildServer(url, token)
if (!options.video || validator.isInt('' + options.video) === false) {
console.error('You need to specify the video id to duplicate and it should be a number.\n')
......@@ -115,11 +114,7 @@ async function addRedundancyCLI (options: { video: number }, command: Command) {
}
try {
await addVideoRedundancy({
url,
accessToken,
videoId: options.video
})
await server.redundancyCommand.addVideo({ videoId: options.video })
console.log('Video will be duplicated by your instance!')
......@@ -139,7 +134,8 @@ async function addRedundancyCLI (options: { video: number }, command: Command) {
async function removeRedundancyCLI (options: { video: number }, command: Command) {
const { url, username, password } = await getServerCredentials(command)
const accessToken = await getAdminTokenOrDie(url, username, password)
const token = await getAdminTokenOrDie(url, username, password)
const server = buildServer(url, token)
if (!options.video || validator.isInt('' + options.video) === false) {
console.error('You need to specify the video id to remove from your redundancies.\n')
......@@ -149,12 +145,12 @@ async function removeRedundancyCLI (options: { video: number }, command: Command
const videoId = parseInt(options.video + '', 10)
let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos')
let videoRedundancy = redundancies.find(r => videoId === r.id)
const myVideoRedundancies = await server.redundancyCommand.listVideos({ target: 'my-videos' })
let videoRedundancy = myVideoRedundancies.data.find(r => videoId === r.id)
if (!videoRedundancy) {
redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos')
videoRedundancy = redundancies.find(r => videoId === r.id)
const remoteVideoRedundancies = await server.redundancyCommand.listVideos({ target: 'remote-videos' })
videoRedundancy = remoteVideoRedundancies.data.find(r => videoId === r.id)
}
if (!videoRedundancy) {
......@@ -168,11 +164,7 @@ async function removeRedundancyCLI (options: { video: number }, command: Command
.map(r => r.id)
for (const id of ids) {
await removeVideoRedundancy({
url,
accessToken,
redundancyId: id
})
await server.redundancyCommand.removeVideo({ redundancyId: id })
}
console.log('Video redundancy removed!')
......@@ -183,16 +175,3 @@ async function removeRedundancyCLI (options: { video: number }, command: Command
process.exit(-1)
}
}
async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) {
const res = await listVideoRedundancies({
url,
accessToken,
start: 0,
count: 100,
sort: 'name',
target
})
return res.body.data as VideoRedundancy[]
}
......@@ -6,7 +6,7 @@ import { access, constants } from 'fs-extra'
import { isAbsolute } from 'path'
import { getAccessToken } from '../../shared/extra-utils'
import { uploadVideo } from '../../shared/extra-utils/'
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli'
import { buildCommonVideoOptions, buildServer, buildVideoAttributesFromCommander, getServerCredentials } from './cli'
let command = program
.name('upload')
......@@ -46,13 +46,14 @@ getServerCredentials(command)
.catch(err => console.error(err))
async function run (url: string, username: string, password: string) {
const accessToken = await getAccessToken(url, username, password)
const token = await getAccessToken(url, username, password)
const server = buildServer(url, token)
await access(options.file, constants.F_OK)
console.log('Uploading %s video...', options.videoName)
const videoAttributes = await buildVideoAttributesFromCommander(url, program)
const videoAttributes = await buildVideoAttributesFromCommander(server, program)
Object.assign(videoAttributes, {
fixture: options.file,
......@@ -61,7 +62,7 @@ async function run (url: string, username: string, password: string) {
})
try {
await uploadVideo(url, accessToken, videoAttributes)
await uploadVideo(url, token, videoAttributes)
console.log(`Video ${options.videoName} uploaded.`)
process.exit(0)
} catch (err) {
......
import { registerTSPaths } from '../helpers/register-ts-paths'
registerTSPaths()
import { LiveVideo, LiveVideoCreate, VideoPrivacy } from '@shared/models'
import { program } from 'commander'
import { LiveVideoCreate, VideoPrivacy } from '@shared/models'
import {