aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/helpers/captions-utils.ts2
-rw-r--r--server/helpers/core-utils.ts10
-rw-r--r--server/helpers/custom-jsonld-signature.ts1
-rw-r--r--server/helpers/logger.ts2
-rw-r--r--server/helpers/webtorrent.ts2
-rw-r--r--server/helpers/youtube-dl.ts2
-rw-r--r--server/lib/activitypub/inbox-manager.ts1
-rw-r--r--server/lib/avatar.ts2
-rw-r--r--server/lib/hls.ts2
-rw-r--r--server/lib/redis.ts2
-rw-r--r--server/middlewares/async.ts5
-rw-r--r--server/middlewares/oauth.ts2
-rw-r--r--server/middlewares/validators/oembed.ts2
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts23
-rw-r--r--server/middlewares/validators/videos/videos.ts5
-rw-r--r--server/models/account/user-notification.ts94
-rw-r--r--server/models/video/video-format-utils.ts6
-rw-r--r--server/tools/cli.ts52
-rw-r--r--server/tools/peertube-auth.ts20
-rw-r--r--server/tools/peertube-get-access-token.ts22
-rw-r--r--server/tools/peertube-import-videos.ts44
-rw-r--r--server/tools/peertube-plugins.ts45
-rw-r--r--server/tools/peertube-redundancy.ts25
-rw-r--r--server/tools/peertube-repl.ts1
-rw-r--r--server/tools/peertube-upload.ts22
-rw-r--r--server/tools/peertube-watch.ts38
-rw-r--r--server/tools/peertube.ts17
-rw-r--r--server/types/express.ts3
28 files changed, 233 insertions, 219 deletions
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts
index 0dad23759..7cbfb3561 100644
--- a/server/helpers/captions-utils.ts
+++ b/server/helpers/captions-utils.ts
@@ -30,7 +30,7 @@ export {
30// --------------------------------------------------------------------------- 30// ---------------------------------------------------------------------------
31 31
32function convertSrtToVtt (source: string, destination: string) { 32function convertSrtToVtt (source: string, destination: string) {
33 return new Promise((res, rej) => { 33 return new Promise<void>((res, rej) => {
34 const file = createReadStream(source) 34 const file = createReadStream(source)
35 const converter = srt2vtt() 35 const converter = srt2vtt()
36 const writer = createWriteStream(destination) 36 const writer = createWriteStream(destination)
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index e1c15a6eb..935fd22d9 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -5,12 +5,12 @@
5 Useful to avoid circular dependencies. 5 Useful to avoid circular dependencies.
6*/ 6*/
7 7
8import { createHash, HexBase64Latin1Encoding, randomBytes } from 'crypto' 8import { exec, ExecOptions } from 'child_process'
9import { BinaryToTextEncoding, createHash, randomBytes } from 'crypto'
10import { truncate } from 'lodash'
9import { basename, isAbsolute, join, resolve } from 'path' 11import { basename, isAbsolute, join, resolve } from 'path'
10import * as pem from 'pem' 12import * as pem from 'pem'
11import { URL } from 'url' 13import { URL } from 'url'
12import { truncate } from 'lodash'
13import { exec, ExecOptions } from 'child_process'
14 14
15const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => { 15const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
16 if (!oldObject || typeof oldObject !== 'object') { 16 if (!oldObject || typeof oldObject !== 'object') {
@@ -205,11 +205,11 @@ function peertubeTruncate (str: string, options: { length: number, separator?: R
205 return truncate(str, options) 205 return truncate(str, options)
206} 206}
207 207
208function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { 208function sha256 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
209 return createHash('sha256').update(str).digest(encoding) 209 return createHash('sha256').update(str).digest(encoding)
210} 210}
211 211
212function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { 212function sha1 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
213 return createHash('sha1').update(str).digest(encoding) 213 return createHash('sha1').update(str).digest(encoding)
214} 214}
215 215
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts
index 749c50cb3..56f10086c 100644
--- a/server/helpers/custom-jsonld-signature.ts
+++ b/server/helpers/custom-jsonld-signature.ts
@@ -76,6 +76,7 @@ const lru = new AsyncLRU({
76 } 76 }
77}) 77})
78 78
79/* eslint-disable no-import-assign */
79jsonld.documentLoader = (url) => { 80jsonld.documentLoader = (url) => {
80 return new Promise((res, rej) => { 81 return new Promise((res, rej) => {
81 lru.get(url, (err, value) => { 82 lru.get(url, (err, value) => {
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts
index 746b2e0a6..6917a64d9 100644
--- a/server/helpers/logger.ts
+++ b/server/helpers/logger.ts
@@ -61,7 +61,7 @@ const consoleLoggerFormat = winston.format.printf(info => {
61 if (CONFIG.LOG.PRETTIFY_SQL) { 61 if (CONFIG.LOG.PRETTIFY_SQL) {
62 additionalInfos += '\n' + sqlFormat(info.sql, { 62 additionalInfos += '\n' + sqlFormat(info.sql, {
63 language: 'sql', 63 language: 'sql',
64 ident: ' ' 64 indent: ' '
65 }) 65 })
66 } else { 66 } else {
67 additionalInfos += ' - ' + info.sql 67 additionalInfos += ' - ' + info.sql
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts
index 089954e32..9c5df2083 100644
--- a/server/helpers/webtorrent.ts
+++ b/server/helpers/webtorrent.ts
@@ -149,7 +149,7 @@ function safeWebtorrentDestroy (
149 downloadedFile?: { directoryPath: string, filepath: string }, 149 downloadedFile?: { directoryPath: string, filepath: string },
150 torrentName?: string 150 torrentName?: string
151) { 151) {
152 return new Promise(res => { 152 return new Promise<void>(res => {
153 webtorrent.destroy(err => { 153 webtorrent.destroy(err => {
154 // Delete torrent file 154 // Delete torrent file
155 if (torrentName) { 155 if (torrentName) {
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts
index 3a9e57561..8537a5772 100644
--- a/server/helpers/youtube-dl.ts
+++ b/server/helpers/youtube-dl.ts
@@ -195,7 +195,7 @@ async function updateYoutubeDLBinary () {
195 195
196 await ensureDir(binDirectory) 196 await ensureDir(binDirectory)
197 197
198 return new Promise(res => { 198 return new Promise<void>(res => {
199 request.get(url, { followRedirect: false }, (err, result) => { 199 request.get(url, { followRedirect: false }, (err, result) => {
200 if (err) { 200 if (err) {
201 logger.error('Cannot update youtube-dl.', { err }) 201 logger.error('Cannot update youtube-dl.', { err })
diff --git a/server/lib/activitypub/inbox-manager.ts b/server/lib/activitypub/inbox-manager.ts
index 19e112f91..6d9bf7cf0 100644
--- a/server/lib/activitypub/inbox-manager.ts
+++ b/server/lib/activitypub/inbox-manager.ts
@@ -41,6 +41,7 @@ class InboxManager {
41 41
42 addInboxMessage (options: QueueParam) { 42 addInboxMessage (options: QueueParam) {
43 this.inboxQueue.push(options) 43 this.inboxQueue.push(options)
44 .catch(err => logger.error('Cannot add options in inbox queue.', { options, err }))
44 } 45 }
45 46
46 static get Instance () { 47 static get Instance () {
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts
index e79cd1546..86f1e7bdb 100644
--- a/server/lib/avatar.ts
+++ b/server/lib/avatar.ts
@@ -65,7 +65,7 @@ const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => {
65}, QUEUE_CONCURRENCY.AVATAR_PROCESS_IMAGE) 65}, QUEUE_CONCURRENCY.AVATAR_PROCESS_IMAGE)
66 66
67function pushAvatarProcessInQueue (task: DownloadImageQueueTask) { 67function pushAvatarProcessInQueue (task: DownloadImageQueueTask) {
68 return new Promise((res, rej) => { 68 return new Promise<void>((res, rej) => {
69 downloadImageQueue.push(task, err => { 69 downloadImageQueue.push(task, err => {
70 if (err) return rej(err) 70 if (err) return rej(err)
71 71
diff --git a/server/lib/hls.ts b/server/lib/hls.ts
index 9ea83f337..ef489097a 100644
--- a/server/lib/hls.ts
+++ b/server/lib/hls.ts
@@ -111,7 +111,7 @@ function downloadPlaylistSegments (playlistUrl: string, destinationDir: string,
111 111
112 logger.info('Importing HLS playlist %s', playlistUrl) 112 logger.info('Importing HLS playlist %s', playlistUrl)
113 113
114 return new Promise<string>(async (res, rej) => { 114 return new Promise<void>(async (res, rej) => {
115 const tmpDirectory = join(CONFIG.STORAGE.TMP_DIR, await generateRandomString(10)) 115 const tmpDirectory = join(CONFIG.STORAGE.TMP_DIR, await generateRandomString(10))
116 116
117 await ensureDir(tmpDirectory) 117 await ensureDir(tmpDirectory)
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index 257cce141..62641e313 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -263,7 +263,7 @@ class Redis {
263 } 263 }
264 264
265 private addToSet (key: string, value: string) { 265 private addToSet (key: string, value: string) {
266 return new Promise<string[]>((res, rej) => { 266 return new Promise<void>((res, rej) => {
267 this.client.sadd(this.prefix + key, value, err => err ? rej(err) : res()) 267 this.client.sadd(this.prefix + key, value, err => err ? rej(err) : res())
268 }) 268 })
269 } 269 }
diff --git a/server/middlewares/async.ts b/server/middlewares/async.ts
index 25b22596c..3a1bdabb8 100644
--- a/server/middlewares/async.ts
+++ b/server/middlewares/async.ts
@@ -1,12 +1,13 @@
1import { eachSeries } from 'async' 1import { eachSeries } from 'async'
2import { NextFunction, Request, RequestHandler, Response } from 'express' 2import { NextFunction, Request, RequestHandler, Response } from 'express'
3import { retryTransactionWrapper } from '../helpers/database-utils'
4import { ValidationChain } from 'express-validator' 3import { ValidationChain } from 'express-validator'
4import { ExpressPromiseHandler } from '@server/types/express'
5import { retryTransactionWrapper } from '../helpers/database-utils'
5 6
6// Syntactic sugar to avoid try/catch in express controllers 7// Syntactic sugar to avoid try/catch in express controllers
7// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016 8// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
8 9
9export type RequestPromiseHandler = ValidationChain | ((req: Request, res: Response, next: NextFunction) => Promise<any>) 10export type RequestPromiseHandler = ValidationChain | ExpressPromiseHandler
10 11
11function asyncMiddleware (fun: RequestPromiseHandler | RequestPromiseHandler[]) { 12function asyncMiddleware (fun: RequestPromiseHandler | RequestPromiseHandler[]) {
12 return (req: Request, res: Response, next: NextFunction) => { 13 return (req: Request, res: Response, next: NextFunction) => {
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts
index ffc1b7ca9..47799153a 100644
--- a/server/middlewares/oauth.ts
+++ b/server/middlewares/oauth.ts
@@ -49,7 +49,7 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
49} 49}
50 50
51function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { 51function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) {
52 return new Promise(resolve => { 52 return new Promise<void>(resolve => {
53 // Already authenticated? (or tried to) 53 // Already authenticated? (or tried to)
54 if (res.locals.oauth?.token.User) return resolve() 54 if (res.locals.oauth?.token.User) return resolve()
55 55
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts
index 86623efcd..2a7dc257b 100644
--- a/server/middlewares/validators/oembed.ts
+++ b/server/middlewares/validators/oembed.ts
@@ -14,7 +14,7 @@ import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-code
14const startVideoPlaylistsURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch', 'playlist') + '/' 14const startVideoPlaylistsURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch', 'playlist') + '/'
15const startVideosURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/' 15const startVideosURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/'
16 16
17const watchRegex = new RegExp('([^/]+)$') 17const watchRegex = /([^/]+)$/
18const isURLOptions = { 18const isURLOptions = {
19 require_host: true, 19 require_host: true,
20 require_tld: true 20 require_tld: true
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index c7a6f68e3..0fba4f5fd 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -1,10 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param, query, ValidationChain } from 'express-validator' 2import { body, param, query, ValidationChain } from 'express-validator'
3import { ExpressPromiseHandler } from '@server/types/express'
4import { MUserAccountId } from '@server/types/models'
3import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' 5import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared'
4import { logger } from '../../../helpers/logger' 6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
5import { areValidationErrors } from '../utils' 7import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
6import { isVideoImage } from '../../../helpers/custom-validators/videos' 8import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
7import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
8import { 9import {
9 isArrayOf, 10 isArrayOf,
10 isIdOrUUIDValid, 11 isIdOrUUIDValid,
@@ -21,15 +22,15 @@ import {
21 isVideoPlaylistTimestampValid, 22 isVideoPlaylistTimestampValid,
22 isVideoPlaylistTypeValid 23 isVideoPlaylistTypeValid
23} from '../../../helpers/custom-validators/video-playlists' 24} from '../../../helpers/custom-validators/video-playlists'
25import { isVideoImage } from '../../../helpers/custom-validators/videos'
24import { cleanUpReqFiles } from '../../../helpers/express-utils' 26import { cleanUpReqFiles } from '../../../helpers/express-utils'
25import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' 27import { logger } from '../../../helpers/logger'
26import { authenticatePromiseIfNeeded } from '../../oauth'
27import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
28import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
29import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../../../helpers/middlewares' 28import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../../../helpers/middlewares'
29import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
30import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
30import { MVideoPlaylist } from '../../../types/models/video/video-playlist' 31import { MVideoPlaylist } from '../../../types/models/video/video-playlist'
31import { MUserAccountId } from '@server/types/models' 32import { authenticatePromiseIfNeeded } from '../../oauth'
32import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 33import { areValidationErrors } from '../utils'
33 34
34const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ 35const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
35 body('displayName') 36 body('displayName')
@@ -395,7 +396,7 @@ function getCommonPlaylistEditAttributes () {
395 body('videoChannelId') 396 body('videoChannelId')
396 .optional() 397 .optional()
397 .customSanitizer(toIntOrNull) 398 .customSanitizer(toIntOrNull)
398 ] as (ValidationChain | express.Handler)[] 399 ] as (ValidationChain | ExpressPromiseHandler)[]
399} 400}
400 401
401function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) { 402function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) {
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 84e309bec..26a671a1e 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -2,8 +2,10 @@ import * as express from 'express'
2import { body, param, query, ValidationChain } from 'express-validator' 2import { body, param, query, ValidationChain } from 'express-validator'
3import { isAbleToUploadVideo } from '@server/lib/user' 3import { isAbleToUploadVideo } from '@server/lib/user'
4import { getServerActor } from '@server/models/application/application' 4import { getServerActor } from '@server/models/application/application'
5import { ExpressPromiseHandler } from '@server/types/express'
5import { MVideoFullLight } from '@server/types/models' 6import { MVideoFullLight } from '@server/types/models'
6import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' 7import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
8import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
7import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' 9import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
8import { 10import {
9 exists, 11 exists,
@@ -54,7 +56,6 @@ import { AccountModel } from '../../../models/account/account'
54import { VideoModel } from '../../../models/video/video' 56import { VideoModel } from '../../../models/video/video'
55import { authenticatePromiseIfNeeded } from '../../oauth' 57import { authenticatePromiseIfNeeded } from '../../oauth'
56import { areValidationErrors } from '../utils' 58import { areValidationErrors } from '../utils'
57import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
58 59
59const videosAddValidator = getCommonVideoEditAttributes().concat([ 60const videosAddValidator = getCommonVideoEditAttributes().concat([
60 body('videofile') 61 body('videofile')
@@ -411,7 +412,7 @@ function getCommonVideoEditAttributes () {
411 .optional() 412 .optional()
412 .customSanitizer(toIntOrNull) 413 .customSanitizer(toIntOrNull)
413 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy') 414 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
414 ] as (ValidationChain | express.Handler)[] 415 ] as (ValidationChain | ExpressPromiseHandler)[]
415} 416}
416 417
417const commonVideosFiltersValidator = [ 418const commonVideosFiltersValidator = [
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index 52b792a5b..add129644 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -469,27 +469,33 @@ export class UserNotificationModel extends Model {
469 ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) }) 469 ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) })
470 : undefined 470 : undefined
471 471
472 const videoImport = this.VideoImport ? { 472 const videoImport = this.VideoImport
473 id: this.VideoImport.id, 473 ? {
474 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined, 474 id: this.VideoImport.id,
475 torrentName: this.VideoImport.torrentName, 475 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
476 magnetUri: this.VideoImport.magnetUri, 476 torrentName: this.VideoImport.torrentName,
477 targetUrl: this.VideoImport.targetUrl 477 magnetUri: this.VideoImport.magnetUri,
478 } : undefined 478 targetUrl: this.VideoImport.targetUrl
479 479 }
480 const comment = this.Comment ? { 480 : undefined
481 id: this.Comment.id, 481
482 threadId: this.Comment.getThreadId(), 482 const comment = this.Comment
483 account: this.formatActor(this.Comment.Account), 483 ? {
484 video: this.formatVideo(this.Comment.Video) 484 id: this.Comment.id,
485 } : undefined 485 threadId: this.Comment.getThreadId(),
486 account: this.formatActor(this.Comment.Account),
487 video: this.formatVideo(this.Comment.Video)
488 }
489 : undefined
486 490
487 const abuse = this.Abuse ? this.formatAbuse(this.Abuse) : undefined 491 const abuse = this.Abuse ? this.formatAbuse(this.Abuse) : undefined
488 492
489 const videoBlacklist = this.VideoBlacklist ? { 493 const videoBlacklist = this.VideoBlacklist
490 id: this.VideoBlacklist.id, 494 ? {
491 video: this.formatVideo(this.VideoBlacklist.Video) 495 id: this.VideoBlacklist.id,
492 } : undefined 496 video: this.formatVideo(this.VideoBlacklist.Video)
497 }
498 : undefined
493 499
494 const account = this.Account ? this.formatActor(this.Account) : undefined 500 const account = this.Account ? this.formatActor(this.Account) : undefined
495 501
@@ -498,23 +504,25 @@ export class UserNotificationModel extends Model {
498 Group: 'channel' as 'channel', 504 Group: 'channel' as 'channel',
499 Person: 'account' as 'account' 505 Person: 'account' as 'account'
500 } 506 }
501 const actorFollow = this.ActorFollow ? { 507 const actorFollow = this.ActorFollow
502 id: this.ActorFollow.id, 508 ? {
503 state: this.ActorFollow.state, 509 id: this.ActorFollow.id,
504 follower: { 510 state: this.ActorFollow.state,
505 id: this.ActorFollow.ActorFollower.Account.id, 511 follower: {
506 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), 512 id: this.ActorFollow.ActorFollower.Account.id,
507 name: this.ActorFollow.ActorFollower.preferredUsername, 513 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
508 avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined, 514 name: this.ActorFollow.ActorFollower.preferredUsername,
509 host: this.ActorFollow.ActorFollower.getHost() 515 avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined,
510 }, 516 host: this.ActorFollow.ActorFollower.getHost()
511 following: { 517 },
512 type: actorFollowingType[this.ActorFollow.ActorFollowing.type], 518 following: {
513 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), 519 type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
514 name: this.ActorFollow.ActorFollowing.preferredUsername, 520 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
515 host: this.ActorFollow.ActorFollowing.getHost() 521 name: this.ActorFollow.ActorFollowing.preferredUsername,
522 host: this.ActorFollow.ActorFollowing.getHost()
523 }
516 } 524 }
517 } : undefined 525 : undefined
518 526
519 return { 527 return {
520 id: this.id, 528 id: this.id,
@@ -541,15 +549,17 @@ export class UserNotificationModel extends Model {
541 } 549 }
542 550
543 formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) { 551 formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) {
544 const commentAbuse = abuse.VideoCommentAbuse?.VideoComment ? { 552 const commentAbuse = abuse.VideoCommentAbuse?.VideoComment
545 threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), 553 ? {
546 554 threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
547 video: { 555
548 id: abuse.VideoCommentAbuse.VideoComment.Video.id, 556 video: {
549 name: abuse.VideoCommentAbuse.VideoComment.Video.name, 557 id: abuse.VideoCommentAbuse.VideoComment.Video.id,
550 uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid 558 name: abuse.VideoCommentAbuse.VideoComment.Video.name,
559 uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
560 }
551 } 561 }
552 } : undefined 562 : undefined
553 563
554 const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined 564 const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined
555 565
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index a1f022fb4..77b8bcfe3 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -82,9 +82,9 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor
82 account: video.VideoChannel.Account.toFormattedSummaryJSON(), 82 account: video.VideoChannel.Account.toFormattedSummaryJSON(),
83 channel: video.VideoChannel.toFormattedSummaryJSON(), 83 channel: video.VideoChannel.toFormattedSummaryJSON(),
84 84
85 userHistory: userHistory ? { 85 userHistory: userHistory
86 currentTime: userHistory.currentTime 86 ? { currentTime: userHistory.currentTime }
87 } : undefined, 87 : undefined,
88 88
89 // Can be added by external plugins 89 // Can be added by external plugins
90 pluginData: (video as any).pluginData 90 pluginData: (video as any).pluginData
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
index d5416fc38..cc89fe46e 100644
--- a/server/tools/cli.ts
+++ b/server/tools/cli.ts
@@ -69,23 +69,25 @@ function deleteSettings () {
69} 69}
70 70
71function getRemoteObjectOrDie ( 71function getRemoteObjectOrDie (
72 program: any, 72 program: CommanderStatic,
73 settings: Settings, 73 settings: Settings,
74 netrc: Netrc 74 netrc: Netrc
75): { url: string, username: string, password: string } { 75): { url: string, username: string, password: string } {
76 if (!program['url'] || !program['username'] || !program['password']) { 76 const options = program.opts()
77
78 if (!options.url || !options.username || !options.password) {
77 // No remote and we don't have program parameters: quit 79 // No remote and we don't have program parameters: quit
78 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { 80 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) {
79 if (!program['url']) console.error('--url field is required.') 81 if (!options.url) console.error('--url field is required.')
80 if (!program['username']) console.error('--username field is required.') 82 if (!options.username) console.error('--username field is required.')
81 if (!program['password']) console.error('--password field is required.') 83 if (!options.password) console.error('--password field is required.')
82 84
83 return process.exit(-1) 85 return process.exit(-1)
84 } 86 }
85 87
86 let url: string = program['url'] 88 let url: string = options.url
87 let username: string = program['username'] 89 let username: string = options.username
88 let password: string = program['password'] 90 let password: string = options.password
89 91
90 if (!url && settings.default !== -1) url = settings.remotes[settings.default] 92 if (!url && settings.default !== -1) url = settings.remotes[settings.default]
91 93
@@ -98,9 +100,9 @@ function getRemoteObjectOrDie (
98 } 100 }
99 101
100 return { 102 return {
101 url: program['url'], 103 url: options.url,
102 username: program['username'], 104 username: options.username,
103 password: program['password'] 105 password: options.password
104 } 106 }
105} 107}
106 108
@@ -127,6 +129,8 @@ function buildCommonVideoOptions (command: CommanderStatic) {
127} 129}
128 130
129async function buildVideoAttributesFromCommander (url: string, command: CommanderStatic, defaultAttributes: any = {}) { 131async function buildVideoAttributesFromCommander (url: string, command: CommanderStatic, defaultAttributes: any = {}) {
132 const options = command.opts()
133
130 const defaultBooleanAttributes = { 134 const defaultBooleanAttributes = {
131 nsfw: false, 135 nsfw: false,
132 commentsEnabled: true, 136 commentsEnabled: true,
@@ -137,8 +141,8 @@ async function buildVideoAttributesFromCommander (url: string, command: Commande
137 const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {} 141 const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
138 142
139 for (const key of Object.keys(defaultBooleanAttributes)) { 143 for (const key of Object.keys(defaultBooleanAttributes)) {
140 if (command[key] !== undefined) { 144 if (options[key] !== undefined) {
141 booleanAttributes[key] = command[key] 145 booleanAttributes[key] = options[key]
142 } else if (defaultAttributes[key] !== undefined) { 146 } else if (defaultAttributes[key] !== undefined) {
143 booleanAttributes[key] = defaultAttributes[key] 147 booleanAttributes[key] = defaultAttributes[key]
144 } else { 148 } else {
@@ -147,20 +151,20 @@ async function buildVideoAttributesFromCommander (url: string, command: Commande
147 } 151 }
148 152
149 const videoAttributes = { 153 const videoAttributes = {
150 name: command['videoName'] || defaultAttributes.name, 154 name: options.videoName || defaultAttributes.name,
151 category: command['category'] || defaultAttributes.category || undefined, 155 category: options.category || defaultAttributes.category || undefined,
152 licence: command['licence'] || defaultAttributes.licence || undefined, 156 licence: options.licence || defaultAttributes.licence || undefined,
153 language: command['language'] || defaultAttributes.language || undefined, 157 language: options.language || defaultAttributes.language || undefined,
154 privacy: command['privacy'] || defaultAttributes.privacy || VideoPrivacy.PUBLIC, 158 privacy: options.privacy || defaultAttributes.privacy || VideoPrivacy.PUBLIC,
155 support: command['support'] || defaultAttributes.support || undefined, 159 support: options.support || defaultAttributes.support || undefined,
156 description: command['videoDescription'] || defaultAttributes.description || undefined, 160 description: options.videoDescription || defaultAttributes.description || undefined,
157 tags: command['tags'] || defaultAttributes.tags || undefined 161 tags: options.tags || defaultAttributes.tags || undefined
158 } 162 }
159 163
160 Object.assign(videoAttributes, booleanAttributes) 164 Object.assign(videoAttributes, booleanAttributes)
161 165
162 if (command['channelName']) { 166 if (options.channelName) {
163 const res = await getVideoChannel(url, command['channelName']) 167 const res = await getVideoChannel(url, options.channelName)
164 const videoChannel: VideoChannel = res.body 168 const videoChannel: VideoChannel = res.body
165 169
166 Object.assign(videoAttributes, { channelId: videoChannel.id }) 170 Object.assign(videoAttributes, { channelId: videoChannel.id })
@@ -173,7 +177,7 @@ async function buildVideoAttributesFromCommander (url: string, command: Commande
173 return videoAttributes 177 return videoAttributes
174} 178}
175 179
176function getServerCredentials (program: any) { 180function getServerCredentials (program: CommanderStatic) {
177 return Promise.all([ getSettings(), getNetrc() ]) 181 return Promise.all([ getSettings(), getNetrc() ])
178 .then(([ settings, netrc ]) => { 182 .then(([ settings, netrc ]) => {
179 return getRemoteObjectOrDie(program, settings, netrc) 183 return getRemoteObjectOrDie(program, settings, netrc)
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts
index 6a0b89fe2..e54649002 100644
--- a/server/tools/peertube-auth.ts
+++ b/server/tools/peertube-auth.ts
@@ -66,7 +66,8 @@ program
66 .option('-U, --username <username>', 'Username') 66 .option('-U, --username <username>', 'Username')
67 .option('-p, --password <token>', 'Password') 67 .option('-p, --password <token>', 'Password')
68 .option('--default', 'add the entry as the new default') 68 .option('--default', 'add the entry as the new default')
69 .action(options => { 69 .action((options: program.OptionValues) => {
70 /* eslint-disable no-import-assign */
70 prompt.override = options 71 prompt.override = options
71 prompt.start() 72 prompt.start()
72 prompt.get({ 73 prompt.get({
@@ -102,7 +103,7 @@ program
102 process.exit(-1) 103 process.exit(-1)
103 } 104 }
104 105
105 await setInstance(result.url, result.username, result.password, program['default']) 106 await setInstance(result.url, result.username, result.password, options.default)
106 107
107 process.exit(0) 108 process.exit(0)
108 }) 109 })
@@ -160,15 +161,12 @@ program
160 } 161 }
161 }) 162 })
162 163
163program.on('--help', function () { 164program.addHelpText('after', '\n\n Examples:\n\n' +
164 console.log(' Examples:') 165 ' $ peertube auth add -u https://peertube.cpy.re -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' +
165 console.log() 166 ' $ peertube auth add -u https://peertube.cpy.re -U root\n' +
166 console.log(' $ peertube auth add -u https://peertube.cpy.re -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"') 167 ' $ peertube auth list\n' +
167 console.log(' $ peertube auth add -u https://peertube.cpy.re -U root') 168 ' $ peertube auth del https://peertube.cpy.re\n'
168 console.log(' $ peertube auth list') 169)
169 console.log(' $ peertube auth del https://peertube.cpy.re')
170 console.log()
171})
172 170
173if (!process.argv.slice(2).length) { 171if (!process.argv.slice(2).length) {
174 program.outputHelp() 172 program.outputHelp()
diff --git a/server/tools/peertube-get-access-token.ts b/server/tools/peertube-get-access-token.ts
index 6d47d8922..b2c278c48 100644
--- a/server/tools/peertube-get-access-token.ts
+++ b/server/tools/peertube-get-access-token.ts
@@ -10,25 +10,27 @@ program
10 .option('-p, --password <token>', 'Password') 10 .option('-p, --password <token>', 'Password')
11 .parse(process.argv) 11 .parse(process.argv)
12 12
13const options = program.opts()
14
13if ( 15if (
14 !program['url'] || 16 !options.url ||
15 !program['username'] || 17 !options.username ||
16 !program['password'] 18 !options.password
17) { 19) {
18 if (!program['url']) console.error('--url field is required.') 20 if (!options.url) console.error('--url field is required.')
19 if (!program['username']) console.error('--username field is required.') 21 if (!options.username) console.error('--username field is required.')
20 if (!program['password']) console.error('--password field is required.') 22 if (!options.password) console.error('--password field is required.')
21 23
22 process.exit(-1) 24 process.exit(-1)
23} 25}
24 26
25getClient(program.url) 27getClient(options.url)
26 .then(res => { 28 .then(res => {
27 const server = { 29 const server = {
28 url: program['url'], 30 url: options.url,
29 user: { 31 user: {
30 username: program['username'], 32 username: options.username,
31 password: program['password'] 33 password: options.password
32 }, 34 },
33 client: { 35 client: {
34 id: res.body.client_id, 36 id: res.body.client_id,
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index 65798adb4..d91767c29 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -45,22 +45,24 @@ command
45 .usage("[global options] [ -- youtube-dl options]") 45 .usage("[global options] [ -- youtube-dl options]")
46 .parse(process.argv) 46 .parse(process.argv)
47 47
48const log = getLogger(program['verbose']) 48const options = command.opts()
49
50const log = getLogger(options.verbose)
49 51
50getServerCredentials(command) 52getServerCredentials(command)
51 .then(({ url, username, password }) => { 53 .then(({ url, username, password }) => {
52 if (!program['targetUrl']) { 54 if (!options.targetUrl) {
53 exitError('--target-url field is required.') 55 exitError('--target-url field is required.')
54 } 56 }
55 57
56 try { 58 try {
57 accessSync(program['tmpdir'], constants.R_OK | constants.W_OK) 59 accessSync(options.tmpdir, constants.R_OK | constants.W_OK)
58 } catch (e) { 60 } catch (e) {
59 exitError('--tmpdir %s: directory does not exist or is not accessible', program['tmpdir']) 61 exitError('--tmpdir %s: directory does not exist or is not accessible', options.tmpdir)
60 } 62 }
61 63
62 url = normalizeTargetUrl(url) 64 url = normalizeTargetUrl(url)
63 program['targetUrl'] = normalizeTargetUrl(program['targetUrl']) 65 options.targetUrl = normalizeTargetUrl(options.targetUrl)
64 66
65 const user = { username, password } 67 const user = { username, password }
66 68
@@ -76,7 +78,7 @@ async function run (url: string, user: UserInfo) {
76 78
77 const youtubeDL = await safeGetYoutubeDL() 79 const youtubeDL = await safeGetYoutubeDL()
78 80
79 let info = await getYoutubeDLInfo(youtubeDL, program['targetUrl'], command.args) 81 let info = await getYoutubeDLInfo(youtubeDL, options.targetUrl, command.args)
80 82
81 if (!Array.isArray(info)) info = [ info ] 83 if (!Array.isArray(info)) info = [ info ]
82 84
@@ -92,10 +94,10 @@ async function run (url: string, user: UserInfo) {
92 let infoArray: any[] 94 let infoArray: any[]
93 95
94 infoArray = [].concat(info) 96 infoArray = [].concat(info)
95 if (program['first']) { 97 if (options.first) {
96 infoArray = infoArray.slice(0, program['first']) 98 infoArray = infoArray.slice(0, options.first)
97 } else if (program['last']) { 99 } else if (options.last) {
98 infoArray = infoArray.slice(-program['last']) 100 infoArray = infoArray.slice(-options.last)
99 } 101 }
100 // Normalize utf8 fields 102 // Normalize utf8 fields
101 infoArray = infoArray.map(i => normalizeObject(i)) 103 infoArray = infoArray.map(i => normalizeObject(i))
@@ -104,12 +106,12 @@ async function run (url: string, user: UserInfo) {
104 106
105 for (const [ index, info ] of infoArray.entries()) { 107 for (const [ index, info ] of infoArray.entries()) {
106 try { 108 try {
107 if (index > 0 && program['waitInterval']) { 109 if (index > 0 && options.waitInterval) {
108 log.info("Wait for %d seconds before continuing.", program['waitInterval'] / 1000) 110 log.info("Wait for %d seconds before continuing.", options.waitInterval / 1000)
109 await new Promise(res => setTimeout(res, program['waitInterval'])) 111 await new Promise(res => setTimeout(res, options.waitInterval))
110 } 112 }
111 await processVideo({ 113 await processVideo({
112 cwd: program['tmpdir'], 114 cwd: options.tmpdir,
113 url, 115 url,
114 user, 116 user,
115 youtubeInfo: info 117 youtubeInfo: info
@@ -119,7 +121,7 @@ async function run (url: string, user: UserInfo) {
119 } 121 }
120 } 122 }
121 123
122 log.info('Video/s for user %s imported: %s', user.username, program['targetUrl']) 124 log.info('Video/s for user %s imported: %s', user.username, options.targetUrl)
123 process.exit(0) 125 process.exit(0)
124} 126}
125 127
@@ -137,14 +139,14 @@ async function processVideo (parameters: {
137 log.debug('Fetched object.', videoInfo) 139 log.debug('Fetched object.', videoInfo)
138 140
139 const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo) 141 const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo)
140 if (program['since'] && originallyPublishedAt && originallyPublishedAt.getTime() < program['since'].getTime()) { 142 if (options.since && originallyPublishedAt && originallyPublishedAt.getTime() < options.since.getTime()) {
141 log.info('Video "%s" has been published before "%s", don\'t upload it.\n', 143 log.info('Video "%s" has been published before "%s", don\'t upload it.\n',
142 videoInfo.title, formatDate(program['since'])) 144 videoInfo.title, formatDate(options.since))
143 return 145 return
144 } 146 }
145 if (program['until'] && originallyPublishedAt && originallyPublishedAt.getTime() > program['until'].getTime()) { 147 if (options.until && originallyPublishedAt && originallyPublishedAt.getTime() > options.until.getTime()) {
146 log.info('Video "%s" has been published after "%s", don\'t upload it.\n', 148 log.info('Video "%s" has been published after "%s", don\'t upload it.\n',
147 videoInfo.title, formatDate(program['until'])) 149 videoInfo.title, formatDate(options.until))
148 return 150 return
149 } 151 }
150 152
@@ -161,11 +163,11 @@ async function processVideo (parameters: {
161 163
162 log.info('Downloading video "%s"...', videoInfo.title) 164 log.info('Downloading video "%s"...', videoInfo.title)
163 165
164 const options = [ '-f', getYoutubeDLVideoFormat(), ...command.args, '-o', path ] 166 const youtubeDLOptions = [ '-f', getYoutubeDLVideoFormat(), ...command.args, '-o', path ]
165 try { 167 try {
166 const youtubeDL = await safeGetYoutubeDL() 168 const youtubeDL = await safeGetYoutubeDL()
167 const youtubeDLExec = promisify(youtubeDL.exec).bind(youtubeDL) 169 const youtubeDLExec = promisify(youtubeDL.exec).bind(youtubeDL)
168 const output = await youtubeDLExec(videoInfo.url, options, processOptions) 170 const output = await youtubeDLExec(videoInfo.url, youtubeDLOptions, processOptions)
169 log.info(output.join('\n')) 171 log.info(output.join('\n'))
170 await uploadVideoOnPeerTube({ 172 await uploadVideoOnPeerTube({
171 cwd, 173 cwd,
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts
index 05b75fab2..08e8cd713 100644
--- a/server/tools/peertube-plugins.ts
+++ b/server/tools/peertube-plugins.ts
@@ -10,6 +10,7 @@ import { getAdminTokenOrDie, getServerCredentials } from './cli'
10import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' 10import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model'
11import { isAbsolute } from 'path' 11import { isAbsolute } from 'path'
12import * as CliTable3 from 'cli-table3' 12import * as CliTable3 from 'cli-table3'
13import commander = require('commander')
13 14
14program 15program
15 .name('plugins') 16 .name('plugins')
@@ -33,7 +34,7 @@ program
33 .option('-p, --password <token>', 'Password') 34 .option('-p, --password <token>', 'Password')
34 .option('-P --path <path>', 'Install from a path') 35 .option('-P --path <path>', 'Install from a path')
35 .option('-n, --npm-name <npmName>', 'Install from npm') 36 .option('-n, --npm-name <npmName>', 'Install from npm')
36 .action((options) => installPluginCLI(options)) 37 .action((options, command) => installPluginCLI(command, options))
37 38
38program 39program
39 .command('update') 40 .command('update')
@@ -43,7 +44,7 @@ program
43 .option('-p, --password <token>', 'Password') 44 .option('-p, --password <token>', 'Password')
44 .option('-P --path <path>', 'Update from a path') 45 .option('-P --path <path>', 'Update from a path')
45 .option('-n, --npm-name <npmName>', 'Update from npm') 46 .option('-n, --npm-name <npmName>', 'Update from npm')
46 .action((options) => updatePluginCLI(options)) 47 .action((options, command) => updatePluginCLI(command, options))
47 48
48program 49program
49 .command('uninstall') 50 .command('uninstall')
@@ -52,7 +53,7 @@ program
52 .option('-U, --username <username>', 'Username') 53 .option('-U, --username <username>', 'Username')
53 .option('-p, --password <token>', 'Password') 54 .option('-p, --password <token>', 'Password')
54 .option('-n, --npm-name <npmName>', 'NPM plugin/theme name') 55 .option('-n, --npm-name <npmName>', 'NPM plugin/theme name')
55 .action(options => uninstallPluginCLI(options)) 56 .action((options, command) => uninstallPluginCLI(command, options))
56 57
57if (!process.argv.slice(2).length) { 58if (!process.argv.slice(2).length) {
58 program.outputHelp() 59 program.outputHelp()
@@ -60,6 +61,8 @@ if (!process.argv.slice(2).length) {
60 61
61program.parse(process.argv) 62program.parse(process.argv)
62 63
64const options = program.opts()
65
63// ---------------------------------------------------------------------------- 66// ----------------------------------------------------------------------------
64 67
65async function pluginsListCLI () { 68async function pluginsListCLI () {
@@ -67,8 +70,8 @@ async function pluginsListCLI () {
67 const accessToken = await getAdminTokenOrDie(url, username, password) 70 const accessToken = await getAdminTokenOrDie(url, username, password)
68 71
69 let pluginType: PluginType 72 let pluginType: PluginType
70 if (program['onlyThemes']) pluginType = PluginType.THEME 73 if (options.onlyThemes) pluginType = PluginType.THEME
71 if (program['onlyPlugins']) pluginType = PluginType.PLUGIN 74 if (options.onlyPlugins) pluginType = PluginType.PLUGIN
72 75
73 const res = await listPlugins({ 76 const res = await listPlugins({
74 url, 77 url,
@@ -101,27 +104,27 @@ async function pluginsListCLI () {
101 process.exit(0) 104 process.exit(0)
102} 105}
103 106
104async function installPluginCLI (options: any) { 107async function installPluginCLI (command: commander.CommanderStatic, options: commander.OptionValues) {
105 if (!options['path'] && !options['npmName']) { 108 if (!options.path && !options.npmName) {
106 console.error('You need to specify the npm name or the path of the plugin you want to install.\n') 109 console.error('You need to specify the npm name or the path of the plugin you want to install.\n')
107 program.outputHelp() 110 program.outputHelp()
108 process.exit(-1) 111 process.exit(-1)
109 } 112 }
110 113
111 if (options['path'] && !isAbsolute(options['path'])) { 114 if (options.path && !isAbsolute(options.path)) {
112 console.error('Path should be absolute.') 115 console.error('Path should be absolute.')
113 process.exit(-1) 116 process.exit(-1)
114 } 117 }
115 118
116 const { url, username, password } = await getServerCredentials(options) 119 const { url, username, password } = await getServerCredentials(command)
117 const accessToken = await getAdminTokenOrDie(url, username, password) 120 const accessToken = await getAdminTokenOrDie(url, username, password)
118 121
119 try { 122 try {
120 await installPlugin({ 123 await installPlugin({
121 url, 124 url,
122 accessToken, 125 accessToken,
123 npmName: options['npmName'], 126 npmName: options.npmName,
124 path: options['path'] 127 path: options.path
125 }) 128 })
126 } catch (err) { 129 } catch (err) {
127 console.error('Cannot install plugin.', err) 130 console.error('Cannot install plugin.', err)
@@ -132,27 +135,27 @@ async function installPluginCLI (options: any) {
132 process.exit(0) 135 process.exit(0)
133} 136}
134 137
135async function updatePluginCLI (options: any) { 138async function updatePluginCLI (command: commander.CommanderStatic, options: commander.OptionValues) {
136 if (!options['path'] && !options['npmName']) { 139 if (!options.path && !options.npmName) {
137 console.error('You need to specify the npm name or the path of the plugin you want to update.\n') 140 console.error('You need to specify the npm name or the path of the plugin you want to update.\n')
138 program.outputHelp() 141 program.outputHelp()
139 process.exit(-1) 142 process.exit(-1)
140 } 143 }
141 144
142 if (options['path'] && !isAbsolute(options['path'])) { 145 if (options.path && !isAbsolute(options.path)) {
143 console.error('Path should be absolute.') 146 console.error('Path should be absolute.')
144 process.exit(-1) 147 process.exit(-1)
145 } 148 }
146 149
147 const { url, username, password } = await getServerCredentials(options) 150 const { url, username, password } = await getServerCredentials(command)
148 const accessToken = await getAdminTokenOrDie(url, username, password) 151 const accessToken = await getAdminTokenOrDie(url, username, password)
149 152
150 try { 153 try {
151 await updatePlugin({ 154 await updatePlugin({
152 url, 155 url,
153 accessToken, 156 accessToken,
154 npmName: options['npmName'], 157 npmName: options.npmName,
155 path: options['path'] 158 path: options.path
156 }) 159 })
157 } catch (err) { 160 } catch (err) {
158 console.error('Cannot update plugin.', err) 161 console.error('Cannot update plugin.', err)
@@ -163,21 +166,21 @@ async function updatePluginCLI (options: any) {
163 process.exit(0) 166 process.exit(0)
164} 167}
165 168
166async function uninstallPluginCLI (options: any) { 169async function uninstallPluginCLI (command: commander.CommanderStatic, options: commander.OptionValues) {
167 if (!options['npmName']) { 170 if (!options.npmName) {
168 console.error('You need to specify the npm name of the plugin/theme you want to uninstall.\n') 171 console.error('You need to specify the npm name of the plugin/theme you want to uninstall.\n')
169 program.outputHelp() 172 program.outputHelp()
170 process.exit(-1) 173 process.exit(-1)
171 } 174 }
172 175
173 const { url, username, password } = await getServerCredentials(options) 176 const { url, username, password } = await getServerCredentials(command)
174 const accessToken = await getAdminTokenOrDie(url, username, password) 177 const accessToken = await getAdminTokenOrDie(url, username, password)
175 178
176 try { 179 try {
177 await uninstallPlugin({ 180 await uninstallPlugin({
178 url, 181 url,
179 accessToken, 182 accessToken,
180 npmName: options['npmName'] 183 npmName: options.npmName
181 }) 184 })
182 } catch (err) { 185 } catch (err) {
183 console.error('Cannot uninstall plugin.', err) 186 console.error('Cannot uninstall plugin.', err)
diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts
index fe482daf4..5bc80ddb9 100644
--- a/server/tools/peertube-redundancy.ts
+++ b/server/tools/peertube-redundancy.ts
@@ -14,6 +14,7 @@ import { URL } from 'url'
14import { uniq } from 'lodash' 14import { uniq } from 'lodash'
15 15
16import bytes = require('bytes') 16import bytes = require('bytes')
17import commander = require('commander')
17 18
18program 19program
19 .name('plugins') 20 .name('plugins')
@@ -42,7 +43,7 @@ program
42 .option('-U, --username <username>', 'Username') 43 .option('-U, --username <username>', 'Username')
43 .option('-p, --password <token>', 'Password') 44 .option('-p, --password <token>', 'Password')
44 .option('-v, --video <videoId>', 'Video id to duplicate') 45 .option('-v, --video <videoId>', 'Video id to duplicate')
45 .action((options) => addRedundancyCLI(options)) 46 .action((options, command) => addRedundancyCLI(options, command))
46 47
47program 48program
48 .command('remove') 49 .command('remove')
@@ -51,7 +52,7 @@ program
51 .option('-U, --username <username>', 'Username') 52 .option('-U, --username <username>', 'Username')
52 .option('-p, --password <token>', 'Password') 53 .option('-p, --password <token>', 'Password')
53 .option('-v, --video <videoId>', 'Video id to remove from redundancies') 54 .option('-v, --video <videoId>', 'Video id to remove from redundancies')
54 .action((options) => removeRedundancyCLI(options)) 55 .action((options, command) => removeRedundancyCLI(options, command))
55 56
56if (!process.argv.slice(2).length) { 57if (!process.argv.slice(2).length) {
57 program.outputHelp() 58 program.outputHelp()
@@ -104,13 +105,13 @@ async function listRedundanciesCLI (target: VideoRedundanciesTarget) {
104 process.exit(0) 105 process.exit(0)
105} 106}
106 107
107async function addRedundancyCLI (options: { videoId: number }) { 108async function addRedundancyCLI (options: { video: number }, command: commander.CommanderStatic) {
108 const { url, username, password } = await getServerCredentials(program) 109 const { url, username, password } = await getServerCredentials(command)
109 const accessToken = await getAdminTokenOrDie(url, username, password) 110 const accessToken = await getAdminTokenOrDie(url, username, password)
110 111
111 if (!options['video'] || validator.isInt('' + options['video']) === false) { 112 if (!options.video || validator.isInt('' + options.video) === false) {
112 console.error('You need to specify the video id to duplicate and it should be a number.\n') 113 console.error('You need to specify the video id to duplicate and it should be a number.\n')
113 program.outputHelp() 114 command.outputHelp()
114 process.exit(-1) 115 process.exit(-1)
115 } 116 }
116 117
@@ -118,7 +119,7 @@ async function addRedundancyCLI (options: { videoId: number }) {
118 await addVideoRedundancy({ 119 await addVideoRedundancy({
119 url, 120 url,
120 accessToken, 121 accessToken,
121 videoId: options['video'] 122 videoId: options.video
122 }) 123 })
123 124
124 console.log('Video will be duplicated by your instance!') 125 console.log('Video will be duplicated by your instance!')
@@ -137,17 +138,17 @@ async function addRedundancyCLI (options: { videoId: number }) {
137 } 138 }
138} 139}
139 140
140async function removeRedundancyCLI (options: { videoId: number }) { 141async function removeRedundancyCLI (options: { video: number }, command: commander.CommanderStatic) {
141 const { url, username, password } = await getServerCredentials(program) 142 const { url, username, password } = await getServerCredentials(command)
142 const accessToken = await getAdminTokenOrDie(url, username, password) 143 const accessToken = await getAdminTokenOrDie(url, username, password)
143 144
144 if (!options['video'] || validator.isInt('' + options['video']) === false) { 145 if (!options.video || validator.isInt('' + options.video) === false) {
145 console.error('You need to specify the video id to remove from your redundancies.\n') 146 console.error('You need to specify the video id to remove from your redundancies.\n')
146 program.outputHelp() 147 command.outputHelp()
147 process.exit(-1) 148 process.exit(-1)
148 } 149 }
149 150
150 const videoId = parseInt(options['video'] + '', 10) 151 const videoId = parseInt(options.video + '', 10)
151 152
152 let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos') 153 let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos')
153 let videoRedundancy = redundancies.find(r => videoId === r.id) 154 let videoRedundancy = redundancies.find(r => videoId === r.id)
diff --git a/server/tools/peertube-repl.ts b/server/tools/peertube-repl.ts
index a5c35e9ea..a38d51801 100644
--- a/server/tools/peertube-repl.ts
+++ b/server/tools/peertube-repl.ts
@@ -82,7 +82,6 @@ const start = async () => {
82 } 82 }
83 replServer.defineCommand('reset', resetCommand) 83 replServer.defineCommand('reset', resetCommand)
84 replServer.defineCommand('r', resetCommand) 84 replServer.defineCommand('r', resetCommand)
85
86} 85}
87 86
88start() 87start()
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts
index 8de952e7b..86c7f3d91 100644
--- a/server/tools/peertube-upload.ts
+++ b/server/tools/peertube-upload.ts
@@ -22,16 +22,18 @@ command
22 .option('-f, --file <file>', 'Video absolute file path') 22 .option('-f, --file <file>', 'Video absolute file path')
23 .parse(process.argv) 23 .parse(process.argv)
24 24
25const options = command.opts()
26
25getServerCredentials(command) 27getServerCredentials(command)
26 .then(({ url, username, password }) => { 28 .then(({ url, username, password }) => {
27 if (!program['videoName'] || !program['file']) { 29 if (!options.videoName || !options.file) {
28 if (!program['videoName']) console.error('--video-name is required.') 30 if (!options.videoName) console.error('--video-name is required.')
29 if (!program['file']) console.error('--file is required.') 31 if (!options.file) console.error('--file is required.')
30 32
31 process.exit(-1) 33 process.exit(-1)
32 } 34 }
33 35
34 if (isAbsolute(program['file']) === false) { 36 if (isAbsolute(options.file) === false) {
35 console.error('File path should be absolute.') 37 console.error('File path should be absolute.')
36 process.exit(-1) 38 process.exit(-1)
37 } 39 }
@@ -46,21 +48,21 @@ getServerCredentials(command)
46async function run (url: string, username: string, password: string) { 48async function run (url: string, username: string, password: string) {
47 const accessToken = await getAccessToken(url, username, password) 49 const accessToken = await getAccessToken(url, username, password)
48 50
49 await access(program['file'], constants.F_OK) 51 await access(options.file, constants.F_OK)
50 52
51 console.log('Uploading %s video...', program['videoName']) 53 console.log('Uploading %s video...', options.videoName)
52 54
53 const videoAttributes = await buildVideoAttributesFromCommander(url, program) 55 const videoAttributes = await buildVideoAttributesFromCommander(url, program)
54 56
55 Object.assign(videoAttributes, { 57 Object.assign(videoAttributes, {
56 fixture: program['file'], 58 fixture: options.file,
57 thumbnailfile: program['thumbnail'], 59 thumbnailfile: options.thumbnail,
58 previewfile: program['preview'] 60 previewfile: options.preview
59 }) 61 })
60 62
61 try { 63 try {
62 await uploadVideo(url, accessToken, videoAttributes) 64 await uploadVideo(url, accessToken, videoAttributes)
63 console.log(`Video ${program['videoName']} uploaded.`) 65 console.log(`Video ${options.videoName} uploaded.`)
64 process.exit(0) 66 process.exit(0)
65 } catch (err) { 67 } catch (err) {
66 console.error(require('util').inspect(err)) 68 console.error(require('util').inspect(err))
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts
index b8e750a37..6d9cfa3b7 100644
--- a/server/tools/peertube-watch.ts
+++ b/server/tools/peertube-watch.ts
@@ -8,40 +8,30 @@ import { execSync } from 'child_process'
8program 8program
9 .name('watch') 9 .name('watch')
10 .arguments('<url>') 10 .arguments('<url>')
11 .option('-g, --gui <player>', 'player type', /^(airplay|stdout|chromecast|mpv|vlc|mplayer|xbmc)$/i, 'vlc') 11 .addOption(
12 new program.Option('-g, --gui <player>', 'player type')
13 .default('vlc')
14 .choices([ 'airplay', 'stdout', 'chromecast', 'mpv', 'vlc', 'mplayer', 'xbmc' ])
15 )
12 .option('-r, --resolution <res>', 'video resolution', '480') 16 .option('-r, --resolution <res>', 'video resolution', '480')
13 .on('--help', function () { 17 .addHelpText('after', '\n\n Examples:\n\n' +
14 console.log(' Available Players:') 18 ' $ peertube watch -g mpv https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n' +
15 console.log() 19 ' $ peertube watch --gui stdout https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n' +
16 console.log(' - mpv') 20 ' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n'
17 console.log(' - mplayer') 21 )
18 console.log(' - vlc') 22 .action((url, options) => run(url, options))
19 console.log(' - stdout')
20 console.log(' - xbmc')
21 console.log(' - airplay')
22 console.log(' - chromecast')
23 console.log()
24 console.log()
25 console.log(' Examples:')
26 console.log()
27 console.log(' $ peertube watch -g mpv https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
28 console.log(' $ peertube watch --gui stdout https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
29 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
30 console.log()
31 })
32 .action((url, cmd) => run(url, cmd))
33 .parse(process.argv) 23 .parse(process.argv)
34 24
35function run (url: string, program: any) { 25function run (url: string, options: program.OptionValues) {
36 if (!url) { 26 if (!url) {
37 console.error('<url> positional argument is required.') 27 console.error('<url> positional argument is required.')
38 process.exit(-1) 28 process.exit(-1)
39 } 29 }
40 30
41 const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js') 31 const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js')
42 const args = ` --${program.gui} ` + 32 const args = ` --${options.gui} ` +
43 url.replace('videos/watch', 'download/torrents') + 33 url.replace('videos/watch', 'download/torrents') +
44 `-${program.resolution}.torrent` 34 `-${options.resolution}.torrent`
45 35
46 try { 36 try {
47 execSync(cmd + args) 37 execSync(cmd + args)
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
index 88dd5f7f6..655f07f0c 100644
--- a/server/tools/peertube.ts
+++ b/server/tools/peertube.ts
@@ -69,17 +69,12 @@ getSettings()
69 : 'instance ' + settings.remotes[settings.default] + ' selected' 69 : 'instance ' + settings.remotes[settings.default] + ' selected'
70 70
71 program 71 program
72 .on('--help', function () { 72 .addHelpText('after', '\n\n State: ' + state + '\n\n' +
73 console.log() 73 ' Examples:\n\n' +
74 console.log(' State: ' + state) 74 ' $ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' +
75 console.log() 75 ' $ peertube up <videoFile>\n' +
76 console.log(' Examples:') 76 ' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10\n'
77 console.log() 77 )
78 console.log(' $ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"')
79 console.log(' $ peertube up <videoFile>')
80 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
81 console.log()
82 })
83 .parse(process.argv) 78 .parse(process.argv)
84 }) 79 })
85 .catch(err => console.error(err)) 80 .catch(err => console.error(err))
diff --git a/server/types/express.ts b/server/types/express.ts
new file mode 100644
index 000000000..e72be36e4
--- /dev/null
+++ b/server/types/express.ts
@@ -0,0 +1,3 @@
1import { NextFunction, Request, Response } from 'express'
2
3export type ExpressPromiseHandler = (req: Request<any>, res: Response, next: NextFunction) => Promise<any>