aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/core-utils/common/array.ts (renamed from shared/core-utils/utils/array.ts)0
-rw-r--r--shared/core-utils/common/env.ts30
-rw-r--r--shared/core-utils/common/index.ts9
-rw-r--r--shared/core-utils/common/object.ts35
-rw-r--r--shared/core-utils/common/path.ts48
-rw-r--r--shared/core-utils/common/random.ts8
-rw-r--r--shared/core-utils/common/time.ts7
-rw-r--r--shared/core-utils/common/url.ts2
-rw-r--r--shared/core-utils/common/version.ts (renamed from shared/core-utils/common/miscs.ts)23
-rw-r--r--shared/core-utils/i18n/i18n.ts3
-rw-r--r--shared/core-utils/index.ts1
-rw-r--r--shared/core-utils/users/user-role.ts2
-rw-r--r--shared/core-utils/utils/index.ts2
-rw-r--r--shared/core-utils/utils/object.ts15
-rw-r--r--shared/core-utils/videos/bitrate.ts2
-rw-r--r--shared/extra-utils/crypto.ts20
-rw-r--r--shared/extra-utils/ffprobe.ts187
-rw-r--r--shared/extra-utils/file.ts11
-rw-r--r--shared/extra-utils/index.ts19
-rw-r--r--shared/extra-utils/miscs/checks.ts58
-rw-r--r--shared/extra-utils/miscs/generate.ts75
-rw-r--r--shared/extra-utils/miscs/index.ts5
-rw-r--r--shared/extra-utils/miscs/tests.ts101
-rw-r--r--shared/extra-utils/mock-servers/index.ts5
-rw-r--r--shared/extra-utils/mock-servers/mock-429.ts33
-rw-r--r--shared/extra-utils/mock-servers/mock-email.ts63
-rw-r--r--shared/extra-utils/mock-servers/mock-instances-index.ts46
-rw-r--r--shared/extra-utils/mock-servers/mock-joinpeertube-versions.ts34
-rw-r--r--shared/extra-utils/mock-servers/mock-object-storage.ts41
-rw-r--r--shared/extra-utils/mock-servers/mock-plugin-blocklist.ts36
-rw-r--r--shared/extra-utils/mock-servers/mock-proxy.ts25
-rw-r--r--shared/extra-utils/mock-servers/utils.ts33
-rw-r--r--shared/extra-utils/requests/activitypub.ts42
-rw-r--r--shared/extra-utils/requests/check-api-params.ts48
-rw-r--r--shared/extra-utils/requests/index.ts3
-rw-r--r--shared/extra-utils/server/directories.ts34
-rw-r--r--shared/extra-utils/server/plugins.ts18
-rw-r--r--shared/extra-utils/server/tracker.ts27
-rw-r--r--shared/extra-utils/users/actors.ts73
-rw-r--r--shared/extra-utils/users/notifications.ts795
-rw-r--r--shared/extra-utils/uuid.ts32
-rw-r--r--shared/extra-utils/videos/captions.ts21
-rw-r--r--shared/extra-utils/videos/playlists.ts25
-rw-r--r--shared/extra-utils/videos/streaming-playlists.ts77
-rw-r--r--shared/extra-utils/videos/videos.ts253
-rw-r--r--shared/index.ts1
-rw-r--r--shared/models/http/http-error-codes.ts2
-rw-r--r--shared/models/http/http-methods.ts2
-rw-r--r--shared/models/moderation/block-status.model.ts15
-rw-r--r--shared/models/moderation/index.ts1
-rw-r--r--shared/models/plugins/client/index.ts1
-rw-r--r--shared/models/plugins/client/plugin-selector-id.type.ts11
-rw-r--r--shared/models/plugins/client/register-client-form-field.model.ts7
-rw-r--r--shared/models/plugins/client/register-client-route.model.ts7
-rw-r--r--shared/models/plugins/client/register-client-settings-script.model.ts2
-rw-r--r--shared/models/plugins/plugin-index/peertube-plugin-index.model.ts2
-rw-r--r--shared/models/plugins/plugin-package-json.model.ts10
-rw-r--r--shared/models/plugins/plugin.type.ts2
-rw-r--r--shared/models/plugins/server/api/install-plugin.model.ts1
-rw-r--r--shared/models/plugins/server/server-hook.model.ts14
-rw-r--r--shared/models/server/custom-config.model.ts14
-rw-r--r--shared/models/server/job.model.ts9
-rw-r--r--shared/models/server/server-config.model.ts31
-rw-r--r--shared/models/users/index.ts1
-rw-r--r--shared/models/users/user-right.enum.ts2
-rw-r--r--shared/models/users/user-role.ts2
-rw-r--r--shared/models/users/user-update-me.model.ts3
-rw-r--r--shared/models/users/user.model.ts4
-rw-r--r--shared/models/videos/video-state.enum.ts3
-rw-r--r--shared/server-commands/bulk/bulk-command.ts (renamed from shared/extra-utils/bulk/bulk-command.ts)0
-rw-r--r--shared/server-commands/bulk/index.ts (renamed from shared/extra-utils/bulk/index.ts)0
-rw-r--r--shared/server-commands/cli/cli-command.ts (renamed from shared/extra-utils/cli/cli-command.ts)0
-rw-r--r--shared/server-commands/cli/index.ts (renamed from shared/extra-utils/cli/index.ts)0
-rw-r--r--shared/server-commands/custom-pages/custom-pages-command.ts (renamed from shared/extra-utils/custom-pages/custom-pages-command.ts)0
-rw-r--r--shared/server-commands/custom-pages/index.ts (renamed from shared/extra-utils/custom-pages/index.ts)0
-rw-r--r--shared/server-commands/feeds/feeds-command.ts (renamed from shared/extra-utils/feeds/feeds-command.ts)0
-rw-r--r--shared/server-commands/feeds/index.ts (renamed from shared/extra-utils/feeds/index.ts)0
-rw-r--r--shared/server-commands/index.ts14
-rw-r--r--shared/server-commands/logs/index.ts (renamed from shared/extra-utils/logs/index.ts)0
-rw-r--r--shared/server-commands/logs/logs-command.ts (renamed from shared/extra-utils/logs/logs-command.ts)3
-rw-r--r--shared/server-commands/miscs/index.ts2
-rw-r--r--shared/server-commands/miscs/sql-command.ts (renamed from shared/extra-utils/miscs/sql-command.ts)2
-rw-r--r--shared/server-commands/miscs/webtorrent.ts (renamed from shared/extra-utils/miscs/webtorrent.ts)0
-rw-r--r--shared/server-commands/moderation/abuses-command.ts (renamed from shared/extra-utils/moderation/abuses-command.ts)0
-rw-r--r--shared/server-commands/moderation/index.ts (renamed from shared/extra-utils/moderation/index.ts)0
-rw-r--r--shared/server-commands/overviews/index.ts (renamed from shared/extra-utils/overviews/index.ts)0
-rw-r--r--shared/server-commands/overviews/overviews-command.ts (renamed from shared/extra-utils/overviews/overviews-command.ts)0
-rw-r--r--shared/server-commands/requests/index.ts1
-rw-r--r--shared/server-commands/requests/requests.ts (renamed from shared/extra-utils/requests/requests.ts)2
-rw-r--r--shared/server-commands/search/index.ts (renamed from shared/extra-utils/search/index.ts)0
-rw-r--r--shared/server-commands/search/search-command.ts (renamed from shared/extra-utils/search/search-command.ts)0
-rw-r--r--shared/server-commands/server/config-command.ts (renamed from shared/extra-utils/server/config-command.ts)19
-rw-r--r--shared/server-commands/server/contact-form-command.ts (renamed from shared/extra-utils/server/contact-form-command.ts)0
-rw-r--r--shared/server-commands/server/debug-command.ts (renamed from shared/extra-utils/server/debug-command.ts)0
-rw-r--r--shared/server-commands/server/follows-command.ts (renamed from shared/extra-utils/server/follows-command.ts)0
-rw-r--r--shared/server-commands/server/follows.ts (renamed from shared/extra-utils/server/follows.ts)0
-rw-r--r--shared/server-commands/server/index.ts (renamed from shared/extra-utils/server/index.ts)3
-rw-r--r--shared/server-commands/server/jobs-command.ts (renamed from shared/extra-utils/server/jobs-command.ts)3
-rw-r--r--shared/server-commands/server/jobs.ts (renamed from shared/extra-utils/server/jobs.ts)2
-rw-r--r--shared/server-commands/server/object-storage-command.ts (renamed from shared/extra-utils/server/object-storage-command.ts)0
-rw-r--r--shared/server-commands/server/plugins-command.ts (renamed from shared/extra-utils/server/plugins-command.ts)11
-rw-r--r--shared/server-commands/server/redundancy-command.ts (renamed from shared/extra-utils/server/redundancy-command.ts)0
-rw-r--r--shared/server-commands/server/server.ts (renamed from shared/extra-utils/server/server.ts)31
-rw-r--r--shared/server-commands/server/servers-command.ts (renamed from shared/extra-utils/server/servers-command.ts)4
-rw-r--r--shared/server-commands/server/servers.ts (renamed from shared/extra-utils/server/servers.ts)2
-rw-r--r--shared/server-commands/server/stats-command.ts (renamed from shared/extra-utils/server/stats-command.ts)0
-rw-r--r--shared/server-commands/shared/abstract-command.ts (renamed from shared/extra-utils/shared/abstract-command.ts)2
-rw-r--r--shared/server-commands/shared/index.ts (renamed from shared/extra-utils/shared/index.ts)0
-rw-r--r--shared/server-commands/socket/index.ts (renamed from shared/extra-utils/socket/index.ts)0
-rw-r--r--shared/server-commands/socket/socket-io-command.ts (renamed from shared/extra-utils/socket/socket-io-command.ts)0
-rw-r--r--shared/server-commands/users/accounts-command.ts (renamed from shared/extra-utils/users/accounts-command.ts)4
-rw-r--r--shared/server-commands/users/blocklist-command.ts (renamed from shared/extra-utils/users/blocklist-command.ts)25
-rw-r--r--shared/server-commands/users/index.ts (renamed from shared/extra-utils/users/index.ts)2
-rw-r--r--shared/server-commands/users/login-command.ts (renamed from shared/extra-utils/users/login-command.ts)0
-rw-r--r--shared/server-commands/users/login.ts (renamed from shared/extra-utils/users/login.ts)0
-rw-r--r--shared/server-commands/users/notifications-command.ts (renamed from shared/extra-utils/users/notifications-command.ts)3
-rw-r--r--shared/server-commands/users/subscriptions-command.ts (renamed from shared/extra-utils/users/subscriptions-command.ts)0
-rw-r--r--shared/server-commands/users/users-command.ts (renamed from shared/extra-utils/users/users-command.ts)5
-rw-r--r--shared/server-commands/videos/blacklist-command.ts (renamed from shared/extra-utils/videos/blacklist-command.ts)3
-rw-r--r--shared/server-commands/videos/captions-command.ts (renamed from shared/extra-utils/videos/captions-command.ts)2
-rw-r--r--shared/server-commands/videos/change-ownership-command.ts (renamed from shared/extra-utils/videos/change-ownership-command.ts)0
-rw-r--r--shared/server-commands/videos/channels-command.ts (renamed from shared/extra-utils/videos/channels-command.ts)12
-rw-r--r--shared/server-commands/videos/channels.ts (renamed from shared/extra-utils/videos/channels.ts)0
-rw-r--r--shared/server-commands/videos/comments-command.ts (renamed from shared/extra-utils/videos/comments-command.ts)0
-rw-r--r--shared/server-commands/videos/history-command.ts (renamed from shared/extra-utils/videos/history-command.ts)0
-rw-r--r--shared/server-commands/videos/imports-command.ts (renamed from shared/extra-utils/videos/imports-command.ts)0
-rw-r--r--shared/server-commands/videos/index.ts (renamed from shared/extra-utils/videos/index.ts)4
-rw-r--r--shared/server-commands/videos/live-command.ts (renamed from shared/extra-utils/videos/live-command.ts)2
-rw-r--r--shared/server-commands/videos/live.ts (renamed from shared/extra-utils/videos/live.ts)41
-rw-r--r--shared/server-commands/videos/playlists-command.ts (renamed from shared/extra-utils/videos/playlists-command.ts)0
-rw-r--r--shared/server-commands/videos/services-command.ts (renamed from shared/extra-utils/videos/services-command.ts)0
-rw-r--r--shared/server-commands/videos/streaming-playlists-command.ts (renamed from shared/extra-utils/videos/streaming-playlists-command.ts)0
-rw-r--r--shared/server-commands/videos/videos-command.ts (renamed from shared/extra-utils/videos/videos-command.ts)15
-rw-r--r--shared/tsconfig.json6
-rw-r--r--shared/tsconfig.types.json12
-rw-r--r--shared/typescript-utils/index.ts1
-rw-r--r--shared/typescript-utils/types.ts (renamed from shared/core-utils/common/types.ts)0
137 files changed, 658 insertions, 2154 deletions
diff --git a/shared/core-utils/utils/array.ts b/shared/core-utils/common/array.ts
index 9e326a5aa..9e326a5aa 100644
--- a/shared/core-utils/utils/array.ts
+++ b/shared/core-utils/common/array.ts
diff --git a/shared/core-utils/common/env.ts b/shared/core-utils/common/env.ts
new file mode 100644
index 000000000..38c96b152
--- /dev/null
+++ b/shared/core-utils/common/env.ts
@@ -0,0 +1,30 @@
1function parallelTests () {
2 return process.env.MOCHA_PARALLEL === 'true'
3}
4
5function isGithubCI () {
6 return !!process.env.GITHUB_WORKSPACE
7}
8
9function areHttpImportTestsDisabled () {
10 const disabled = process.env.DISABLE_HTTP_IMPORT_TESTS === 'true'
11
12 if (disabled) console.log('DISABLE_HTTP_IMPORT_TESTS env set to "true" so import tests are disabled')
13
14 return disabled
15}
16
17function areObjectStorageTestsDisabled () {
18 const disabled = process.env.ENABLE_OBJECT_STORAGE_TESTS !== 'true'
19
20 if (disabled) console.log('ENABLE_OBJECT_STORAGE_TESTS env is not set to "true" so object storage tests are disabled')
21
22 return disabled
23}
24
25export {
26 parallelTests,
27 isGithubCI,
28 areHttpImportTestsDisabled,
29 areObjectStorageTestsDisabled
30}
diff --git a/shared/core-utils/common/index.ts b/shared/core-utils/common/index.ts
index 0908ff981..720977ead 100644
--- a/shared/core-utils/common/index.ts
+++ b/shared/core-utils/common/index.ts
@@ -1,6 +1,11 @@
1export * from './array'
2export * from './random'
1export * from './date' 3export * from './date'
2export * from './miscs' 4export * from './env'
5export * from './object'
6export * from './path'
3export * from './regexp' 7export * from './regexp'
8export * from './time'
4export * from './promises' 9export * from './promises'
5export * from './types'
6export * from './url' 10export * from './url'
11export * from './version'
diff --git a/shared/core-utils/common/object.ts b/shared/core-utils/common/object.ts
new file mode 100644
index 000000000..49d209819
--- /dev/null
+++ b/shared/core-utils/common/object.ts
@@ -0,0 +1,35 @@
1function pick <O extends object, K extends keyof O> (object: O, keys: K[]): Pick<O, K> {
2 const result: any = {}
3
4 for (const key of keys) {
5 if (Object.prototype.hasOwnProperty.call(object, key)) {
6 result[key] = object[key]
7 }
8 }
9
10 return result
11}
12
13function getKeys <O extends object, K extends keyof O> (object: O, keys: K[]): K[] {
14 return (Object.keys(object) as K[]).filter(k => keys.includes(k))
15}
16
17function sortObjectComparator (key: string, order: 'asc' | 'desc') {
18 return (a: any, b: any) => {
19 if (a[key] < b[key]) {
20 return order === 'asc' ? -1 : 1
21 }
22
23 if (a[key] > b[key]) {
24 return order === 'asc' ? 1 : -1
25 }
26
27 return 0
28 }
29}
30
31export {
32 pick,
33 getKeys,
34 sortObjectComparator
35}
diff --git a/shared/core-utils/common/path.ts b/shared/core-utils/common/path.ts
new file mode 100644
index 000000000..006505316
--- /dev/null
+++ b/shared/core-utils/common/path.ts
@@ -0,0 +1,48 @@
1import { basename, extname, isAbsolute, join, resolve } from 'path'
2
3let rootPath: string
4
5function root () {
6 if (rootPath) return rootPath
7
8 rootPath = __dirname
9
10 if (basename(rootPath) === 'tools') rootPath = resolve(rootPath, '..')
11 if (basename(rootPath) === 'scripts') rootPath = resolve(rootPath, '..')
12 if (basename(rootPath) === 'common') rootPath = resolve(rootPath, '..')
13 if (basename(rootPath) === 'core-utils') rootPath = resolve(rootPath, '..')
14 if (basename(rootPath) === 'shared') rootPath = resolve(rootPath, '..')
15 if (basename(rootPath) === 'server') rootPath = resolve(rootPath, '..')
16 if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
17
18 return rootPath
19}
20
21function buildPath (path: string) {
22 if (isAbsolute(path)) return path
23
24 return join(root(), path)
25}
26
27function getLowercaseExtension (filename: string) {
28 const ext = extname(filename) || ''
29
30 return ext.toLowerCase()
31}
32
33function buildAbsoluteFixturePath (path: string, customCIPath = false) {
34 if (isAbsolute(path)) return path
35
36 if (customCIPath && process.env.GITHUB_WORKSPACE) {
37 return join(process.env.GITHUB_WORKSPACE, 'fixtures', path)
38 }
39
40 return join(root(), 'server', 'tests', 'fixtures', path)
41}
42
43export {
44 root,
45 buildPath,
46 buildAbsoluteFixturePath,
47 getLowercaseExtension
48}
diff --git a/shared/core-utils/common/random.ts b/shared/core-utils/common/random.ts
new file mode 100644
index 000000000..705735d09
--- /dev/null
+++ b/shared/core-utils/common/random.ts
@@ -0,0 +1,8 @@
1// high excluded
2function randomInt (low: number, high: number) {
3 return Math.floor(Math.random() * (high - low) + low)
4}
5
6export {
7 randomInt
8}
diff --git a/shared/core-utils/common/time.ts b/shared/core-utils/common/time.ts
new file mode 100644
index 000000000..2992609ca
--- /dev/null
+++ b/shared/core-utils/common/time.ts
@@ -0,0 +1,7 @@
1function wait (milliseconds: number) {
2 return new Promise(resolve => setTimeout(resolve, milliseconds))
3}
4
5export {
6 wait
7}
diff --git a/shared/core-utils/common/url.ts b/shared/core-utils/common/url.ts
index 9c111cbcc..8020d9b28 100644
--- a/shared/core-utils/common/url.ts
+++ b/shared/core-utils/common/url.ts
@@ -50,6 +50,7 @@ function decorateVideoLink (options: {
50 warningTitle?: boolean 50 warningTitle?: boolean
51 controls?: boolean 51 controls?: boolean
52 peertubeLink?: boolean 52 peertubeLink?: boolean
53 p2p?: boolean
53}) { 54}) {
54 const { url } = options 55 const { url } = options
55 56
@@ -74,6 +75,7 @@ function decorateVideoLink (options: {
74 if (options.warningTitle === false) params.set('warningTitle', '0') 75 if (options.warningTitle === false) params.set('warningTitle', '0')
75 if (options.controls === false) params.set('controls', '0') 76 if (options.controls === false) params.set('controls', '0')
76 if (options.peertubeLink === false) params.set('peertubeLink', '0') 77 if (options.peertubeLink === false) params.set('peertubeLink', '0')
78 if (options.p2p !== undefined) params.set('p2p', options.p2p ? '1' : '0')
77 79
78 return buildUrl(url, params) 80 return buildUrl(url, params)
79} 81}
diff --git a/shared/core-utils/common/miscs.ts b/shared/core-utils/common/version.ts
index bc65dc338..8a64f8c4d 100644
--- a/shared/core-utils/common/miscs.ts
+++ b/shared/core-utils/common/version.ts
@@ -1,8 +1,3 @@
1// high excluded
2function randomInt (low: number, high: number) {
3 return Math.floor(Math.random() * (high - low) + low)
4}
5
6// Thanks https://stackoverflow.com/a/16187766 1// Thanks https://stackoverflow.com/a/16187766
7function compareSemVer (a: string, b: string) { 2function compareSemVer (a: string, b: string) {
8 const regExStrip0 = /(\.0+)+$/ 3 const regExStrip0 = /(\.0+)+$/
@@ -20,22 +15,6 @@ function compareSemVer (a: string, b: string) {
20 return segmentsA.length - segmentsB.length 15 return segmentsA.length - segmentsB.length
21} 16}
22 17
23function sortObjectComparator (key: string, order: 'asc' | 'desc') {
24 return (a: any, b: any) => {
25 if (a[key] < b[key]) {
26 return order === 'asc' ? -1 : 1
27 }
28
29 if (a[key] > b[key]) {
30 return order === 'asc' ? 1 : -1
31 }
32
33 return 0
34 }
35}
36
37export { 18export {
38 randomInt, 19 compareSemVer
39 compareSemVer,
40 sortObjectComparator
41} 20}
diff --git a/shared/core-utils/i18n/i18n.ts b/shared/core-utils/i18n/i18n.ts
index f27de20f1..ae6af8192 100644
--- a/shared/core-utils/i18n/i18n.ts
+++ b/shared/core-utils/i18n/i18n.ts
@@ -28,6 +28,8 @@ export const I18N_LOCALES = {
28 'ru-RU': 'русский', 28 'ru-RU': 'русский',
29 'sq': 'Shqip', 29 'sq': 'Shqip',
30 'sv-SE': 'Svenska', 30 'sv-SE': 'Svenska',
31 'nn': 'norsk nynorsk',
32 'nb-NO': 'norsk bokmål',
31 'th-TH': 'ไทย', 33 'th-TH': 'ไทย',
32 'vi-VN': 'Tiếng Việt', 34 'vi-VN': 'Tiếng Việt',
33 'zh-Hans-CN': '简体中文(中国)', 35 'zh-Hans-CN': '简体中文(中国)',
@@ -52,6 +54,7 @@ const I18N_LOCALE_ALIAS = {
52 'nl': 'nl-NL', 54 'nl': 'nl-NL',
53 'pl': 'pl-PL', 55 'pl': 'pl-PL',
54 'pt': 'pt-BR', 56 'pt': 'pt-BR',
57 'nb': 'nb-NO',
55 'ru': 'ru-RU', 58 'ru': 'ru-RU',
56 'sv': 'sv-SE', 59 'sv': 'sv-SE',
57 'th': 'th-TH', 60 'th': 'th-TH',
diff --git a/shared/core-utils/index.ts b/shared/core-utils/index.ts
index e0a6a8087..8daaa2d04 100644
--- a/shared/core-utils/index.ts
+++ b/shared/core-utils/index.ts
@@ -4,5 +4,4 @@ export * from './i18n'
4export * from './plugins' 4export * from './plugins'
5export * from './renderer' 5export * from './renderer'
6export * from './users' 6export * from './users'
7export * from './utils'
8export * from './videos' 7export * from './videos'
diff --git a/shared/core-utils/users/user-role.ts b/shared/core-utils/users/user-role.ts
index 81cba1dad..cc757d779 100644
--- a/shared/core-utils/users/user-role.ts
+++ b/shared/core-utils/users/user-role.ts
@@ -14,8 +14,8 @@ const userRoleRights: { [ id in UserRole ]: UserRight[] } = {
14 [UserRole.MODERATOR]: [ 14 [UserRole.MODERATOR]: [
15 UserRight.MANAGE_VIDEO_BLACKLIST, 15 UserRight.MANAGE_VIDEO_BLACKLIST,
16 UserRight.MANAGE_ABUSES, 16 UserRight.MANAGE_ABUSES,
17 UserRight.MANAGE_ANY_VIDEO_CHANNEL,
17 UserRight.REMOVE_ANY_VIDEO, 18 UserRight.REMOVE_ANY_VIDEO,
18 UserRight.REMOVE_ANY_VIDEO_CHANNEL,
19 UserRight.REMOVE_ANY_VIDEO_PLAYLIST, 19 UserRight.REMOVE_ANY_VIDEO_PLAYLIST,
20 UserRight.REMOVE_ANY_VIDEO_COMMENT, 20 UserRight.REMOVE_ANY_VIDEO_COMMENT,
21 UserRight.UPDATE_ANY_VIDEO, 21 UserRight.UPDATE_ANY_VIDEO,
diff --git a/shared/core-utils/utils/index.ts b/shared/core-utils/utils/index.ts
deleted file mode 100644
index 8d16365a8..000000000
--- a/shared/core-utils/utils/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './array'
2export * from './object'
diff --git a/shared/core-utils/utils/object.ts b/shared/core-utils/utils/object.ts
deleted file mode 100644
index 9a8a98f9b..000000000
--- a/shared/core-utils/utils/object.ts
+++ /dev/null
@@ -1,15 +0,0 @@
1function pick <O extends object, K extends keyof O> (object: O, keys: K[]): Pick<O, K> {
2 const result: any = {}
3
4 for (const key of keys) {
5 if (Object.prototype.hasOwnProperty.call(object, key)) {
6 result[key] = object[key]
7 }
8 }
9
10 return result
11}
12
13export {
14 pick
15}
diff --git a/shared/core-utils/videos/bitrate.ts b/shared/core-utils/videos/bitrate.ts
index c1891188f..30d22df09 100644
--- a/shared/core-utils/videos/bitrate.ts
+++ b/shared/core-utils/videos/bitrate.ts
@@ -1,4 +1,4 @@
1import { VideoResolution } from "@shared/models" 1import { VideoResolution } from '@shared/models'
2 2
3type BitPerPixel = { [ id in VideoResolution ]: number } 3type BitPerPixel = { [ id in VideoResolution ]: number }
4 4
diff --git a/shared/extra-utils/crypto.ts b/shared/extra-utils/crypto.ts
new file mode 100644
index 000000000..1a583f1a0
--- /dev/null
+++ b/shared/extra-utils/crypto.ts
@@ -0,0 +1,20 @@
1import { BinaryToTextEncoding, createHash } from 'crypto'
2
3function sha256 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
4 return createHash('sha256').update(str).digest(encoding)
5}
6
7function sha1 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
8 return createHash('sha1').update(str).digest(encoding)
9}
10
11// high excluded
12function randomInt (low: number, high: number) {
13 return Math.floor(Math.random() * (high - low) + low)
14}
15
16export {
17 randomInt,
18 sha256,
19 sha1
20}
diff --git a/shared/extra-utils/ffprobe.ts b/shared/extra-utils/ffprobe.ts
new file mode 100644
index 000000000..53a3aa001
--- /dev/null
+++ b/shared/extra-utils/ffprobe.ts
@@ -0,0 +1,187 @@
1import { ffprobe, FfprobeData } from 'fluent-ffmpeg'
2import { VideoFileMetadata } from '@shared/models/videos'
3
4/**
5 *
6 * Helpers to run ffprobe and extract data from the JSON output
7 *
8 */
9
10function ffprobePromise (path: string) {
11 return new Promise<FfprobeData>((res, rej) => {
12 ffprobe(path, (err, data) => {
13 if (err) return rej(err)
14
15 return res(data)
16 })
17 })
18}
19
20async function isAudioFile (path: string, existingProbe?: FfprobeData) {
21 const videoStream = await getVideoStreamFromFile(path, existingProbe)
22
23 return !videoStream
24}
25
26async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
27 // without position, ffprobe considers the last input only
28 // we make it consider the first input only
29 // if you pass a file path to pos, then ffprobe acts on that file directly
30 const data = existingProbe || await ffprobePromise(videoPath)
31
32 if (Array.isArray(data.streams)) {
33 const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
34
35 if (audioStream) {
36 return {
37 absolutePath: data.format.filename,
38 audioStream,
39 bitrate: parseInt(audioStream['bit_rate'] + '', 10)
40 }
41 }
42 }
43
44 return { absolutePath: data.format.filename }
45}
46
47function getMaxAudioBitrate (type: 'aac' | 'mp3' | string, bitrate: number) {
48 const maxKBitrate = 384
49 const kToBits = (kbits: number) => kbits * 1000
50
51 // If we did not manage to get the bitrate, use an average value
52 if (!bitrate) return 256
53
54 if (type === 'aac') {
55 switch (true) {
56 case bitrate > kToBits(maxKBitrate):
57 return maxKBitrate
58
59 default:
60 return -1 // we interpret it as a signal to copy the audio stream as is
61 }
62 }
63
64 /*
65 a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
66 That's why, when using aac, we can go to lower kbit/sec. The equivalences
67 made here are not made to be accurate, especially with good mp3 encoders.
68 */
69 switch (true) {
70 case bitrate <= kToBits(192):
71 return 128
72
73 case bitrate <= kToBits(384):
74 return 256
75
76 default:
77 return maxKBitrate
78 }
79}
80
81async function getVideoStreamSize (path: string, existingProbe?: FfprobeData): Promise<{ width: number, height: number }> {
82 const videoStream = await getVideoStreamFromFile(path, existingProbe)
83
84 return videoStream === null
85 ? { width: 0, height: 0 }
86 : { width: videoStream.width, height: videoStream.height }
87}
88
89async function getVideoFileResolution (path: string, existingProbe?: FfprobeData) {
90 const size = await getVideoStreamSize(path, existingProbe)
91
92 return {
93 width: size.width,
94 height: size.height,
95 ratio: Math.max(size.height, size.width) / Math.min(size.height, size.width),
96 resolution: Math.min(size.height, size.width),
97 isPortraitMode: size.height > size.width
98 }
99}
100
101async function getVideoFileFPS (path: string, existingProbe?: FfprobeData) {
102 const videoStream = await getVideoStreamFromFile(path, existingProbe)
103 if (videoStream === null) return 0
104
105 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
106 const valuesText: string = videoStream[key]
107 if (!valuesText) continue
108
109 const [ frames, seconds ] = valuesText.split('/')
110 if (!frames || !seconds) continue
111
112 const result = parseInt(frames, 10) / parseInt(seconds, 10)
113 if (result > 0) return Math.round(result)
114 }
115
116 return 0
117}
118
119async function getMetadataFromFile (path: string, existingProbe?: FfprobeData) {
120 const metadata = existingProbe || await ffprobePromise(path)
121
122 return new VideoFileMetadata(metadata)
123}
124
125async function getVideoFileBitrate (path: string, existingProbe?: FfprobeData): Promise<number> {
126 const metadata = await getMetadataFromFile(path, existingProbe)
127
128 let bitrate = metadata.format.bit_rate as number
129 if (bitrate && !isNaN(bitrate)) return bitrate
130
131 const videoStream = await getVideoStreamFromFile(path, existingProbe)
132 if (!videoStream) return undefined
133
134 bitrate = videoStream?.bit_rate
135 if (bitrate && !isNaN(bitrate)) return bitrate
136
137 return undefined
138}
139
140async function getDurationFromVideoFile (path: string, existingProbe?: FfprobeData) {
141 const metadata = await getMetadataFromFile(path, existingProbe)
142
143 return Math.round(metadata.format.duration)
144}
145
146async function getVideoStreamFromFile (path: string, existingProbe?: FfprobeData) {
147 const metadata = await getMetadataFromFile(path, existingProbe)
148
149 return metadata.streams.find(s => s.codec_type === 'video') || null
150}
151
152async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
153 const parsedAudio = await getAudioStream(path, probe)
154
155 if (!parsedAudio.audioStream) return true
156
157 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
158
159 const audioBitrate = parsedAudio.bitrate
160 if (!audioBitrate) return false
161
162 const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
163 if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
164
165 const channelLayout = parsedAudio.audioStream['channel_layout']
166 // Causes playback issues with Chrome
167 if (!channelLayout || channelLayout === 'unknown') return false
168
169 return true
170}
171
172// ---------------------------------------------------------------------------
173
174export {
175 getVideoStreamSize,
176 getVideoFileResolution,
177 getMetadataFromFile,
178 getMaxAudioBitrate,
179 getVideoStreamFromFile,
180 getDurationFromVideoFile,
181 getAudioStream,
182 getVideoFileFPS,
183 isAudioFile,
184 ffprobePromise,
185 getVideoFileBitrate,
186 canDoQuickAudioTranscode
187}
diff --git a/shared/extra-utils/file.ts b/shared/extra-utils/file.ts
new file mode 100644
index 000000000..8060ab520
--- /dev/null
+++ b/shared/extra-utils/file.ts
@@ -0,0 +1,11 @@
1import { stat } from 'fs-extra'
2
3async function getFileSize (path: string) {
4 const stats = await stat(path)
5
6 return stats.size
7}
8
9export {
10 getFileSize
11}
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts
index 4b3636d06..e2e161a7b 100644
--- a/shared/extra-utils/index.ts
+++ b/shared/extra-utils/index.ts
@@ -1,15 +1,4 @@
1export * from './bulk' 1export * from './crypto'
2export * from './cli' 2export * from './ffprobe'
3export * from './custom-pages' 3export * from './file'
4export * from './feeds' 4export * from './uuid'
5export * from './logs'
6export * from './miscs'
7export * from './mock-servers'
8export * from './moderation'
9export * from './overviews'
10export * from './requests'
11export * from './search'
12export * from './server'
13export * from './socket'
14export * from './users'
15export * from './videos'
diff --git a/shared/extra-utils/miscs/checks.ts b/shared/extra-utils/miscs/checks.ts
deleted file mode 100644
index b1be214b1..000000000
--- a/shared/extra-utils/miscs/checks.ts
+++ /dev/null
@@ -1,58 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { expect } from 'chai'
4import { pathExists, readFile } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@server/helpers/core-utils'
7import { HttpStatusCode } from '@shared/models'
8import { makeGetRequest } from '../requests'
9import { PeerTubeServer } from '../server'
10
11// Default interval -> 5 minutes
12function dateIsValid (dateString: string, interval = 300000) {
13 const dateToCheck = new Date(dateString)
14 const now = new Date()
15
16 return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval
17}
18
19function expectStartWith (str: string, start: string) {
20 expect(str.startsWith(start), `${str} does not start with ${start}`).to.be.true
21}
22
23async function expectLogDoesNotContain (server: PeerTubeServer, str: string) {
24 const content = await server.servers.getLogContent()
25
26 expect(content.toString()).to.not.contain(str)
27}
28
29async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
30 const res = await makeGetRequest({
31 url,
32 path: imagePath,
33 expectedStatus: HttpStatusCode.OK_200
34 })
35
36 const body = res.body
37
38 const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension))
39 const minLength = body.length - ((30 * body.length) / 100)
40 const maxLength = body.length + ((30 * body.length) / 100)
41
42 expect(data.length).to.be.above(minLength, 'the generated image is way smaller than the recorded fixture')
43 expect(data.length).to.be.below(maxLength, 'the generated image is way larger than the recorded fixture')
44}
45
46async function testFileExistsOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) {
47 const base = server.servers.buildDirectory(directory)
48
49 expect(await pathExists(join(base, filePath))).to.equal(exist)
50}
51
52export {
53 dateIsValid,
54 testImage,
55 expectLogDoesNotContain,
56 testFileExistsOrNot,
57 expectStartWith
58}
diff --git a/shared/extra-utils/miscs/generate.ts b/shared/extra-utils/miscs/generate.ts
deleted file mode 100644
index 3b29c0ad4..000000000
--- a/shared/extra-utils/miscs/generate.ts
+++ /dev/null
@@ -1,75 +0,0 @@
1import { expect } from 'chai'
2import ffmpeg from 'fluent-ffmpeg'
3import { ensureDir, pathExists } from 'fs-extra'
4import { dirname } from 'path'
5import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils'
6import { getMaxBitrate } from '@shared/core-utils'
7import { buildAbsoluteFixturePath } from './tests'
8
9async function ensureHasTooBigBitrate (fixturePath: string) {
10 const bitrate = await getVideoFileBitrate(fixturePath)
11 const dataResolution = await getVideoFileResolution(fixturePath)
12 const fps = await getVideoFileFPS(fixturePath)
13
14 const maxBitrate = getMaxBitrate({ ...dataResolution, fps })
15 expect(bitrate).to.be.above(maxBitrate)
16}
17
18async function generateHighBitrateVideo () {
19 const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true)
20
21 await ensureDir(dirname(tempFixturePath))
22
23 const exists = await pathExists(tempFixturePath)
24 if (!exists) {
25 console.log('Generating high bitrate video.')
26
27 // Generate a random, high bitrate video on the fly, so we don't have to include
28 // a large file in the repo. The video needs to have a certain minimum length so
29 // that FFmpeg properly applies bitrate limits.
30 // https://stackoverflow.com/a/15795112
31 return new Promise<string>((res, rej) => {
32 ffmpeg()
33 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ])
34 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
35 .outputOptions([ '-maxrate 10M', '-bufsize 10M' ])
36 .output(tempFixturePath)
37 .on('error', rej)
38 .on('end', () => res(tempFixturePath))
39 .run()
40 })
41 }
42
43 await ensureHasTooBigBitrate(tempFixturePath)
44
45 return tempFixturePath
46}
47
48async function generateVideoWithFramerate (fps = 60) {
49 const tempFixturePath = buildAbsoluteFixturePath(`video_${fps}fps.mp4`, true)
50
51 await ensureDir(dirname(tempFixturePath))
52
53 const exists = await pathExists(tempFixturePath)
54 if (!exists) {
55 console.log('Generating video with framerate %d.', fps)
56
57 return new Promise<string>((res, rej) => {
58 ffmpeg()
59 .outputOptions([ '-f rawvideo', '-video_size 1280x720', '-i /dev/urandom' ])
60 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
61 .outputOptions([ `-r ${fps}` ])
62 .output(tempFixturePath)
63 .on('error', rej)
64 .on('end', () => res(tempFixturePath))
65 .run()
66 })
67 }
68
69 return tempFixturePath
70}
71
72export {
73 generateHighBitrateVideo,
74 generateVideoWithFramerate
75}
diff --git a/shared/extra-utils/miscs/index.ts b/shared/extra-utils/miscs/index.ts
deleted file mode 100644
index 4474661de..000000000
--- a/shared/extra-utils/miscs/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
1export * from './checks'
2export * from './generate'
3export * from './sql-command'
4export * from './tests'
5export * from './webtorrent'
diff --git a/shared/extra-utils/miscs/tests.ts b/shared/extra-utils/miscs/tests.ts
deleted file mode 100644
index 658fe5fd3..000000000
--- a/shared/extra-utils/miscs/tests.ts
+++ /dev/null
@@ -1,101 +0,0 @@
1import { stat } from 'fs-extra'
2import { basename, isAbsolute, join, resolve } from 'path'
3
4const FIXTURE_URLS = {
5 peertube_long: 'https://peertube2.cpy.re/videos/watch/122d093a-1ede-43bd-bd34-59d2931ffc5e',
6 peertube_short: 'https://peertube2.cpy.re/w/3fbif9S3WmtTP8gGsC5HBd',
7
8 youtube: 'https://www.youtube.com/watch?v=msX3jv1XdvM',
9
10 /**
11 * The video is used to check format-selection correctness wrt. HDR,
12 * which brings its own set of oddities outside of a MediaSource.
13 *
14 * The video needs to have the following format_ids:
15 * (which you can check by using `youtube-dl <url> -F`):
16 * - (webm vp9)
17 * - (mp4 avc1)
18 * - (webm vp9.2 HDR)
19 */
20 youtubeHDR: 'https://www.youtube.com/watch?v=RQgnBB9z_N4',
21
22 // eslint-disable-next-line max-len
23 magnet: 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Flazy-static%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4',
24
25 badVideo: 'https://download.cpy.re/peertube/bad_video.mp4',
26 goodVideo: 'https://download.cpy.re/peertube/good_video.mp4',
27 goodVideo720: 'https://download.cpy.re/peertube/good_video_720.mp4',
28
29 file4K: 'https://download.cpy.re/peertube/4k_file.txt'
30}
31
32function parallelTests () {
33 return process.env.MOCHA_PARALLEL === 'true'
34}
35
36function isGithubCI () {
37 return !!process.env.GITHUB_WORKSPACE
38}
39
40function areHttpImportTestsDisabled () {
41 const disabled = process.env.DISABLE_HTTP_IMPORT_TESTS === 'true'
42
43 if (disabled) console.log('DISABLE_HTTP_IMPORT_TESTS env set to "true" so import tests are disabled')
44
45 return disabled
46}
47
48function areObjectStorageTestsDisabled () {
49 const disabled = process.env.ENABLE_OBJECT_STORAGE_TESTS !== 'true'
50
51 if (disabled) console.log('ENABLE_OBJECT_STORAGE_TESTS env is not set to "true" so object storage tests are disabled')
52
53 return disabled
54}
55
56function buildAbsoluteFixturePath (path: string, customCIPath = false) {
57 if (isAbsolute(path)) return path
58
59 if (customCIPath && process.env.GITHUB_WORKSPACE) {
60 return join(process.env.GITHUB_WORKSPACE, 'fixtures', path)
61 }
62
63 return join(root(), 'server', 'tests', 'fixtures', path)
64}
65
66function root () {
67 // We are in /miscs
68 let root = join(__dirname, '..', '..', '..')
69
70 if (basename(root) === 'dist') root = resolve(root, '..')
71
72 return root
73}
74
75function wait (milliseconds: number) {
76 return new Promise(resolve => setTimeout(resolve, milliseconds))
77}
78
79async function getFileSize (path: string) {
80 const stats = await stat(path)
81
82 return stats.size
83}
84
85function buildRequestStub (): any {
86 return { }
87}
88
89export {
90 FIXTURE_URLS,
91
92 parallelTests,
93 isGithubCI,
94 areHttpImportTestsDisabled,
95 buildAbsoluteFixturePath,
96 getFileSize,
97 buildRequestStub,
98 areObjectStorageTestsDisabled,
99 wait,
100 root
101}
diff --git a/shared/extra-utils/mock-servers/index.ts b/shared/extra-utils/mock-servers/index.ts
deleted file mode 100644
index 93c00c788..000000000
--- a/shared/extra-utils/mock-servers/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
1export * from './mock-email'
2export * from './mock-instances-index'
3export * from './mock-joinpeertube-versions'
4export * from './mock-plugin-blocklist'
5export * from './mock-object-storage'
diff --git a/shared/extra-utils/mock-servers/mock-429.ts b/shared/extra-utils/mock-servers/mock-429.ts
deleted file mode 100644
index 9e0d1281a..000000000
--- a/shared/extra-utils/mock-servers/mock-429.ts
+++ /dev/null
@@ -1,33 +0,0 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './utils'
4
5export class Mock429 {
6 private server: Server
7 private responseSent = false
8
9 async initialize () {
10 const app = express()
11
12 app.get('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
13
14 if (!this.responseSent) {
15 this.responseSent = true
16
17 // Retry after 5 seconds
18 res.header('retry-after', '2')
19 return res.sendStatus(429)
20 }
21
22 return res.sendStatus(200)
23 })
24
25 this.server = await randomListen(app)
26
27 return getPort(this.server)
28 }
29
30 terminate () {
31 return terminateServer(this.server)
32 }
33}
diff --git a/shared/extra-utils/mock-servers/mock-email.ts b/shared/extra-utils/mock-servers/mock-email.ts
deleted file mode 100644
index f646c1621..000000000
--- a/shared/extra-utils/mock-servers/mock-email.ts
+++ /dev/null
@@ -1,63 +0,0 @@
1import { ChildProcess } from 'child_process'
2import MailDev from '@peertube/maildev'
3import { randomInt } from '@shared/core-utils'
4import { parallelTests } from '../miscs'
5
6class MockSmtpServer {
7
8 private static instance: MockSmtpServer
9 private started = false
10 private emailChildProcess: ChildProcess
11 private emails: object[]
12
13 private constructor () { }
14
15 collectEmails (emailsCollection: object[]) {
16 return new Promise<number>((res, rej) => {
17 const port = parallelTests() ? randomInt(1000, 2000) : 1025
18 this.emails = emailsCollection
19
20 if (this.started) {
21 return res(undefined)
22 }
23
24 const maildev = new MailDev({
25 ip: '127.0.0.1',
26 smtp: port,
27 disableWeb: true,
28 silent: true
29 })
30
31 maildev.on('new', email => {
32 this.emails.push(email)
33 })
34
35 maildev.listen(err => {
36 if (err) return rej(err)
37
38 this.started = true
39
40 return res(port)
41 })
42 })
43 }
44
45 kill () {
46 if (!this.emailChildProcess) return
47
48 process.kill(this.emailChildProcess.pid)
49
50 this.emailChildProcess = null
51 MockSmtpServer.instance = null
52 }
53
54 static get Instance () {
55 return this.instance || (this.instance = new this())
56 }
57}
58
59// ---------------------------------------------------------------------------
60
61export {
62 MockSmtpServer
63}
diff --git a/shared/extra-utils/mock-servers/mock-instances-index.ts b/shared/extra-utils/mock-servers/mock-instances-index.ts
deleted file mode 100644
index 92b12d6f3..000000000
--- a/shared/extra-utils/mock-servers/mock-instances-index.ts
+++ /dev/null
@@ -1,46 +0,0 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './utils'
4
5export class MockInstancesIndex {
6 private server: Server
7
8 private readonly indexInstances: { host: string, createdAt: string }[] = []
9
10 async initialize () {
11 const app = express()
12
13 app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
14 if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
15
16 return next()
17 })
18
19 app.get('/api/v1/instances/hosts', (req: express.Request, res: express.Response) => {
20 const since = req.query.since
21
22 const filtered = this.indexInstances.filter(i => {
23 if (!since) return true
24
25 return i.createdAt > since
26 })
27
28 return res.json({
29 total: filtered.length,
30 data: filtered
31 })
32 })
33
34 this.server = await randomListen(app)
35
36 return getPort(this.server)
37 }
38
39 addInstance (host: string) {
40 this.indexInstances.push({ host, createdAt: new Date().toISOString() })
41 }
42
43 terminate () {
44 return terminateServer(this.server)
45 }
46}
diff --git a/shared/extra-utils/mock-servers/mock-joinpeertube-versions.ts b/shared/extra-utils/mock-servers/mock-joinpeertube-versions.ts
deleted file mode 100644
index e7906ea56..000000000
--- a/shared/extra-utils/mock-servers/mock-joinpeertube-versions.ts
+++ /dev/null
@@ -1,34 +0,0 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen } from './utils'
4
5export class MockJoinPeerTubeVersions {
6 private server: Server
7 private latestVersion: string
8
9 async initialize () {
10 const app = express()
11
12 app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
14
15 return next()
16 })
17
18 app.get('/versions.json', (req: express.Request, res: express.Response) => {
19 return res.json({
20 peertube: {
21 latestVersion: this.latestVersion
22 }
23 })
24 })
25
26 this.server = await randomListen(app)
27
28 return getPort(this.server)
29 }
30
31 setLatestVersion (latestVersion: string) {
32 this.latestVersion = latestVersion
33 }
34}
diff --git a/shared/extra-utils/mock-servers/mock-object-storage.ts b/shared/extra-utils/mock-servers/mock-object-storage.ts
deleted file mode 100644
index d135c2631..000000000
--- a/shared/extra-utils/mock-servers/mock-object-storage.ts
+++ /dev/null
@@ -1,41 +0,0 @@
1import express from 'express'
2import got, { RequestError } from 'got'
3import { Server } from 'http'
4import { pipeline } from 'stream'
5import { ObjectStorageCommand } from '../server'
6import { getPort, randomListen, terminateServer } from './utils'
7
8export class MockObjectStorage {
9 private server: Server
10
11 async initialize () {
12 const app = express()
13
14 app.get('/:bucketName/:path(*)', (req: express.Request, res: express.Response, next: express.NextFunction) => {
15 const url = `http://${req.params.bucketName}.${ObjectStorageCommand.getEndpointHost()}/${req.params.path}`
16
17 if (process.env.DEBUG) {
18 console.log('Receiving request on mocked server %s.', req.url)
19 console.log('Proxifying request to %s', url)
20 }
21
22 return pipeline(
23 got.stream(url, { throwHttpErrors: false }),
24 res,
25 (err: RequestError) => {
26 if (!err) return
27
28 console.error('Pipeline failed.', err)
29 }
30 )
31 })
32
33 this.server = await randomListen(app)
34
35 return getPort(this.server)
36 }
37
38 terminate () {
39 return terminateServer(this.server)
40 }
41}
diff --git a/shared/extra-utils/mock-servers/mock-plugin-blocklist.ts b/shared/extra-utils/mock-servers/mock-plugin-blocklist.ts
deleted file mode 100644
index f8a271cba..000000000
--- a/shared/extra-utils/mock-servers/mock-plugin-blocklist.ts
+++ /dev/null
@@ -1,36 +0,0 @@
1import express, { Request, Response } from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './utils'
4
5type BlocklistResponse = {
6 data: {
7 value: string
8 action?: 'add' | 'remove'
9 updatedAt?: string
10 }[]
11}
12
13export class MockBlocklist {
14 private body: BlocklistResponse
15 private server: Server
16
17 async initialize () {
18 const app = express()
19
20 app.get('/blocklist', (req: Request, res: Response) => {
21 return res.json(this.body)
22 })
23
24 this.server = await randomListen(app)
25
26 return getPort(this.server)
27 }
28
29 replace (body: BlocklistResponse) {
30 this.body = body
31 }
32
33 terminate () {
34 return terminateServer(this.server)
35 }
36}
diff --git a/shared/extra-utils/mock-servers/mock-proxy.ts b/shared/extra-utils/mock-servers/mock-proxy.ts
deleted file mode 100644
index 75ac79055..000000000
--- a/shared/extra-utils/mock-servers/mock-proxy.ts
+++ /dev/null
@@ -1,25 +0,0 @@
1
2import { createServer, Server } from 'http'
3import proxy from 'proxy'
4import { getPort, terminateServer } from './utils'
5
6class MockProxy {
7 private server: Server
8
9 initialize () {
10 return new Promise<number>(res => {
11 this.server = proxy(createServer())
12 this.server.listen(0, () => res(getPort(this.server)))
13 })
14 }
15
16 terminate () {
17 return terminateServer(this.server)
18 }
19}
20
21// ---------------------------------------------------------------------------
22
23export {
24 MockProxy
25}
diff --git a/shared/extra-utils/mock-servers/utils.ts b/shared/extra-utils/mock-servers/utils.ts
deleted file mode 100644
index 235642439..000000000
--- a/shared/extra-utils/mock-servers/utils.ts
+++ /dev/null
@@ -1,33 +0,0 @@
1import { Express } from 'express'
2import { Server } from 'http'
3import { AddressInfo } from 'net'
4
5function randomListen (app: Express) {
6 return new Promise<Server>(res => {
7 const server = app.listen(0, () => res(server))
8 })
9}
10
11function getPort (server: Server) {
12 const address = server.address() as AddressInfo
13
14 return address.port
15}
16
17function terminateServer (server: Server) {
18 if (!server) return Promise.resolve()
19
20 return new Promise<void>((res, rej) => {
21 server.close(err => {
22 if (err) return rej(err)
23
24 return res()
25 })
26 })
27}
28
29export {
30 randomListen,
31 getPort,
32 terminateServer
33}
diff --git a/shared/extra-utils/requests/activitypub.ts b/shared/extra-utils/requests/activitypub.ts
deleted file mode 100644
index 4ae878384..000000000
--- a/shared/extra-utils/requests/activitypub.ts
+++ /dev/null
@@ -1,42 +0,0 @@
1import { activityPubContextify } from '../../../server/helpers/activitypub'
2import { doRequest } from '../../../server/helpers/requests'
3import { HTTP_SIGNATURE } from '../../../server/initializers/constants'
4import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils'
5
6function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) {
7 const options = {
8 method: 'POST' as 'POST',
9 json: body,
10 httpSignature,
11 headers
12 }
13
14 return doRequest(url, options)
15}
16
17async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
18 const follow = {
19 type: 'Follow',
20 id: by.url + '/' + new Date().getTime(),
21 actor: by.url,
22 object: to.url
23 }
24
25 const body = activityPubContextify(follow)
26
27 const httpSignature = {
28 algorithm: HTTP_SIGNATURE.ALGORITHM,
29 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
30 keyId: by.url,
31 key: by.privateKey,
32 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
33 }
34 const headers = buildGlobalHeaders(body)
35
36 return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers)
37}
38
39export {
40 makePOSTAPRequest,
41 makeFollowRequest
42}
diff --git a/shared/extra-utils/requests/check-api-params.ts b/shared/extra-utils/requests/check-api-params.ts
deleted file mode 100644
index 26ba1e913..000000000
--- a/shared/extra-utils/requests/check-api-params.ts
+++ /dev/null
@@ -1,48 +0,0 @@
1import { HttpStatusCode } from '@shared/models'
2import { makeGetRequest } from './requests'
3
4function checkBadStartPagination (url: string, path: string, token?: string, query = {}) {
5 return makeGetRequest({
6 url,
7 path,
8 token,
9 query: { ...query, start: 'hello' },
10 expectedStatus: HttpStatusCode.BAD_REQUEST_400
11 })
12}
13
14async function checkBadCountPagination (url: string, path: string, token?: string, query = {}) {
15 await makeGetRequest({
16 url,
17 path,
18 token,
19 query: { ...query, count: 'hello' },
20 expectedStatus: HttpStatusCode.BAD_REQUEST_400
21 })
22
23 await makeGetRequest({
24 url,
25 path,
26 token,
27 query: { ...query, count: 2000 },
28 expectedStatus: HttpStatusCode.BAD_REQUEST_400
29 })
30}
31
32function checkBadSortPagination (url: string, path: string, token?: string, query = {}) {
33 return makeGetRequest({
34 url,
35 path,
36 token,
37 query: { ...query, sort: 'hello' },
38 expectedStatus: HttpStatusCode.BAD_REQUEST_400
39 })
40}
41
42// ---------------------------------------------------------------------------
43
44export {
45 checkBadStartPagination,
46 checkBadCountPagination,
47 checkBadSortPagination
48}
diff --git a/shared/extra-utils/requests/index.ts b/shared/extra-utils/requests/index.ts
deleted file mode 100644
index 501163f92..000000000
--- a/shared/extra-utils/requests/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1// Don't include activitypub that import stuff from server
2export * from './check-api-params'
3export * from './requests'
diff --git a/shared/extra-utils/server/directories.ts b/shared/extra-utils/server/directories.ts
deleted file mode 100644
index b6465cbf4..000000000
--- a/shared/extra-utils/server/directories.ts
+++ /dev/null
@@ -1,34 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@server/helpers/core-utils'
7import { PeerTubeServer } from './server'
8
9async function checkTmpIsEmpty (server: PeerTubeServer) {
10 await checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css', 'hls', 'resumable-uploads' ])
11
12 if (await pathExists(join('test' + server.internalServerNumber, 'tmp', 'hls'))) {
13 await checkDirectoryIsEmpty(server, 'tmp/hls')
14 }
15}
16
17async function checkDirectoryIsEmpty (server: PeerTubeServer, directory: string, exceptions: string[] = []) {
18 const testDirectory = 'test' + server.internalServerNumber
19
20 const directoryPath = join(root(), testDirectory, directory)
21
22 const directoryExists = await pathExists(directoryPath)
23 expect(directoryExists).to.be.true
24
25 const files = await readdir(directoryPath)
26 const filtered = files.filter(f => exceptions.includes(f) === false)
27
28 expect(filtered).to.have.lengthOf(0)
29}
30
31export {
32 checkTmpIsEmpty,
33 checkDirectoryIsEmpty
34}
diff --git a/shared/extra-utils/server/plugins.ts b/shared/extra-utils/server/plugins.ts
deleted file mode 100644
index 0f5fabd5a..000000000
--- a/shared/extra-utils/server/plugins.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { PeerTubeServer } from '../server/server'
5
6async function testHelloWorldRegisteredSettings (server: PeerTubeServer) {
7 const body = await server.plugins.getRegisteredSettings({ npmName: 'peertube-plugin-hello-world' })
8
9 const registeredSettings = body.registeredSettings
10 expect(registeredSettings).to.have.length.at.least(1)
11
12 const adminNameSettings = registeredSettings.find(s => s.name === 'admin-name')
13 expect(adminNameSettings).to.not.be.undefined
14}
15
16export {
17 testHelloWorldRegisteredSettings
18}
diff --git a/shared/extra-utils/server/tracker.ts b/shared/extra-utils/server/tracker.ts
deleted file mode 100644
index f04e8f8a1..000000000
--- a/shared/extra-utils/server/tracker.ts
+++ /dev/null
@@ -1,27 +0,0 @@
1import { expect } from 'chai'
2import { sha1 } from '@server/helpers/core-utils'
3import { makeGetRequest } from '../requests'
4
5async function hlsInfohashExist (serverUrl: string, masterPlaylistUrl: string, fileNumber: number) {
6 const path = '/tracker/announce'
7
8 const infohash = sha1(`2${masterPlaylistUrl}+V${fileNumber}`)
9
10 // From bittorrent-tracker
11 const infohashBinary = escape(Buffer.from(infohash, 'hex').toString('binary')).replace(/[@*/+]/g, function (char) {
12 return '%' + char.charCodeAt(0).toString(16).toUpperCase()
13 })
14
15 const res = await makeGetRequest({
16 url: serverUrl,
17 path,
18 rawQuery: `peer_id=-WW0105-NkvYO/egUAr4&info_hash=${infohashBinary}&port=42100`,
19 expectedStatus: 200
20 })
21
22 expect(res.text).to.not.contain('failure')
23}
24
25export {
26 hlsInfohashExist
27}
diff --git a/shared/extra-utils/users/actors.ts b/shared/extra-utils/users/actors.ts
deleted file mode 100644
index cfcc7d0a7..000000000
--- a/shared/extra-utils/users/actors.ts
+++ /dev/null
@@ -1,73 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@server/helpers/core-utils'
7import { Account, VideoChannel } from '@shared/models'
8import { PeerTubeServer } from '../server'
9
10async function expectChannelsFollows (options: {
11 server: PeerTubeServer
12 handle: string
13 followers: number
14 following: number
15}) {
16 const { server } = options
17 const { data } = await server.channels.list()
18
19 return expectActorFollow({ ...options, data })
20}
21
22async function expectAccountFollows (options: {
23 server: PeerTubeServer
24 handle: string
25 followers: number
26 following: number
27}) {
28 const { server } = options
29 const { data } = await server.accounts.list()
30
31 return expectActorFollow({ ...options, data })
32}
33
34async function checkActorFilesWereRemoved (filename: string, serverNumber: number) {
35 const testDirectory = 'test' + serverNumber
36
37 for (const directory of [ 'avatars' ]) {
38 const directoryPath = join(root(), testDirectory, directory)
39
40 const directoryExists = await pathExists(directoryPath)
41 expect(directoryExists).to.be.true
42
43 const files = await readdir(directoryPath)
44 for (const file of files) {
45 expect(file).to.not.contain(filename)
46 }
47 }
48}
49
50export {
51 expectAccountFollows,
52 expectChannelsFollows,
53 checkActorFilesWereRemoved
54}
55
56// ---------------------------------------------------------------------------
57
58function expectActorFollow (options: {
59 server: PeerTubeServer
60 data: (Account | VideoChannel)[]
61 handle: string
62 followers: number
63 following: number
64}) {
65 const { server, data, handle, followers, following } = options
66
67 const actor = data.find(a => a.name + '@' + a.host === handle)
68 const message = `${handle} on ${server.url}`
69
70 expect(actor, message).to.exist
71 expect(actor.followersCount).to.equal(followers, message)
72 expect(actor.followingCount).to.equal(following, message)
73}
diff --git a/shared/extra-utils/users/notifications.ts b/shared/extra-utils/users/notifications.ts
deleted file mode 100644
index 07ccb0f8d..000000000
--- a/shared/extra-utils/users/notifications.ts
+++ /dev/null
@@ -1,795 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { inspect } from 'util'
5import { AbuseState, PluginType } from '@shared/models'
6import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
7import { MockSmtpServer } from '../mock-servers/mock-email'
8import { PeerTubeServer } from '../server'
9import { doubleFollow } from '../server/follows'
10import { createMultipleServers } from '../server/servers'
11import { setAccessTokensToServers } from './login'
12
13type CheckerBaseParams = {
14 server: PeerTubeServer
15 emails: any[]
16 socketNotifications: UserNotification[]
17 token: string
18 check?: { web: boolean, mail: boolean }
19}
20
21type CheckerType = 'presence' | 'absence'
22
23function getAllNotificationsSettings (): UserNotificationSetting {
24 return {
25 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
26 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
27 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
28 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
29 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
30 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
31 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
32 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
33 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
34 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
35 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
36 abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
37 abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
38 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
39 newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
40 newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
41 }
42}
43
44async function checkNewVideoFromSubscription (options: CheckerBaseParams & {
45 videoName: string
46 shortUUID: string
47 checkType: CheckerType
48}) {
49 const { videoName, shortUUID } = options
50 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
51
52 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
53 if (checkType === 'presence') {
54 expect(notification).to.not.be.undefined
55 expect(notification.type).to.equal(notificationType)
56
57 checkVideo(notification.video, videoName, shortUUID)
58 checkActor(notification.video.channel)
59 } else {
60 expect(notification).to.satisfy((n: UserNotification) => {
61 return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
62 })
63 }
64 }
65
66 function emailNotificationFinder (email: object) {
67 const text = email['text']
68 return text.indexOf(shortUUID) !== -1 && text.indexOf('Your subscription') !== -1
69 }
70
71 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
72}
73
74async function checkVideoIsPublished (options: CheckerBaseParams & {
75 videoName: string
76 shortUUID: string
77 checkType: CheckerType
78}) {
79 const { videoName, shortUUID } = options
80 const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
81
82 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
83 if (checkType === 'presence') {
84 expect(notification).to.not.be.undefined
85 expect(notification.type).to.equal(notificationType)
86
87 checkVideo(notification.video, videoName, shortUUID)
88 checkActor(notification.video.channel)
89 } else {
90 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
91 }
92 }
93
94 function emailNotificationFinder (email: object) {
95 const text: string = email['text']
96 return text.includes(shortUUID) && text.includes('Your video')
97 }
98
99 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
100}
101
102async function checkMyVideoImportIsFinished (options: CheckerBaseParams & {
103 videoName: string
104 shortUUID: string
105 url: string
106 success: boolean
107 checkType: CheckerType
108}) {
109 const { videoName, shortUUID, url, success } = options
110
111 const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
112
113 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
114 if (checkType === 'presence') {
115 expect(notification).to.not.be.undefined
116 expect(notification.type).to.equal(notificationType)
117
118 expect(notification.videoImport.targetUrl).to.equal(url)
119
120 if (success) checkVideo(notification.videoImport.video, videoName, shortUUID)
121 } else {
122 expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
123 }
124 }
125
126 function emailNotificationFinder (email: object) {
127 const text: string = email['text']
128 const toFind = success ? ' finished' : ' error'
129
130 return text.includes(url) && text.includes(toFind)
131 }
132
133 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
134}
135
136async function checkUserRegistered (options: CheckerBaseParams & {
137 username: string
138 checkType: CheckerType
139}) {
140 const { username } = options
141 const notificationType = UserNotificationType.NEW_USER_REGISTRATION
142
143 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
144 if (checkType === 'presence') {
145 expect(notification).to.not.be.undefined
146 expect(notification.type).to.equal(notificationType)
147
148 checkActor(notification.account)
149 expect(notification.account.name).to.equal(username)
150 } else {
151 expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
152 }
153 }
154
155 function emailNotificationFinder (email: object) {
156 const text: string = email['text']
157
158 return text.includes(' registered.') && text.includes(username)
159 }
160
161 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
162}
163
164async function checkNewActorFollow (options: CheckerBaseParams & {
165 followType: 'channel' | 'account'
166 followerName: string
167 followerDisplayName: string
168 followingDisplayName: string
169 checkType: CheckerType
170}) {
171 const { followType, followerName, followerDisplayName, followingDisplayName } = options
172 const notificationType = UserNotificationType.NEW_FOLLOW
173
174 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
175 if (checkType === 'presence') {
176 expect(notification).to.not.be.undefined
177 expect(notification.type).to.equal(notificationType)
178
179 checkActor(notification.actorFollow.follower)
180 expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
181 expect(notification.actorFollow.follower.name).to.equal(followerName)
182 expect(notification.actorFollow.follower.host).to.not.be.undefined
183
184 const following = notification.actorFollow.following
185 expect(following.displayName).to.equal(followingDisplayName)
186 expect(following.type).to.equal(followType)
187 } else {
188 expect(notification).to.satisfy(n => {
189 return n.type !== notificationType ||
190 (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
191 })
192 }
193 }
194
195 function emailNotificationFinder (email: object) {
196 const text: string = email['text']
197
198 return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
199 }
200
201 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
202}
203
204async function checkNewInstanceFollower (options: CheckerBaseParams & {
205 followerHost: string
206 checkType: CheckerType
207}) {
208 const { followerHost } = options
209 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
210
211 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
212 if (checkType === 'presence') {
213 expect(notification).to.not.be.undefined
214 expect(notification.type).to.equal(notificationType)
215
216 checkActor(notification.actorFollow.follower)
217 expect(notification.actorFollow.follower.name).to.equal('peertube')
218 expect(notification.actorFollow.follower.host).to.equal(followerHost)
219
220 expect(notification.actorFollow.following.name).to.equal('peertube')
221 } else {
222 expect(notification).to.satisfy(n => {
223 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
224 })
225 }
226 }
227
228 function emailNotificationFinder (email: object) {
229 const text: string = email['text']
230
231 return text.includes('instance has a new follower') && text.includes(followerHost)
232 }
233
234 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
235}
236
237async function checkAutoInstanceFollowing (options: CheckerBaseParams & {
238 followerHost: string
239 followingHost: string
240 checkType: CheckerType
241}) {
242 const { followerHost, followingHost } = options
243 const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
244
245 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
246 if (checkType === 'presence') {
247 expect(notification).to.not.be.undefined
248 expect(notification.type).to.equal(notificationType)
249
250 const following = notification.actorFollow.following
251 checkActor(following)
252 expect(following.name).to.equal('peertube')
253 expect(following.host).to.equal(followingHost)
254
255 expect(notification.actorFollow.follower.name).to.equal('peertube')
256 expect(notification.actorFollow.follower.host).to.equal(followerHost)
257 } else {
258 expect(notification).to.satisfy(n => {
259 return n.type !== notificationType || n.actorFollow.following.host !== followingHost
260 })
261 }
262 }
263
264 function emailNotificationFinder (email: object) {
265 const text: string = email['text']
266
267 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
268 }
269
270 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
271}
272
273async function checkCommentMention (options: CheckerBaseParams & {
274 shortUUID: string
275 commentId: number
276 threadId: number
277 byAccountDisplayName: string
278 checkType: CheckerType
279}) {
280 const { shortUUID, commentId, threadId, byAccountDisplayName } = options
281 const notificationType = UserNotificationType.COMMENT_MENTION
282
283 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
284 if (checkType === 'presence') {
285 expect(notification).to.not.be.undefined
286 expect(notification.type).to.equal(notificationType)
287
288 checkComment(notification.comment, commentId, threadId)
289 checkActor(notification.comment.account)
290 expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
291
292 checkVideo(notification.comment.video, undefined, shortUUID)
293 } else {
294 expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
295 }
296 }
297
298 function emailNotificationFinder (email: object) {
299 const text: string = email['text']
300
301 return text.includes(' mentioned ') && text.includes(shortUUID) && text.includes(byAccountDisplayName)
302 }
303
304 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
305}
306
307let lastEmailCount = 0
308
309async function checkNewCommentOnMyVideo (options: CheckerBaseParams & {
310 shortUUID: string
311 commentId: number
312 threadId: number
313 checkType: CheckerType
314}) {
315 const { server, shortUUID, commentId, threadId, checkType, emails } = options
316 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
317
318 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
319 if (checkType === 'presence') {
320 expect(notification).to.not.be.undefined
321 expect(notification.type).to.equal(notificationType)
322
323 checkComment(notification.comment, commentId, threadId)
324 checkActor(notification.comment.account)
325 checkVideo(notification.comment.video, undefined, shortUUID)
326 } else {
327 expect(notification).to.satisfy((n: UserNotification) => {
328 return n === undefined || n.comment === undefined || n.comment.id !== commentId
329 })
330 }
331 }
332
333 const commentUrl = `http://localhost:${server.port}/w/${shortUUID};threadId=${threadId}`
334
335 function emailNotificationFinder (email: object) {
336 return email['text'].indexOf(commentUrl) !== -1
337 }
338
339 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
340
341 if (checkType === 'presence') {
342 // We cannot detect email duplicates, so check we received another email
343 expect(emails).to.have.length.above(lastEmailCount)
344 lastEmailCount = emails.length
345 }
346}
347
348async function checkNewVideoAbuseForModerators (options: CheckerBaseParams & {
349 shortUUID: string
350 videoName: string
351 checkType: CheckerType
352}) {
353 const { shortUUID, videoName } = options
354 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
355
356 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
357 if (checkType === 'presence') {
358 expect(notification).to.not.be.undefined
359 expect(notification.type).to.equal(notificationType)
360
361 expect(notification.abuse.id).to.be.a('number')
362 checkVideo(notification.abuse.video, videoName, shortUUID)
363 } else {
364 expect(notification).to.satisfy((n: UserNotification) => {
365 return n === undefined || n.abuse === undefined || n.abuse.video.shortUUID !== shortUUID
366 })
367 }
368 }
369
370 function emailNotificationFinder (email: object) {
371 const text = email['text']
372 return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1
373 }
374
375 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
376}
377
378async function checkNewAbuseMessage (options: CheckerBaseParams & {
379 abuseId: number
380 message: string
381 toEmail: string
382 checkType: CheckerType
383}) {
384 const { abuseId, message, toEmail } = options
385 const notificationType = UserNotificationType.ABUSE_NEW_MESSAGE
386
387 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
388 if (checkType === 'presence') {
389 expect(notification).to.not.be.undefined
390 expect(notification.type).to.equal(notificationType)
391
392 expect(notification.abuse.id).to.equal(abuseId)
393 } else {
394 expect(notification).to.satisfy((n: UserNotification) => {
395 return n === undefined || n.type !== notificationType || n.abuse === undefined || n.abuse.id !== abuseId
396 })
397 }
398 }
399
400 function emailNotificationFinder (email: object) {
401 const text = email['text']
402 const to = email['to'].filter(t => t.address === toEmail)
403
404 return text.indexOf(message) !== -1 && to.length !== 0
405 }
406
407 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
408}
409
410async function checkAbuseStateChange (options: CheckerBaseParams & {
411 abuseId: number
412 state: AbuseState
413 checkType: CheckerType
414}) {
415 const { abuseId, state } = options
416 const notificationType = UserNotificationType.ABUSE_STATE_CHANGE
417
418 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
419 if (checkType === 'presence') {
420 expect(notification).to.not.be.undefined
421 expect(notification.type).to.equal(notificationType)
422
423 expect(notification.abuse.id).to.equal(abuseId)
424 expect(notification.abuse.state).to.equal(state)
425 } else {
426 expect(notification).to.satisfy((n: UserNotification) => {
427 return n === undefined || n.abuse === undefined || n.abuse.id !== abuseId
428 })
429 }
430 }
431
432 function emailNotificationFinder (email: object) {
433 const text = email['text']
434
435 const contains = state === AbuseState.ACCEPTED
436 ? ' accepted'
437 : ' rejected'
438
439 return text.indexOf(contains) !== -1
440 }
441
442 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
443}
444
445async function checkNewCommentAbuseForModerators (options: CheckerBaseParams & {
446 shortUUID: string
447 videoName: string
448 checkType: CheckerType
449}) {
450 const { shortUUID, videoName } = options
451 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
452
453 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
454 if (checkType === 'presence') {
455 expect(notification).to.not.be.undefined
456 expect(notification.type).to.equal(notificationType)
457
458 expect(notification.abuse.id).to.be.a('number')
459 checkVideo(notification.abuse.comment.video, videoName, shortUUID)
460 } else {
461 expect(notification).to.satisfy((n: UserNotification) => {
462 return n === undefined || n.abuse === undefined || n.abuse.comment.video.shortUUID !== shortUUID
463 })
464 }
465 }
466
467 function emailNotificationFinder (email: object) {
468 const text = email['text']
469 return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1
470 }
471
472 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
473}
474
475async function checkNewAccountAbuseForModerators (options: CheckerBaseParams & {
476 displayName: string
477 checkType: CheckerType
478}) {
479 const { displayName } = options
480 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
481
482 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
483 if (checkType === 'presence') {
484 expect(notification).to.not.be.undefined
485 expect(notification.type).to.equal(notificationType)
486
487 expect(notification.abuse.id).to.be.a('number')
488 expect(notification.abuse.account.displayName).to.equal(displayName)
489 } else {
490 expect(notification).to.satisfy((n: UserNotification) => {
491 return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
492 })
493 }
494 }
495
496 function emailNotificationFinder (email: object) {
497 const text = email['text']
498 return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
499 }
500
501 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
502}
503
504async function checkVideoAutoBlacklistForModerators (options: CheckerBaseParams & {
505 shortUUID: string
506 videoName: string
507 checkType: CheckerType
508}) {
509 const { shortUUID, videoName } = options
510 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
511
512 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
513 if (checkType === 'presence') {
514 expect(notification).to.not.be.undefined
515 expect(notification.type).to.equal(notificationType)
516
517 expect(notification.videoBlacklist.video.id).to.be.a('number')
518 checkVideo(notification.videoBlacklist.video, videoName, shortUUID)
519 } else {
520 expect(notification).to.satisfy((n: UserNotification) => {
521 return n === undefined || n.video === undefined || n.video.shortUUID !== shortUUID
522 })
523 }
524 }
525
526 function emailNotificationFinder (email: object) {
527 const text = email['text']
528 return text.indexOf(shortUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
529 }
530
531 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
532}
533
534async function checkNewBlacklistOnMyVideo (options: CheckerBaseParams & {
535 shortUUID: string
536 videoName: string
537 blacklistType: 'blacklist' | 'unblacklist'
538}) {
539 const { videoName, shortUUID, blacklistType } = options
540 const notificationType = blacklistType === 'blacklist'
541 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
542 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
543
544 function notificationChecker (notification: UserNotification) {
545 expect(notification).to.not.be.undefined
546 expect(notification.type).to.equal(notificationType)
547
548 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
549
550 checkVideo(video, videoName, shortUUID)
551 }
552
553 function emailNotificationFinder (email: object) {
554 const text = email['text']
555 const blacklistText = blacklistType === 'blacklist'
556 ? 'blacklisted'
557 : 'unblacklisted'
558
559 return text.includes(shortUUID) && text.includes(blacklistText)
560 }
561
562 await checkNotification({ ...options, notificationChecker, emailNotificationFinder, checkType: 'presence' })
563}
564
565async function checkNewPeerTubeVersion (options: CheckerBaseParams & {
566 latestVersion: string
567 checkType: CheckerType
568}) {
569 const { latestVersion } = options
570 const notificationType = UserNotificationType.NEW_PEERTUBE_VERSION
571
572 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
573 if (checkType === 'presence') {
574 expect(notification).to.not.be.undefined
575 expect(notification.type).to.equal(notificationType)
576
577 expect(notification.peertube).to.exist
578 expect(notification.peertube.latestVersion).to.equal(latestVersion)
579 } else {
580 expect(notification).to.satisfy((n: UserNotification) => {
581 return n === undefined || n.peertube === undefined || n.peertube.latestVersion !== latestVersion
582 })
583 }
584 }
585
586 function emailNotificationFinder (email: object) {
587 const text = email['text']
588
589 return text.includes(latestVersion)
590 }
591
592 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
593}
594
595async function checkNewPluginVersion (options: CheckerBaseParams & {
596 pluginType: PluginType
597 pluginName: string
598 checkType: CheckerType
599}) {
600 const { pluginName, pluginType } = options
601 const notificationType = UserNotificationType.NEW_PLUGIN_VERSION
602
603 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
604 if (checkType === 'presence') {
605 expect(notification).to.not.be.undefined
606 expect(notification.type).to.equal(notificationType)
607
608 expect(notification.plugin.name).to.equal(pluginName)
609 expect(notification.plugin.type).to.equal(pluginType)
610 } else {
611 expect(notification).to.satisfy((n: UserNotification) => {
612 return n === undefined || n.plugin === undefined || n.plugin.name !== pluginName
613 })
614 }
615 }
616
617 function emailNotificationFinder (email: object) {
618 const text = email['text']
619
620 return text.includes(pluginName)
621 }
622
623 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
624}
625
626async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) {
627 const userNotifications: UserNotification[] = []
628 const adminNotifications: UserNotification[] = []
629 const adminNotificationsServer2: UserNotification[] = []
630 const emails: object[] = []
631
632 const port = await MockSmtpServer.Instance.collectEmails(emails)
633
634 const overrideConfig = {
635 smtp: {
636 hostname: 'localhost',
637 port
638 },
639 signup: {
640 limit: 20
641 }
642 }
643 const servers = await createMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg))
644
645 await setAccessTokensToServers(servers)
646
647 if (serversCount > 1) {
648 await doubleFollow(servers[0], servers[1])
649 }
650
651 const user = { username: 'user_1', password: 'super password' }
652 await servers[0].users.create({ ...user, videoQuota: 10 * 1000 * 1000 })
653 const userAccessToken = await servers[0].login.getAccessToken(user)
654
655 await servers[0].notifications.updateMySettings({ token: userAccessToken, settings: getAllNotificationsSettings() })
656 await servers[0].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
657
658 if (serversCount > 1) {
659 await servers[1].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
660 }
661
662 {
663 const socket = servers[0].socketIO.getUserNotificationSocket({ token: userAccessToken })
664 socket.on('new-notification', n => userNotifications.push(n))
665 }
666 {
667 const socket = servers[0].socketIO.getUserNotificationSocket()
668 socket.on('new-notification', n => adminNotifications.push(n))
669 }
670
671 if (serversCount > 1) {
672 const socket = servers[1].socketIO.getUserNotificationSocket()
673 socket.on('new-notification', n => adminNotificationsServer2.push(n))
674 }
675
676 const { videoChannels } = await servers[0].users.getMyInfo()
677 const channelId = videoChannels[0].id
678
679 return {
680 userNotifications,
681 adminNotifications,
682 adminNotificationsServer2,
683 userAccessToken,
684 emails,
685 servers,
686 channelId
687 }
688}
689
690// ---------------------------------------------------------------------------
691
692export {
693 getAllNotificationsSettings,
694
695 CheckerBaseParams,
696 CheckerType,
697 checkMyVideoImportIsFinished,
698 checkUserRegistered,
699 checkAutoInstanceFollowing,
700 checkVideoIsPublished,
701 checkNewVideoFromSubscription,
702 checkNewActorFollow,
703 checkNewCommentOnMyVideo,
704 checkNewBlacklistOnMyVideo,
705 checkCommentMention,
706 checkNewVideoAbuseForModerators,
707 checkVideoAutoBlacklistForModerators,
708 checkNewAbuseMessage,
709 checkAbuseStateChange,
710 checkNewInstanceFollower,
711 prepareNotificationsTest,
712 checkNewCommentAbuseForModerators,
713 checkNewAccountAbuseForModerators,
714 checkNewPeerTubeVersion,
715 checkNewPluginVersion
716}
717
718// ---------------------------------------------------------------------------
719
720async function checkNotification (options: CheckerBaseParams & {
721 notificationChecker: (notification: UserNotification, checkType: CheckerType) => void
722 emailNotificationFinder: (email: object) => boolean
723 checkType: CheckerType
724}) {
725 const { server, token, checkType, notificationChecker, emailNotificationFinder, socketNotifications, emails } = options
726
727 const check = options.check || { web: true, mail: true }
728
729 if (check.web) {
730 const notification = await server.notifications.getLatest({ token: token })
731
732 if (notification || checkType !== 'absence') {
733 notificationChecker(notification, checkType)
734 }
735
736 const socketNotification = socketNotifications.find(n => {
737 try {
738 notificationChecker(n, 'presence')
739 return true
740 } catch {
741 return false
742 }
743 })
744
745 if (checkType === 'presence') {
746 const obj = inspect(socketNotifications, { depth: 5 })
747 expect(socketNotification, 'The socket notification is absent when it should be present. ' + obj).to.not.be.undefined
748 } else {
749 const obj = inspect(socketNotification, { depth: 5 })
750 expect(socketNotification, 'The socket notification is present when it should not be present. ' + obj).to.be.undefined
751 }
752 }
753
754 if (check.mail) {
755 // Last email
756 const email = emails
757 .slice()
758 .reverse()
759 .find(e => emailNotificationFinder(e))
760
761 if (checkType === 'presence') {
762 const texts = emails.map(e => e.text)
763 expect(email, 'The email is absent when is should be present. ' + inspect(texts)).to.not.be.undefined
764 } else {
765 expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
766 }
767 }
768}
769
770function checkVideo (video: any, videoName?: string, shortUUID?: string) {
771 if (videoName) {
772 expect(video.name).to.be.a('string')
773 expect(video.name).to.not.be.empty
774 expect(video.name).to.equal(videoName)
775 }
776
777 if (shortUUID) {
778 expect(video.shortUUID).to.be.a('string')
779 expect(video.shortUUID).to.not.be.empty
780 expect(video.shortUUID).to.equal(shortUUID)
781 }
782
783 expect(video.id).to.be.a('number')
784}
785
786function checkActor (actor: any) {
787 expect(actor.displayName).to.be.a('string')
788 expect(actor.displayName).to.not.be.empty
789 expect(actor.host).to.not.be.undefined
790}
791
792function checkComment (comment: any, commentId: number, threadId: number) {
793 expect(comment.id).to.equal(commentId)
794 expect(comment.threadId).to.equal(threadId)
795}
diff --git a/shared/extra-utils/uuid.ts b/shared/extra-utils/uuid.ts
new file mode 100644
index 000000000..f3c80e046
--- /dev/null
+++ b/shared/extra-utils/uuid.ts
@@ -0,0 +1,32 @@
1import short, { uuid } from 'short-uuid'
2
3const translator = short()
4
5function buildUUID () {
6 return uuid()
7}
8
9function uuidToShort (uuid: string) {
10 if (!uuid) return uuid
11
12 return translator.fromUUID(uuid)
13}
14
15function shortToUUID (shortUUID: string) {
16 if (!shortUUID) return shortUUID
17
18 return translator.toUUID(shortUUID)
19}
20
21function isShortUUID (value: string) {
22 if (!value) return false
23
24 return value.length === translator.maxLength
25}
26
27export {
28 buildUUID,
29 uuidToShort,
30 shortToUUID,
31 isShortUUID
32}
diff --git a/shared/extra-utils/videos/captions.ts b/shared/extra-utils/videos/captions.ts
deleted file mode 100644
index 35e722408..000000000
--- a/shared/extra-utils/videos/captions.ts
+++ /dev/null
@@ -1,21 +0,0 @@
1import { expect } from 'chai'
2import request from 'supertest'
3import { HttpStatusCode } from '@shared/models'
4
5async function testCaptionFile (url: string, captionPath: string, toTest: RegExp | string) {
6 const res = await request(url)
7 .get(captionPath)
8 .expect(HttpStatusCode.OK_200)
9
10 if (toTest instanceof RegExp) {
11 expect(res.text).to.match(toTest)
12 } else {
13 expect(res.text).to.contain(toTest)
14 }
15}
16
17// ---------------------------------------------------------------------------
18
19export {
20 testCaptionFile
21}
diff --git a/shared/extra-utils/videos/playlists.ts b/shared/extra-utils/videos/playlists.ts
deleted file mode 100644
index 3dde52bb9..000000000
--- a/shared/extra-utils/videos/playlists.ts
+++ /dev/null
@@ -1,25 +0,0 @@
1import { expect } from 'chai'
2import { readdir } from 'fs-extra'
3import { join } from 'path'
4import { root } from '../miscs'
5
6async function checkPlaylistFilesWereRemoved (
7 playlistUUID: string,
8 internalServerNumber: number,
9 directories = [ 'thumbnails' ]
10) {
11 const testDirectory = 'test' + internalServerNumber
12
13 for (const directory of directories) {
14 const directoryPath = join(root(), testDirectory, directory)
15
16 const files = await readdir(directoryPath)
17 for (const file of files) {
18 expect(file).to.not.contain(playlistUUID)
19 }
20 }
21}
22
23export {
24 checkPlaylistFilesWereRemoved
25}
diff --git a/shared/extra-utils/videos/streaming-playlists.ts b/shared/extra-utils/videos/streaming-playlists.ts
deleted file mode 100644
index 6671e3fa6..000000000
--- a/shared/extra-utils/videos/streaming-playlists.ts
+++ /dev/null
@@ -1,77 +0,0 @@
1import { expect } from 'chai'
2import { basename } from 'path'
3import { sha256 } from '@server/helpers/core-utils'
4import { removeFragmentedMP4Ext } from '@shared/core-utils'
5import { HttpStatusCode, VideoStreamingPlaylist } from '@shared/models'
6import { PeerTubeServer } from '../server'
7
8async function checkSegmentHash (options: {
9 server: PeerTubeServer
10 baseUrlPlaylist: string
11 baseUrlSegment: string
12 resolution: number
13 hlsPlaylist: VideoStreamingPlaylist
14}) {
15 const { server, baseUrlPlaylist, baseUrlSegment, resolution, hlsPlaylist } = options
16 const command = server.streamingPlaylists
17
18 const file = hlsPlaylist.files.find(f => f.resolution.id === resolution)
19 const videoName = basename(file.fileUrl)
20
21 const playlist = await command.get({ url: `${baseUrlPlaylist}/${removeFragmentedMP4Ext(videoName)}.m3u8` })
22
23 const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist)
24
25 const length = parseInt(matches[1], 10)
26 const offset = parseInt(matches[2], 10)
27 const range = `${offset}-${offset + length - 1}`
28
29 const segmentBody = await command.getSegment({
30 url: `${baseUrlSegment}/${videoName}`,
31 expectedStatus: HttpStatusCode.PARTIAL_CONTENT_206,
32 range: `bytes=${range}`
33 })
34
35 const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
36 expect(sha256(segmentBody)).to.equal(shaBody[videoName][range])
37}
38
39async function checkLiveSegmentHash (options: {
40 server: PeerTubeServer
41 baseUrlSegment: string
42 videoUUID: string
43 segmentName: string
44 hlsPlaylist: VideoStreamingPlaylist
45}) {
46 const { server, baseUrlSegment, videoUUID, segmentName, hlsPlaylist } = options
47 const command = server.streamingPlaylists
48
49 const segmentBody = await command.getSegment({ url: `${baseUrlSegment}/${videoUUID}/${segmentName}` })
50 const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
51
52 expect(sha256(segmentBody)).to.equal(shaBody[segmentName])
53}
54
55async function checkResolutionsInMasterPlaylist (options: {
56 server: PeerTubeServer
57 playlistUrl: string
58 resolutions: number[]
59}) {
60 const { server, playlistUrl, resolutions } = options
61
62 const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl })
63
64 for (const resolution of resolutions) {
65 const reg = new RegExp(
66 '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"'
67 )
68
69 expect(masterPlaylist).to.match(reg)
70 }
71}
72
73export {
74 checkSegmentHash,
75 checkLiveSegmentHash,
76 checkResolutionsInMasterPlaylist
77}
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
deleted file mode 100644
index 4d2784dde..000000000
--- a/shared/extra-utils/videos/videos.ts
+++ /dev/null
@@ -1,253 +0,0 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { basename, join } from 'path'
6import { getLowercaseExtension } from '@server/helpers/core-utils'
7import { uuidRegex } from '@shared/core-utils'
8import { HttpStatusCode, VideoCaption, VideoDetails } from '@shared/models'
9import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
10import { dateIsValid, testImage, webtorrentAdd } from '../miscs'
11import { makeRawRequest } from '../requests/requests'
12import { waitJobs } from '../server'
13import { PeerTubeServer } from '../server/server'
14import { VideoEdit } from './videos-command'
15
16async function checkVideoFilesWereRemoved (options: {
17 server: PeerTubeServer
18 video: VideoDetails
19 captions?: VideoCaption[]
20 onlyVideoFiles?: boolean // default false
21}) {
22 const { video, server, captions = [], onlyVideoFiles = false } = options
23
24 const webtorrentFiles = video.files || []
25 const hlsFiles = video.streamingPlaylists[0]?.files || []
26
27 const thumbnailName = basename(video.thumbnailPath)
28 const previewName = basename(video.previewPath)
29
30 const torrentNames = webtorrentFiles.concat(hlsFiles).map(f => basename(f.torrentUrl))
31
32 const captionNames = captions.map(c => basename(c.captionPath))
33
34 const webtorrentFilenames = webtorrentFiles.map(f => basename(f.fileUrl))
35 const hlsFilenames = hlsFiles.map(f => basename(f.fileUrl))
36
37 let directories: { [ directory: string ]: string[] } = {
38 videos: webtorrentFilenames,
39 redundancy: webtorrentFilenames,
40 [join('playlists', 'hls')]: hlsFilenames,
41 [join('redundancy', 'hls')]: hlsFilenames
42 }
43
44 if (onlyVideoFiles !== true) {
45 directories = {
46 ...directories,
47
48 thumbnails: [ thumbnailName ],
49 previews: [ previewName ],
50 torrents: torrentNames,
51 captions: captionNames
52 }
53 }
54
55 for (const directory of Object.keys(directories)) {
56 const directoryPath = server.servers.buildDirectory(directory)
57
58 const directoryExists = await pathExists(directoryPath)
59 if (directoryExists === false) continue
60
61 const existingFiles = await readdir(directoryPath)
62 for (const existingFile of existingFiles) {
63 for (const shouldNotExist of directories[directory]) {
64 expect(existingFile, `File ${existingFile} should not exist in ${directoryPath}`).to.not.contain(shouldNotExist)
65 }
66 }
67 }
68}
69
70async function saveVideoInServers (servers: PeerTubeServer[], uuid: string) {
71 for (const server of servers) {
72 server.store.videoDetails = await server.videos.get({ id: uuid })
73 }
74}
75
76function checkUploadVideoParam (
77 server: PeerTubeServer,
78 token: string,
79 attributes: Partial<VideoEdit>,
80 expectedStatus = HttpStatusCode.OK_200,
81 mode: 'legacy' | 'resumable' = 'legacy'
82) {
83 return mode === 'legacy'
84 ? server.videos.buildLegacyUpload({ token, attributes, expectedStatus })
85 : server.videos.buildResumeUpload({ token, attributes, expectedStatus })
86}
87
88async function completeVideoCheck (
89 server: PeerTubeServer,
90 video: any,
91 attributes: {
92 name: string
93 category: number
94 licence: number
95 language: string
96 nsfw: boolean
97 commentsEnabled: boolean
98 downloadEnabled: boolean
99 description: string
100 publishedAt?: string
101 support: string
102 originallyPublishedAt?: string
103 account: {
104 name: string
105 host: string
106 }
107 isLocal: boolean
108 tags: string[]
109 privacy: number
110 likes?: number
111 dislikes?: number
112 duration: number
113 channel: {
114 displayName: string
115 name: string
116 description: string
117 isLocal: boolean
118 }
119 fixture: string
120 files: {
121 resolution: number
122 size: number
123 }[]
124 thumbnailfile?: string
125 previewfile?: string
126 }
127) {
128 if (!attributes.likes) attributes.likes = 0
129 if (!attributes.dislikes) attributes.dislikes = 0
130
131 const host = new URL(server.url).host
132 const originHost = attributes.account.host
133
134 expect(video.name).to.equal(attributes.name)
135 expect(video.category.id).to.equal(attributes.category)
136 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
137 expect(video.licence.id).to.equal(attributes.licence)
138 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
139 expect(video.language.id).to.equal(attributes.language)
140 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
141 expect(video.privacy.id).to.deep.equal(attributes.privacy)
142 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
143 expect(video.nsfw).to.equal(attributes.nsfw)
144 expect(video.description).to.equal(attributes.description)
145 expect(video.account.id).to.be.a('number')
146 expect(video.account.host).to.equal(attributes.account.host)
147 expect(video.account.name).to.equal(attributes.account.name)
148 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
149 expect(video.channel.name).to.equal(attributes.channel.name)
150 expect(video.likes).to.equal(attributes.likes)
151 expect(video.dislikes).to.equal(attributes.dislikes)
152 expect(video.isLocal).to.equal(attributes.isLocal)
153 expect(video.duration).to.equal(attributes.duration)
154 expect(video.url).to.contain(originHost)
155 expect(dateIsValid(video.createdAt)).to.be.true
156 expect(dateIsValid(video.publishedAt)).to.be.true
157 expect(dateIsValid(video.updatedAt)).to.be.true
158
159 if (attributes.publishedAt) {
160 expect(video.publishedAt).to.equal(attributes.publishedAt)
161 }
162
163 if (attributes.originallyPublishedAt) {
164 expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt)
165 } else {
166 expect(video.originallyPublishedAt).to.be.null
167 }
168
169 const videoDetails = await server.videos.get({ id: video.uuid })
170
171 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
172 expect(videoDetails.tags).to.deep.equal(attributes.tags)
173 expect(videoDetails.account.name).to.equal(attributes.account.name)
174 expect(videoDetails.account.host).to.equal(attributes.account.host)
175 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
176 expect(video.channel.name).to.equal(attributes.channel.name)
177 expect(videoDetails.channel.host).to.equal(attributes.account.host)
178 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
179 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
180 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
181 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
182 expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled)
183
184 for (const attributeFile of attributes.files) {
185 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
186 expect(file).not.to.be.undefined
187
188 let extension = getLowercaseExtension(attributes.fixture)
189 // Transcoding enabled: extension will always be .mp4
190 if (attributes.files.length > 1) extension = '.mp4'
191
192 expect(file.magnetUri).to.have.lengthOf.above(2)
193
194 expect(file.torrentDownloadUrl).to.match(new RegExp(`http://${host}/download/torrents/${uuidRegex}-${file.resolution.id}.torrent`))
195 expect(file.torrentUrl).to.match(new RegExp(`http://${host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}.torrent`))
196
197 expect(file.fileUrl).to.match(new RegExp(`http://${originHost}/static/webseed/${uuidRegex}-${file.resolution.id}${extension}`))
198 expect(file.fileDownloadUrl).to.match(new RegExp(`http://${originHost}/download/videos/${uuidRegex}-${file.resolution.id}${extension}`))
199
200 await Promise.all([
201 makeRawRequest(file.torrentUrl, 200),
202 makeRawRequest(file.torrentDownloadUrl, 200),
203 makeRawRequest(file.metadataUrl, 200)
204 ])
205
206 expect(file.resolution.id).to.equal(attributeFile.resolution)
207 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
208
209 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
210 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
211 expect(
212 file.size,
213 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')'
214 ).to.be.above(minSize).and.below(maxSize)
215
216 const torrent = await webtorrentAdd(file.magnetUri, true)
217 expect(torrent.files).to.be.an('array')
218 expect(torrent.files.length).to.equal(1)
219 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
220 }
221
222 expect(videoDetails.thumbnailPath).to.exist
223 await testImage(server.url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
224
225 if (attributes.previewfile) {
226 expect(videoDetails.previewPath).to.exist
227 await testImage(server.url, attributes.previewfile, videoDetails.previewPath)
228 }
229}
230
231// serverNumber starts from 1
232async function uploadRandomVideoOnServers (
233 servers: PeerTubeServer[],
234 serverNumber: number,
235 additionalParams?: VideoEdit & { prefixName?: string }
236) {
237 const server = servers.find(s => s.serverNumber === serverNumber)
238 const res = await server.videos.randomUpload({ wait: false, additionalParams })
239
240 await waitJobs(servers)
241
242 return res
243}
244
245// ---------------------------------------------------------------------------
246
247export {
248 checkUploadVideoParam,
249 completeVideoCheck,
250 uploadRandomVideoOnServers,
251 checkVideoFilesWereRemoved,
252 saveVideoInServers
253}
diff --git a/shared/index.ts b/shared/index.ts
deleted file mode 100644
index ad200c539..000000000
--- a/shared/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './models'
diff --git a/shared/models/http/http-error-codes.ts b/shared/models/http/http-error-codes.ts
index b2fbdfc5a..5ebff1cb5 100644
--- a/shared/models/http/http-error-codes.ts
+++ b/shared/models/http/http-error-codes.ts
@@ -4,7 +4,7 @@
4 * 4 *
5 * WebDAV and other codes useless with regards to PeerTube are not listed. 5 * WebDAV and other codes useless with regards to PeerTube are not listed.
6 */ 6 */
7export enum HttpStatusCode { 7export const enum HttpStatusCode {
8 8
9 /** 9 /**
10 * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1 10 * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1
diff --git a/shared/models/http/http-methods.ts b/shared/models/http/http-methods.ts
index 1cfa458b9..3f4adafe2 100644
--- a/shared/models/http/http-methods.ts
+++ b/shared/models/http/http-methods.ts
@@ -1,5 +1,5 @@
1/** HTTP request method to indicate the desired action to be performed for a given resource. */ 1/** HTTP request method to indicate the desired action to be performed for a given resource. */
2export enum HttpMethod { 2export const enum HttpMethod {
3 /** The CONNECT method establishes a tunnel to the server identified by the target resource. */ 3 /** The CONNECT method establishes a tunnel to the server identified by the target resource. */
4 CONNECT = 'CONNECT', 4 CONNECT = 'CONNECT',
5 /** The DELETE method deletes the specified resource. */ 5 /** The DELETE method deletes the specified resource. */
diff --git a/shared/models/moderation/block-status.model.ts b/shared/models/moderation/block-status.model.ts
new file mode 100644
index 000000000..597312757
--- /dev/null
+++ b/shared/models/moderation/block-status.model.ts
@@ -0,0 +1,15 @@
1export interface BlockStatus {
2 accounts: {
3 [ handle: string ]: {
4 blockedByServer: boolean
5 blockedByUser?: boolean
6 }
7 }
8
9 hosts: {
10 [ host: string ]: {
11 blockedByServer: boolean
12 blockedByUser?: boolean
13 }
14 }
15}
diff --git a/shared/models/moderation/index.ts b/shared/models/moderation/index.ts
index 8b6042e97..f8e6d351c 100644
--- a/shared/models/moderation/index.ts
+++ b/shared/models/moderation/index.ts
@@ -1,3 +1,4 @@
1export * from './abuse' 1export * from './abuse'
2export * from './block-status.model'
2export * from './account-block.model' 3export * from './account-block.model'
3export * from './server-block.model' 4export * from './server-block.model'
diff --git a/shared/models/plugins/client/index.ts b/shared/models/plugins/client/index.ts
index c500185c9..f3e3fcbcf 100644
--- a/shared/models/plugins/client/index.ts
+++ b/shared/models/plugins/client/index.ts
@@ -4,4 +4,5 @@ export * from './plugin-element-placeholder.type'
4export * from './plugin-selector-id.type' 4export * from './plugin-selector-id.type'
5export * from './register-client-form-field.model' 5export * from './register-client-form-field.model'
6export * from './register-client-hook.model' 6export * from './register-client-hook.model'
7export * from './register-client-route.model'
7export * from './register-client-settings-script.model' 8export * from './register-client-settings-script.model'
diff --git a/shared/models/plugins/client/plugin-selector-id.type.ts b/shared/models/plugins/client/plugin-selector-id.type.ts
index b74dffbef..8d23314b5 100644
--- a/shared/models/plugins/client/plugin-selector-id.type.ts
+++ b/shared/models/plugins/client/plugin-selector-id.type.ts
@@ -1 +1,10 @@
1export type PluginSelectorId = 'login-form' 1export type PluginSelectorId =
2 'login-form' |
3 'menu-user-dropdown-language-item' |
4 'about-instance-features' |
5 'about-instance-statistics' |
6 'about-instance-moderation' |
7 'about-menu-instance' |
8 'about-menu-peertube' |
9 'about-menu-network' |
10 'about-instance-other-information'
diff --git a/shared/models/plugins/client/register-client-form-field.model.ts b/shared/models/plugins/client/register-client-form-field.model.ts
index 2df071337..153c4a6ea 100644
--- a/shared/models/plugins/client/register-client-form-field.model.ts
+++ b/shared/models/plugins/client/register-client-form-field.model.ts
@@ -16,8 +16,15 @@ export type RegisterClientFormFieldOptions = {
16 16
17 // Not supported by plugin setting registration, use registerSettingsScript instead 17 // Not supported by plugin setting registration, use registerSettingsScript instead
18 hidden?: (options: any) => boolean 18 hidden?: (options: any) => boolean
19
20 // Return undefined | null if there is no error or return a string with the detailed error
21 // Not supported by plugin setting registration
22 error?: (options: any) => Promise<{ error: boolean, text?: string }>
19} 23}
20 24
21export interface RegisterClientVideoFieldOptions { 25export interface RegisterClientVideoFieldOptions {
22 type: 'update' | 'upload' | 'import-url' | 'import-torrent' | 'go-live' 26 type: 'update' | 'upload' | 'import-url' | 'import-torrent' | 'go-live'
27
28 // Default to 'plugin-settings'
29 tab?: 'main' | 'plugin-settings'
23} 30}
diff --git a/shared/models/plugins/client/register-client-route.model.ts b/shared/models/plugins/client/register-client-route.model.ts
new file mode 100644
index 000000000..271b67834
--- /dev/null
+++ b/shared/models/plugins/client/register-client-route.model.ts
@@ -0,0 +1,7 @@
1export interface RegisterClientRouteOptions {
2 route: string
3
4 onMount (options: {
5 rootEl: HTMLElement
6 }): void
7}
diff --git a/shared/models/plugins/client/register-client-settings-script.model.ts b/shared/models/plugins/client/register-client-settings-script.model.ts
index 481ceef96..117ca4739 100644
--- a/shared/models/plugins/client/register-client-settings-script.model.ts
+++ b/shared/models/plugins/client/register-client-settings-script.model.ts
@@ -1,6 +1,6 @@
1import { RegisterServerSettingOptions } from '../server' 1import { RegisterServerSettingOptions } from '../server'
2 2
3export interface RegisterClientSettingsScript { 3export interface RegisterClientSettingsScriptOptions {
4 isSettingHidden (options: { 4 isSettingHidden (options: {
5 setting: RegisterServerSettingOptions 5 setting: RegisterServerSettingOptions
6 formValues: { [name: string]: any } 6 formValues: { [name: string]: any }
diff --git a/shared/models/plugins/plugin-index/peertube-plugin-index.model.ts b/shared/models/plugins/plugin-index/peertube-plugin-index.model.ts
index e91c8b4dc..36dfef943 100644
--- a/shared/models/plugins/plugin-index/peertube-plugin-index.model.ts
+++ b/shared/models/plugins/plugin-index/peertube-plugin-index.model.ts
@@ -9,6 +9,8 @@ export interface PeerTubePluginIndex {
9 9
10 latestVersion: string 10 latestVersion: string
11 11
12 official: boolean
13
12 name?: string 14 name?: string
13 installed?: boolean 15 installed?: boolean
14} 16}
diff --git a/shared/models/plugins/plugin-package-json.model.ts b/shared/models/plugins/plugin-package-json.model.ts
index b2f92af80..7ce968ff2 100644
--- a/shared/models/plugins/plugin-package-json.model.ts
+++ b/shared/models/plugins/plugin-package-json.model.ts
@@ -1,15 +1,15 @@
1import { PluginClientScope } from './client/plugin-client-scope.type' 1import { PluginClientScope } from './client/plugin-client-scope.type'
2 2
3export type PluginTranslationPaths = { 3export type PluginTranslationPathsJSON = {
4 [ locale: string ]: string 4 [ locale: string ]: string
5} 5}
6 6
7export type ClientScript = { 7export type ClientScriptJSON = {
8 script: string 8 script: string
9 scopes: PluginClientScope[] 9 scopes: PluginClientScope[]
10} 10}
11 11
12export type PluginPackageJson = { 12export type PluginPackageJSON = {
13 name: string 13 name: string
14 version: string 14 version: string
15 description: string 15 description: string
@@ -23,7 +23,7 @@ export type PluginPackageJson = {
23 staticDirs: { [ name: string ]: string } 23 staticDirs: { [ name: string ]: string }
24 css: string[] 24 css: string[]
25 25
26 clientScripts: ClientScript[] 26 clientScripts: ClientScriptJSON[]
27 27
28 translations: PluginTranslationPaths 28 translations: PluginTranslationPathsJSON
29} 29}
diff --git a/shared/models/plugins/plugin.type.ts b/shared/models/plugins/plugin.type.ts
index b6766821a..016219ceb 100644
--- a/shared/models/plugins/plugin.type.ts
+++ b/shared/models/plugins/plugin.type.ts
@@ -1,4 +1,4 @@
1export enum PluginType { 1export const enum PluginType {
2 PLUGIN = 1, 2 PLUGIN = 1,
3 THEME = 2 3 THEME = 2
4} 4}
diff --git a/shared/models/plugins/server/api/install-plugin.model.ts b/shared/models/plugins/server/api/install-plugin.model.ts
index 5a268ebe1..a1d009a00 100644
--- a/shared/models/plugins/server/api/install-plugin.model.ts
+++ b/shared/models/plugins/server/api/install-plugin.model.ts
@@ -1,4 +1,5 @@
1export interface InstallOrUpdatePlugin { 1export interface InstallOrUpdatePlugin {
2 npmName?: string 2 npmName?: string
3 pluginVersion?: string
3 path?: string 4 path?: string
4} 5}
diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts
index 3ab910197..bd2b27da5 100644
--- a/shared/models/plugins/server/server-hook.model.ts
+++ b/shared/models/plugins/server/server-hook.model.ts
@@ -53,6 +53,12 @@ export const serverFilterHookObject = {
53 'filter:api.video-thread.create.accept.result': true, 53 'filter:api.video-thread.create.accept.result': true,
54 'filter:api.video-comment-reply.create.accept.result': true, 54 'filter:api.video-comment-reply.create.accept.result': true,
55 55
56 // Filter attributes when creating video object
57 'filter:api.video.upload.video-attribute.result': true,
58 'filter:api.video.import-url.video-attribute.result': true,
59 'filter:api.video.import-torrent.video-attribute.result': true,
60 'filter:api.video.live.video-attribute.result': true,
61
56 // Filter params/result used to list threads of a specific video 62 // Filter params/result used to list threads of a specific video
57 // (used by the video watch page) 63 // (used by the video watch page)
58 'filter:api.video-threads.list.params': true, 64 'filter:api.video-threads.list.params': true,
@@ -63,6 +69,9 @@ export const serverFilterHookObject = {
63 'filter:api.video-thread-comments.list.params': true, 69 'filter:api.video-thread-comments.list.params': true,
64 'filter:api.video-thread-comments.list.result': true, 70 'filter:api.video-thread-comments.list.result': true,
65 71
72 // Filter get stats result
73 'filter:api.server.stats.get.result': true,
74
66 // Filter result used to check if we need to auto blacklist a video 75 // Filter result used to check if we need to auto blacklist a video
67 // (fired when a local or remote video is created or updated) 76 // (fired when a local or remote video is created or updated)
68 'filter:video.auto-blacklist.result': true, 77 'filter:video.auto-blacklist.result': true,
@@ -106,6 +115,11 @@ export const serverActionHookObject = {
106 // Fired when a comment (thread or reply) is deleted 115 // Fired when a comment (thread or reply) is deleted
107 'action:api.video-comment.deleted': true, 116 'action:api.video-comment.deleted': true,
108 117
118 // Fired when a caption is created
119 'action:api.video-caption.created': true,
120 // Fired when a caption is deleted
121 'action:api.video-caption.deleted': true,
122
109 // Fired when a user is blocked (banned) 123 // Fired when a user is blocked (banned)
110 'action:api.user.blocked': true, 124 'action:api.user.blocked': true,
111 // Fired when a user is unblocked (unbanned) 125 // Fired when a user is unblocked (unbanned)
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index 3ed932494..52d3d9588 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -52,6 +52,20 @@ export interface CustomConfig {
52 } 52 }
53 } 53 }
54 54
55 client: {
56 videos: {
57 miniature: {
58 preferAuthorDisplayName: boolean
59 }
60 }
61
62 menu: {
63 login: {
64 redirectOnSingleExternalAuth: boolean
65 }
66 }
67 }
68
55 cache: { 69 cache: {
56 previews: { 70 previews: {
57 size: number 71 size: number
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts
index ecc960da5..8a69d11fa 100644
--- a/shared/models/server/job.model.ts
+++ b/shared/models/server/job.model.ts
@@ -103,18 +103,23 @@ interface BaseTranscodingPayload {
103 103
104export interface HLSTranscodingPayload extends BaseTranscodingPayload { 104export interface HLSTranscodingPayload extends BaseTranscodingPayload {
105 type: 'new-resolution-to-hls' 105 type: 'new-resolution-to-hls'
106 isPortraitMode?: boolean
107 resolution: VideoResolution 106 resolution: VideoResolution
108 copyCodecs: boolean 107 copyCodecs: boolean
109 108
109 hasAudio: boolean
110 isPortraitMode?: boolean
111
110 autoDeleteWebTorrentIfNeeded: boolean 112 autoDeleteWebTorrentIfNeeded: boolean
111 isMaxQuality: boolean 113 isMaxQuality: boolean
112} 114}
113 115
114export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload { 116export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {
115 type: 'new-resolution-to-webtorrent' 117 type: 'new-resolution-to-webtorrent'
116 isPortraitMode?: boolean
117 resolution: VideoResolution 118 resolution: VideoResolution
119
120 hasAudio: boolean
121
122 isPortraitMode?: boolean
118} 123}
119 124
120export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload { 125export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index e75eefd47..32be96b9d 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -1,12 +1,14 @@
1import { ClientScript } from '../plugins/plugin-package-json.model' 1import { VideoPrivacy } from '../videos/video-privacy.enum'
2import { ClientScriptJSON } from '../plugins/plugin-package-json.model'
2import { NSFWPolicyType } from '../videos/nsfw-policy.type' 3import { NSFWPolicyType } from '../videos/nsfw-policy.type'
3import { BroadcastMessageLevel } from './broadcast-message-level.type' 4import { BroadcastMessageLevel } from './broadcast-message-level.type'
4 5
5export interface ServerConfigPlugin { 6export interface ServerConfigPlugin {
6 name: string 7 name: string
8 npmName: string
7 version: string 9 version: string
8 description: string 10 description: string
9 clientScripts: { [name: string]: ClientScript } 11 clientScripts: { [name: string]: ClientScriptJSON }
10} 12}
11 13
12export interface ServerConfigTheme extends ServerConfigPlugin { 14export interface ServerConfigTheme extends ServerConfigPlugin {
@@ -39,6 +41,31 @@ export interface ServerConfig {
39 preferAuthorDisplayName: boolean 41 preferAuthorDisplayName: boolean
40 } 42 }
41 } 43 }
44
45 menu: {
46 login: {
47 redirectOnSingleExternalAuth: boolean
48 }
49 }
50 }
51
52 defaults: {
53 publish: {
54 downloadEnabled: boolean
55 commentsEnabled: boolean
56 privacy: VideoPrivacy
57 licence: number
58 }
59
60 p2p: {
61 webapp: {
62 enabled: boolean
63 }
64
65 embed: {
66 enabled: boolean
67 }
68 }
42 } 69 }
43 70
44 webadmin: { 71 webadmin: {
diff --git a/shared/models/users/index.ts b/shared/models/users/index.ts
index b61a8cd40..a24ffee96 100644
--- a/shared/models/users/index.ts
+++ b/shared/models/users/index.ts
@@ -8,6 +8,7 @@ export * from './user-refresh-token.model'
8export * from './user-register.model' 8export * from './user-register.model'
9export * from './user-right.enum' 9export * from './user-right.enum'
10export * from './user-role' 10export * from './user-role'
11export * from './user-scoped-token'
11export * from './user-update-me.model' 12export * from './user-update-me.model'
12export * from './user-update.model' 13export * from './user-update.model'
13export * from './user-video-quota.model' 14export * from './user-video-quota.model'
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts
index 6415ca6f2..668535f4e 100644
--- a/shared/models/users/user-right.enum.ts
+++ b/shared/models/users/user-right.enum.ts
@@ -22,9 +22,9 @@ export const enum UserRight {
22 MANAGE_SERVERS_BLOCKLIST, 22 MANAGE_SERVERS_BLOCKLIST,
23 23
24 MANAGE_VIDEO_BLACKLIST, 24 MANAGE_VIDEO_BLACKLIST,
25 MANAGE_ANY_VIDEO_CHANNEL,
25 26
26 REMOVE_ANY_VIDEO, 27 REMOVE_ANY_VIDEO,
27 REMOVE_ANY_VIDEO_CHANNEL,
28 REMOVE_ANY_VIDEO_PLAYLIST, 28 REMOVE_ANY_VIDEO_PLAYLIST,
29 REMOVE_ANY_VIDEO_COMMENT, 29 REMOVE_ANY_VIDEO_COMMENT,
30 30
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts
index 94413abca..687a2aa0d 100644
--- a/shared/models/users/user-role.ts
+++ b/shared/models/users/user-role.ts
@@ -1,5 +1,5 @@
1// Keep the order 1// Keep the order
2export enum UserRole { 2export const enum UserRole {
3 ADMINISTRATOR = 0, 3 ADMINISTRATOR = 0,
4 MODERATOR = 1, 4 MODERATOR = 1,
5 USER = 2 5 USER = 2
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts
index 6d7df38fb..e664e44b5 100644
--- a/shared/models/users/user-update-me.model.ts
+++ b/shared/models/users/user-update-me.model.ts
@@ -5,7 +5,10 @@ export interface UserUpdateMe {
5 description?: string 5 description?: string
6 nsfwPolicy?: NSFWPolicyType 6 nsfwPolicy?: NSFWPolicyType
7 7
8 // FIXME: deprecated in favour of p2pEnabled in 4.1
8 webTorrentEnabled?: boolean 9 webTorrentEnabled?: boolean
10 p2pEnabled?: boolean
11
9 autoPlayVideo?: boolean 12 autoPlayVideo?: boolean
10 autoPlayNextVideo?: boolean 13 autoPlayNextVideo?: boolean
11 autoPlayNextVideoPlaylist?: boolean 14 autoPlayNextVideoPlaylist?: boolean
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 78870c556..63c5c8a92 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -20,7 +20,11 @@ export interface User {
20 autoPlayVideo: boolean 20 autoPlayVideo: boolean
21 autoPlayNextVideo: boolean 21 autoPlayNextVideo: boolean
22 autoPlayNextVideoPlaylist: boolean 22 autoPlayNextVideoPlaylist: boolean
23
24 // @deprecated in favour of p2pEnabled
23 webTorrentEnabled: boolean 25 webTorrentEnabled: boolean
26 p2pEnabled: boolean
27
24 videosHistoryEnabled: boolean 28 videosHistoryEnabled: boolean
25 videoLanguages: string[] 29 videoLanguages: string[]
26 30
diff --git a/shared/models/videos/video-state.enum.ts b/shared/models/videos/video-state.enum.ts
index 6112b6e16..09268d2ff 100644
--- a/shared/models/videos/video-state.enum.ts
+++ b/shared/models/videos/video-state.enum.ts
@@ -5,5 +5,6 @@ export const enum VideoState {
5 WAITING_FOR_LIVE = 4, 5 WAITING_FOR_LIVE = 4,
6 LIVE_ENDED = 5, 6 LIVE_ENDED = 5,
7 TO_MOVE_TO_EXTERNAL_STORAGE = 6, 7 TO_MOVE_TO_EXTERNAL_STORAGE = 6,
8 TRANSCODING_FAILED = 7 8 TRANSCODING_FAILED = 7,
9 TO_MOVE_TO_EXTERNAL_STORAGE_FAILED = 8
9} 10}
diff --git a/shared/extra-utils/bulk/bulk-command.ts b/shared/server-commands/bulk/bulk-command.ts
index b5c5673ce..b5c5673ce 100644
--- a/shared/extra-utils/bulk/bulk-command.ts
+++ b/shared/server-commands/bulk/bulk-command.ts
diff --git a/shared/extra-utils/bulk/index.ts b/shared/server-commands/bulk/index.ts
index 391597243..391597243 100644
--- a/shared/extra-utils/bulk/index.ts
+++ b/shared/server-commands/bulk/index.ts
diff --git a/shared/extra-utils/cli/cli-command.ts b/shared/server-commands/cli/cli-command.ts
index ab9738174..ab9738174 100644
--- a/shared/extra-utils/cli/cli-command.ts
+++ b/shared/server-commands/cli/cli-command.ts
diff --git a/shared/extra-utils/cli/index.ts b/shared/server-commands/cli/index.ts
index 91b5abfbe..91b5abfbe 100644
--- a/shared/extra-utils/cli/index.ts
+++ b/shared/server-commands/cli/index.ts
diff --git a/shared/extra-utils/custom-pages/custom-pages-command.ts b/shared/server-commands/custom-pages/custom-pages-command.ts
index cd869a8de..cd869a8de 100644
--- a/shared/extra-utils/custom-pages/custom-pages-command.ts
+++ b/shared/server-commands/custom-pages/custom-pages-command.ts
diff --git a/shared/extra-utils/custom-pages/index.ts b/shared/server-commands/custom-pages/index.ts
index 58aed04f2..58aed04f2 100644
--- a/shared/extra-utils/custom-pages/index.ts
+++ b/shared/server-commands/custom-pages/index.ts
diff --git a/shared/extra-utils/feeds/feeds-command.ts b/shared/server-commands/feeds/feeds-command.ts
index 3c95f9536..3c95f9536 100644
--- a/shared/extra-utils/feeds/feeds-command.ts
+++ b/shared/server-commands/feeds/feeds-command.ts
diff --git a/shared/extra-utils/feeds/index.ts b/shared/server-commands/feeds/index.ts
index 662a22b6f..662a22b6f 100644
--- a/shared/extra-utils/feeds/index.ts
+++ b/shared/server-commands/feeds/index.ts
diff --git a/shared/server-commands/index.ts b/shared/server-commands/index.ts
new file mode 100644
index 000000000..c24ebb2df
--- /dev/null
+++ b/shared/server-commands/index.ts
@@ -0,0 +1,14 @@
1export * from './bulk'
2export * from './cli'
3export * from './custom-pages'
4export * from './feeds'
5export * from './logs'
6export * from './miscs'
7export * from './moderation'
8export * from './overviews'
9export * from './requests'
10export * from './search'
11export * from './server'
12export * from './socket'
13export * from './users'
14export * from './videos'
diff --git a/shared/extra-utils/logs/index.ts b/shared/server-commands/logs/index.ts
index 69452d7f0..69452d7f0 100644
--- a/shared/extra-utils/logs/index.ts
+++ b/shared/server-commands/logs/index.ts
diff --git a/shared/extra-utils/logs/logs-command.ts b/shared/server-commands/logs/logs-command.ts
index 7b5c66c0c..8f63383ea 100644
--- a/shared/extra-utils/logs/logs-command.ts
+++ b/shared/server-commands/logs/logs-command.ts
@@ -1,5 +1,4 @@
1import { HttpStatusCode } from '@shared/models' 1import { HttpStatusCode, LogLevel } from '@shared/models'
2import { LogLevel } from '../../models/server/log-level.type'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 2import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 3
5export class LogsCommand extends AbstractCommand { 4export class LogsCommand extends AbstractCommand {
diff --git a/shared/server-commands/miscs/index.ts b/shared/server-commands/miscs/index.ts
new file mode 100644
index 000000000..a1d14e998
--- /dev/null
+++ b/shared/server-commands/miscs/index.ts
@@ -0,0 +1,2 @@
1export * from './sql-command'
2export * from './webtorrent'
diff --git a/shared/extra-utils/miscs/sql-command.ts b/shared/server-commands/miscs/sql-command.ts
index bedb3349b..09a99f834 100644
--- a/shared/extra-utils/miscs/sql-command.ts
+++ b/shared/server-commands/miscs/sql-command.ts
@@ -1,5 +1,5 @@
1import { QueryTypes, Sequelize } from 'sequelize' 1import { QueryTypes, Sequelize } from 'sequelize'
2import { AbstractCommand } from '../shared/abstract-command' 2import { AbstractCommand } from '../shared'
3 3
4export class SQLCommand extends AbstractCommand { 4export class SQLCommand extends AbstractCommand {
5 private sequelize: Sequelize 5 private sequelize: Sequelize
diff --git a/shared/extra-utils/miscs/webtorrent.ts b/shared/server-commands/miscs/webtorrent.ts
index 0683f8893..0683f8893 100644
--- a/shared/extra-utils/miscs/webtorrent.ts
+++ b/shared/server-commands/miscs/webtorrent.ts
diff --git a/shared/extra-utils/moderation/abuses-command.ts b/shared/server-commands/moderation/abuses-command.ts
index 0db32ba46..0db32ba46 100644
--- a/shared/extra-utils/moderation/abuses-command.ts
+++ b/shared/server-commands/moderation/abuses-command.ts
diff --git a/shared/extra-utils/moderation/index.ts b/shared/server-commands/moderation/index.ts
index b37643956..b37643956 100644
--- a/shared/extra-utils/moderation/index.ts
+++ b/shared/server-commands/moderation/index.ts
diff --git a/shared/extra-utils/overviews/index.ts b/shared/server-commands/overviews/index.ts
index e19551907..e19551907 100644
--- a/shared/extra-utils/overviews/index.ts
+++ b/shared/server-commands/overviews/index.ts
diff --git a/shared/extra-utils/overviews/overviews-command.ts b/shared/server-commands/overviews/overviews-command.ts
index 06b4892d2..06b4892d2 100644
--- a/shared/extra-utils/overviews/overviews-command.ts
+++ b/shared/server-commands/overviews/overviews-command.ts
diff --git a/shared/server-commands/requests/index.ts b/shared/server-commands/requests/index.ts
new file mode 100644
index 000000000..802982301
--- /dev/null
+++ b/shared/server-commands/requests/index.ts
@@ -0,0 +1 @@
export * from './requests'
diff --git a/shared/extra-utils/requests/requests.ts b/shared/server-commands/requests/requests.ts
index b6b9024ed..95e4fe6b1 100644
--- a/shared/extra-utils/requests/requests.ts
+++ b/shared/server-commands/requests/requests.ts
@@ -3,8 +3,8 @@
3import { decode } from 'querystring' 3import { decode } from 'querystring'
4import request from 'supertest' 4import request from 'supertest'
5import { URL } from 'url' 5import { URL } from 'url'
6import { buildAbsoluteFixturePath } from '@shared/core-utils'
6import { HttpStatusCode } from '@shared/models' 7import { HttpStatusCode } from '@shared/models'
7import { buildAbsoluteFixturePath } from '../miscs/tests'
8 8
9export type CommonRequestParams = { 9export type CommonRequestParams = {
10 url: string 10 url: string
diff --git a/shared/extra-utils/search/index.ts b/shared/server-commands/search/index.ts
index 48dbe8ae9..48dbe8ae9 100644
--- a/shared/extra-utils/search/index.ts
+++ b/shared/server-commands/search/index.ts
diff --git a/shared/extra-utils/search/search-command.ts b/shared/server-commands/search/search-command.ts
index 0fbbcd6ef..0fbbcd6ef 100644
--- a/shared/extra-utils/search/search-command.ts
+++ b/shared/server-commands/search/search-command.ts
diff --git a/shared/extra-utils/server/config-command.ts b/shared/server-commands/server/config-command.ts
index 7a768b4df..797231b1d 100644
--- a/shared/extra-utils/server/config-command.ts
+++ b/shared/server-commands/server/config-command.ts
@@ -1,8 +1,7 @@
1import { merge } from 'lodash' 1import { merge } from 'lodash'
2import { DeepPartial } from '@shared/core-utils' 2import { About, CustomConfig, HttpStatusCode, ServerConfig } from '@shared/models'
3import { About, HttpStatusCode, ServerConfig } from '@shared/models' 3import { DeepPartial } from '@shared/typescript-utils'
4import { CustomConfig } from '../../models/server/custom-config.model' 4import { AbstractCommand, OverrideCommandOptions } from '../shared/abstract-command'
5import { AbstractCommand, OverrideCommandOptions } from '../shared'
6 5
7export class ConfigCommand extends AbstractCommand { 6export class ConfigCommand extends AbstractCommand {
8 7
@@ -194,6 +193,18 @@ export class ConfigCommand extends AbstractCommand {
194 whitelisted: true 193 whitelisted: true
195 } 194 }
196 }, 195 },
196 client: {
197 videos: {
198 miniature: {
199 preferAuthorDisplayName: false
200 }
201 },
202 menu: {
203 login: {
204 redirectOnSingleExternalAuth: false
205 }
206 }
207 },
197 cache: { 208 cache: {
198 previews: { 209 previews: {
199 size: 2 210 size: 2
diff --git a/shared/extra-utils/server/contact-form-command.ts b/shared/server-commands/server/contact-form-command.ts
index 0e8fd6d84..0e8fd6d84 100644
--- a/shared/extra-utils/server/contact-form-command.ts
+++ b/shared/server-commands/server/contact-form-command.ts
diff --git a/shared/extra-utils/server/debug-command.ts b/shared/server-commands/server/debug-command.ts
index 3c5a785bb..3c5a785bb 100644
--- a/shared/extra-utils/server/debug-command.ts
+++ b/shared/server-commands/server/debug-command.ts
diff --git a/shared/extra-utils/server/follows-command.ts b/shared/server-commands/server/follows-command.ts
index 01ef6f179..01ef6f179 100644
--- a/shared/extra-utils/server/follows-command.ts
+++ b/shared/server-commands/server/follows-command.ts
diff --git a/shared/extra-utils/server/follows.ts b/shared/server-commands/server/follows.ts
index 698238f29..698238f29 100644
--- a/shared/extra-utils/server/follows.ts
+++ b/shared/server-commands/server/follows.ts
diff --git a/shared/extra-utils/server/index.ts b/shared/server-commands/server/index.ts
index 76a2099da..0a4b21fc4 100644
--- a/shared/extra-utils/server/index.ts
+++ b/shared/server-commands/server/index.ts
@@ -1,17 +1,14 @@
1export * from './config-command' 1export * from './config-command'
2export * from './contact-form-command' 2export * from './contact-form-command'
3export * from './debug-command' 3export * from './debug-command'
4export * from './directories'
5export * from './follows-command' 4export * from './follows-command'
6export * from './follows' 5export * from './follows'
7export * from './jobs' 6export * from './jobs'
8export * from './jobs-command' 7export * from './jobs-command'
9export * from './object-storage-command' 8export * from './object-storage-command'
10export * from './plugins-command' 9export * from './plugins-command'
11export * from './plugins'
12export * from './redundancy-command' 10export * from './redundancy-command'
13export * from './server' 11export * from './server'
14export * from './servers-command' 12export * from './servers-command'
15export * from './servers' 13export * from './servers'
16export * from './stats-command' 14export * from './stats-command'
17export * from './tracker'
diff --git a/shared/extra-utils/server/jobs-command.ts b/shared/server-commands/server/jobs-command.ts
index 6636e7e4d..ac62157d1 100644
--- a/shared/extra-utils/server/jobs-command.ts
+++ b/shared/server-commands/server/jobs-command.ts
@@ -1,6 +1,5 @@
1import { pick } from '@shared/core-utils' 1import { pick } from '@shared/core-utils'
2import { HttpStatusCode } from '@shared/models' 2import { HttpStatusCode, Job, JobState, JobType, ResultList } from '@shared/models'
3import { Job, JobState, JobType, ResultList } from '../../models'
4import { AbstractCommand, OverrideCommandOptions } from '../shared' 3import { AbstractCommand, OverrideCommandOptions } from '../shared'
5 4
6export class JobsCommand extends AbstractCommand { 5export class JobsCommand extends AbstractCommand {
diff --git a/shared/extra-utils/server/jobs.ts b/shared/server-commands/server/jobs.ts
index 34fefd444..fc65a873b 100644
--- a/shared/extra-utils/server/jobs.ts
+++ b/shared/server-commands/server/jobs.ts
@@ -1,7 +1,7 @@
1 1
2import { expect } from 'chai' 2import { expect } from 'chai'
3import { wait } from '@shared/core-utils'
3import { JobState, JobType } from '../../models' 4import { JobState, JobType } from '../../models'
4import { wait } from '../miscs'
5import { PeerTubeServer } from './server' 5import { PeerTubeServer } from './server'
6 6
7async function waitJobs (serversArg: PeerTubeServer[] | PeerTubeServer, skipDelayed = false) { 7async function waitJobs (serversArg: PeerTubeServer[] | PeerTubeServer, skipDelayed = false) {
diff --git a/shared/extra-utils/server/object-storage-command.ts b/shared/server-commands/server/object-storage-command.ts
index b4de8f4cb..b4de8f4cb 100644
--- a/shared/extra-utils/server/object-storage-command.ts
+++ b/shared/server-commands/server/object-storage-command.ts
diff --git a/shared/extra-utils/server/plugins-command.ts b/shared/server-commands/server/plugins-command.ts
index b944475a2..bb1277a7c 100644
--- a/shared/extra-utils/server/plugins-command.ts
+++ b/shared/server-commands/server/plugins-command.ts
@@ -2,13 +2,13 @@
2 2
3import { readJSON, writeJSON } from 'fs-extra' 3import { readJSON, writeJSON } from 'fs-extra'
4import { join } from 'path' 4import { join } from 'path'
5import { root } from '@server/helpers/core-utils' 5import { root } from '@shared/core-utils'
6import { 6import {
7 HttpStatusCode, 7 HttpStatusCode,
8 PeerTubePlugin, 8 PeerTubePlugin,
9 PeerTubePluginIndex, 9 PeerTubePluginIndex,
10 PeertubePluginIndexList, 10 PeertubePluginIndexList,
11 PluginPackageJson, 11 PluginPackageJSON,
12 PluginTranslation, 12 PluginTranslation,
13 PluginType, 13 PluginType,
14 PublicServerSetting, 14 PublicServerSetting,
@@ -158,15 +158,16 @@ export class PluginsCommand extends AbstractCommand {
158 install (options: OverrideCommandOptions & { 158 install (options: OverrideCommandOptions & {
159 path?: string 159 path?: string
160 npmName?: string 160 npmName?: string
161 pluginVersion?: string
161 }) { 162 }) {
162 const { npmName, path } = options 163 const { npmName, path, pluginVersion } = options
163 const apiPath = '/api/v1/plugins/install' 164 const apiPath = '/api/v1/plugins/install'
164 165
165 return this.postBodyRequest({ 166 return this.postBodyRequest({
166 ...options, 167 ...options,
167 168
168 path: apiPath, 169 path: apiPath,
169 fields: { npmName, path }, 170 fields: { npmName, path, pluginVersion },
170 implicitToken: true, 171 implicitToken: true,
171 defaultExpectedStatus: HttpStatusCode.OK_200 172 defaultExpectedStatus: HttpStatusCode.OK_200
172 }) 173 })
@@ -244,7 +245,7 @@ export class PluginsCommand extends AbstractCommand {
244 return writeJSON(path, json) 245 return writeJSON(path, json)
245 } 246 }
246 247
247 getPackageJSON (npmName: string): Promise<PluginPackageJson> { 248 getPackageJSON (npmName: string): Promise<PluginPackageJSON> {
248 const path = this.getPackageJSONPath(npmName) 249 const path = this.getPackageJSONPath(npmName)
249 250
250 return readJSON(path) 251 return readJSON(path)
diff --git a/shared/extra-utils/server/redundancy-command.ts b/shared/server-commands/server/redundancy-command.ts
index e7a8b3c29..e7a8b3c29 100644
--- a/shared/extra-utils/server/redundancy-command.ts
+++ b/shared/server-commands/server/redundancy-command.ts
diff --git a/shared/extra-utils/server/server.ts b/shared/server-commands/server/server.ts
index 31224ebe9..da89fd876 100644
--- a/shared/extra-utils/server/server.ts
+++ b/shared/server-commands/server/server.ts
@@ -1,15 +1,14 @@
1import { ChildProcess, fork } from 'child_process' 1import { ChildProcess, fork } from 'child_process'
2import { copy } from 'fs-extra' 2import { copy } from 'fs-extra'
3import { join } from 'path' 3import { join } from 'path'
4import { root } from '@server/helpers/core-utils' 4import { parallelTests, randomInt, root } from '@shared/core-utils'
5import { randomInt } from '@shared/core-utils' 5import { Video, VideoChannel, VideoCreateResult, VideoDetails } from '@shared/models'
6import { Video, VideoChannel, VideoCreateResult, VideoDetails } from '../../models/videos'
7import { BulkCommand } from '../bulk' 6import { BulkCommand } from '../bulk'
8import { CLICommand } from '../cli' 7import { CLICommand } from '../cli'
9import { CustomPagesCommand } from '../custom-pages' 8import { CustomPagesCommand } from '../custom-pages'
10import { FeedCommand } from '../feeds' 9import { FeedCommand } from '../feeds'
11import { LogsCommand } from '../logs' 10import { LogsCommand } from '../logs'
12import { parallelTests, SQLCommand } from '../miscs' 11import { SQLCommand } from '../miscs'
13import { AbusesCommand } from '../moderation' 12import { AbusesCommand } from '../moderation'
14import { OverviewsCommand } from '../overviews' 13import { OverviewsCommand } from '../overviews'
15import { SearchCommand } from '../search' 14import { SearchCommand } from '../search'
@@ -34,11 +33,11 @@ import { ContactFormCommand } from './contact-form-command'
34import { DebugCommand } from './debug-command' 33import { DebugCommand } from './debug-command'
35import { FollowsCommand } from './follows-command' 34import { FollowsCommand } from './follows-command'
36import { JobsCommand } from './jobs-command' 35import { JobsCommand } from './jobs-command'
36import { ObjectStorageCommand } from './object-storage-command'
37import { PluginsCommand } from './plugins-command' 37import { PluginsCommand } from './plugins-command'
38import { RedundancyCommand } from './redundancy-command' 38import { RedundancyCommand } from './redundancy-command'
39import { ServersCommand } from './servers-command' 39import { ServersCommand } from './servers-command'
40import { StatsCommand } from './stats-command' 40import { StatsCommand } from './stats-command'
41import { ObjectStorageCommand } from './object-storage-command'
42 41
43export type RunServerOptions = { 42export type RunServerOptions = {
44 hideLogs?: boolean 43 hideLogs?: boolean
@@ -211,19 +210,26 @@ export class PeerTubeServer {
211 Object.assign(env, options.env) 210 Object.assign(env, options.env)
212 } 211 }
213 212
213 const execArgv = options.nodeArgs || []
214 // FIXME: too slow :/
215 // execArgv.push('--enable-source-maps')
216
214 const forkOptions = { 217 const forkOptions = {
215 silent: true, 218 silent: true,
216 env, 219 env,
217 detached: true, 220 detached: true,
218 execArgv: options.nodeArgs || [] 221 execArgv
219 } 222 }
220 223
224 const peertubeArgs = options.peertubeArgs || []
225
221 return new Promise<void>((res, rej) => { 226 return new Promise<void>((res, rej) => {
222 const self = this 227 const self = this
228 let aggregatedLogs = ''
223 229
224 this.app = fork(join(root(), 'dist', 'server.js'), options.peertubeArgs || [], forkOptions) 230 this.app = fork(join(root(), 'dist', 'server.js'), peertubeArgs, forkOptions)
225 231
226 const onPeerTubeExit = () => rej(new Error('Process exited')) 232 const onPeerTubeExit = () => rej(new Error('Process exited:\n' + aggregatedLogs))
227 const onParentExit = () => { 233 const onParentExit = () => {
228 if (!this.app || !this.app.pid) return 234 if (!this.app || !this.app.pid) return
229 235
@@ -238,10 +244,13 @@ export class PeerTubeServer {
238 this.app.stdout.on('data', function onStdout (data) { 244 this.app.stdout.on('data', function onStdout (data) {
239 let dontContinue = false 245 let dontContinue = false
240 246
247 const log: string = data.toString()
248 aggregatedLogs += log
249
241 // Capture things if we want to 250 // Capture things if we want to
242 for (const key of Object.keys(regexps)) { 251 for (const key of Object.keys(regexps)) {
243 const regexp = regexps[key] 252 const regexp = regexps[key]
244 const matches = data.toString().match(regexp) 253 const matches = log.match(regexp)
245 if (matches !== null) { 254 if (matches !== null) {
246 if (key === 'client_id') self.store.client.id = matches[1] 255 if (key === 'client_id') self.store.client.id = matches[1]
247 else if (key === 'client_secret') self.store.client.secret = matches[1] 256 else if (key === 'client_secret') self.store.client.secret = matches[1]
@@ -252,7 +261,7 @@ export class PeerTubeServer {
252 261
253 // Check if all required sentences are here 262 // Check if all required sentences are here
254 for (const key of Object.keys(serverRunString)) { 263 for (const key of Object.keys(serverRunString)) {
255 if (data.toString().indexOf(key) !== -1) serverRunString[key] = true 264 if (log.includes(key)) serverRunString[key] = true
256 if (serverRunString[key] === false) dontContinue = true 265 if (serverRunString[key] === false) dontContinue = true
257 } 266 }
258 267
@@ -260,7 +269,7 @@ export class PeerTubeServer {
260 if (dontContinue === true) return 269 if (dontContinue === true) return
261 270
262 if (options.hideLogs === false) { 271 if (options.hideLogs === false) {
263 console.log(data.toString()) 272 console.log(log)
264 } else { 273 } else {
265 process.removeListener('exit', onParentExit) 274 process.removeListener('exit', onParentExit)
266 self.app.stdout.removeListener('data', onStdout) 275 self.app.stdout.removeListener('data', onStdout)
diff --git a/shared/extra-utils/server/servers-command.ts b/shared/server-commands/server/servers-command.ts
index 776d2123c..c5d8d18dc 100644
--- a/shared/extra-utils/server/servers-command.ts
+++ b/shared/server-commands/server/servers-command.ts
@@ -1,9 +1,9 @@
1import { exec } from 'child_process' 1import { exec } from 'child_process'
2import { copy, ensureDir, readFile, remove } from 'fs-extra' 2import { copy, ensureDir, readFile, remove } from 'fs-extra'
3import { basename, join } from 'path' 3import { basename, join } from 'path'
4import { root } from '@server/helpers/core-utils' 4import { isGithubCI, root, wait } from '@shared/core-utils'
5import { getFileSize } from '@shared/extra-utils'
5import { HttpStatusCode } from '@shared/models' 6import { HttpStatusCode } from '@shared/models'
6import { getFileSize, isGithubCI, wait } from '../miscs'
7import { AbstractCommand, OverrideCommandOptions } from '../shared' 7import { AbstractCommand, OverrideCommandOptions } from '../shared'
8 8
9export class ServersCommand extends AbstractCommand { 9export class ServersCommand extends AbstractCommand {
diff --git a/shared/extra-utils/server/servers.ts b/shared/server-commands/server/servers.ts
index 21ab9405b..0faee3a8d 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/server-commands/server/servers.ts
@@ -1,5 +1,5 @@
1import { ensureDir } from 'fs-extra' 1import { ensureDir } from 'fs-extra'
2import { isGithubCI } from '../miscs' 2import { isGithubCI } from '@shared/core-utils'
3import { PeerTubeServer, RunServerOptions } from './server' 3import { PeerTubeServer, RunServerOptions } from './server'
4 4
5async function createSingleServer (serverNumber: number, configOverride?: Object, options: RunServerOptions = {}) { 5async function createSingleServer (serverNumber: number, configOverride?: Object, options: RunServerOptions = {}) {
diff --git a/shared/extra-utils/server/stats-command.ts b/shared/server-commands/server/stats-command.ts
index 64a452306..64a452306 100644
--- a/shared/extra-utils/server/stats-command.ts
+++ b/shared/server-commands/server/stats-command.ts
diff --git a/shared/extra-utils/shared/abstract-command.ts b/shared/server-commands/shared/abstract-command.ts
index a57c857fc..1b53a5330 100644
--- a/shared/extra-utils/shared/abstract-command.ts
+++ b/shared/server-commands/shared/abstract-command.ts
@@ -1,5 +1,5 @@
1import { isAbsolute, join } from 'path' 1import { isAbsolute, join } from 'path'
2import { root } from '../miscs/tests' 2import { root } from '@shared/core-utils'
3import { 3import {
4 makeDeleteRequest, 4 makeDeleteRequest,
5 makeGetRequest, 5 makeGetRequest,
diff --git a/shared/extra-utils/shared/index.ts b/shared/server-commands/shared/index.ts
index e807ab4f7..e807ab4f7 100644
--- a/shared/extra-utils/shared/index.ts
+++ b/shared/server-commands/shared/index.ts
diff --git a/shared/extra-utils/socket/index.ts b/shared/server-commands/socket/index.ts
index 594329b2f..594329b2f 100644
--- a/shared/extra-utils/socket/index.ts
+++ b/shared/server-commands/socket/index.ts
diff --git a/shared/extra-utils/socket/socket-io-command.ts b/shared/server-commands/socket/socket-io-command.ts
index c277ead28..c277ead28 100644
--- a/shared/extra-utils/socket/socket-io-command.ts
+++ b/shared/server-commands/socket/socket-io-command.ts
diff --git a/shared/extra-utils/users/accounts-command.ts b/shared/server-commands/users/accounts-command.ts
index 98d9d5927..5844b330b 100644
--- a/shared/extra-utils/users/accounts-command.ts
+++ b/shared/server-commands/users/accounts-command.ts
@@ -1,6 +1,4 @@
1import { HttpStatusCode, ResultList } from '@shared/models' 1import { Account, AccountVideoRate, ActorFollow, HttpStatusCode, ResultList, VideoRateType } from '@shared/models'
2import { Account, ActorFollow } from '../../models/actors'
3import { AccountVideoRate, VideoRateType } from '../../models/videos'
4import { AbstractCommand, OverrideCommandOptions } from '../shared' 2import { AbstractCommand, OverrideCommandOptions } from '../shared'
5 3
6export class AccountsCommand extends AbstractCommand { 4export class AccountsCommand extends AbstractCommand {
diff --git a/shared/extra-utils/users/blocklist-command.ts b/shared/server-commands/users/blocklist-command.ts
index 14491a1ae..2e7ed074d 100644
--- a/shared/extra-utils/users/blocklist-command.ts
+++ b/shared/server-commands/users/blocklist-command.ts
@@ -1,6 +1,6 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { AccountBlock, HttpStatusCode, ResultList, ServerBlock } from '@shared/models' 3import { AccountBlock, BlockStatus, HttpStatusCode, ResultList, ServerBlock } from '@shared/models'
4import { AbstractCommand, OverrideCommandOptions } from '../shared' 4import { AbstractCommand, OverrideCommandOptions } from '../shared'
5 5
6type ListBlocklistOptions = OverrideCommandOptions & { 6type ListBlocklistOptions = OverrideCommandOptions & {
@@ -37,6 +37,29 @@ export class BlocklistCommand extends AbstractCommand {
37 37
38 // --------------------------------------------------------------------------- 38 // ---------------------------------------------------------------------------
39 39
40 getStatus (options: OverrideCommandOptions & {
41 accounts?: string[]
42 hosts?: string[]
43 }) {
44 const { accounts, hosts } = options
45
46 const path = '/api/v1/blocklist/status'
47
48 return this.getRequestBody<BlockStatus>({
49 ...options,
50
51 path,
52 query: {
53 accounts,
54 hosts
55 },
56 implicitToken: false,
57 defaultExpectedStatus: HttpStatusCode.OK_200
58 })
59 }
60
61 // ---------------------------------------------------------------------------
62
40 addToMyBlocklist (options: OverrideCommandOptions & { 63 addToMyBlocklist (options: OverrideCommandOptions & {
41 account?: string 64 account?: string
42 server?: string 65 server?: string
diff --git a/shared/extra-utils/users/index.ts b/shared/server-commands/users/index.ts
index 460a06f70..c2bc5c44f 100644
--- a/shared/extra-utils/users/index.ts
+++ b/shared/server-commands/users/index.ts
@@ -1,9 +1,7 @@
1export * from './accounts-command' 1export * from './accounts-command'
2export * from './actors'
3export * from './blocklist-command' 2export * from './blocklist-command'
4export * from './login' 3export * from './login'
5export * from './login-command' 4export * from './login-command'
6export * from './notifications'
7export * from './notifications-command' 5export * from './notifications-command'
8export * from './subscriptions-command' 6export * from './subscriptions-command'
9export * from './users-command' 7export * from './users-command'
diff --git a/shared/extra-utils/users/login-command.ts b/shared/server-commands/users/login-command.ts
index 143f72a59..143f72a59 100644
--- a/shared/extra-utils/users/login-command.ts
+++ b/shared/server-commands/users/login-command.ts
diff --git a/shared/extra-utils/users/login.ts b/shared/server-commands/users/login.ts
index f1df027d3..f1df027d3 100644
--- a/shared/extra-utils/users/login.ts
+++ b/shared/server-commands/users/login.ts
diff --git a/shared/extra-utils/users/notifications-command.ts b/shared/server-commands/users/notifications-command.ts
index 692420b8b..6bd815daa 100644
--- a/shared/extra-utils/users/notifications-command.ts
+++ b/shared/server-commands/users/notifications-command.ts
@@ -1,5 +1,4 @@
1import { HttpStatusCode, ResultList } from '@shared/models' 1import { HttpStatusCode, ResultList, UserNotification, UserNotificationSetting } from '@shared/models'
2import { UserNotification, UserNotificationSetting } from '../../models/users'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 2import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 3
5export class NotificationsCommand extends AbstractCommand { 4export class NotificationsCommand extends AbstractCommand {
diff --git a/shared/extra-utils/users/subscriptions-command.ts b/shared/server-commands/users/subscriptions-command.ts
index edc60e612..edc60e612 100644
--- a/shared/extra-utils/users/subscriptions-command.ts
+++ b/shared/server-commands/users/subscriptions-command.ts
diff --git a/shared/extra-utils/users/users-command.ts b/shared/server-commands/users/users-command.ts
index 2a10e4fc8..b5ae9008e 100644
--- a/shared/extra-utils/users/users-command.ts
+++ b/shared/server-commands/users/users-command.ts
@@ -4,6 +4,7 @@ import {
4 HttpStatusCode, 4 HttpStatusCode,
5 MyUser, 5 MyUser,
6 ResultList, 6 ResultList,
7 ScopedToken,
7 User, 8 User,
8 UserAdminFlag, 9 UserAdminFlag,
9 UserCreateResult, 10 UserCreateResult,
@@ -13,7 +14,6 @@ import {
13 UserVideoQuota, 14 UserVideoQuota,
14 UserVideoRate 15 UserVideoRate
15} from '@shared/models' 16} from '@shared/models'
16import { ScopedToken } from '@shared/models/users/user-scoped-token'
17import { unwrapBody } from '../requests' 17import { unwrapBody } from '../requests'
18import { AbstractCommand, OverrideCommandOptions } from '../shared' 18import { AbstractCommand, OverrideCommandOptions } from '../shared'
19 19
@@ -202,7 +202,8 @@ export class UsersCommand extends AbstractCommand {
202 return { 202 return {
203 token, 203 token,
204 userId: user.id, 204 userId: user.id,
205 userChannelId: me.videoChannels[0].id 205 userChannelId: me.videoChannels[0].id,
206 userChannelName: me.videoChannels[0].name
206 } 207 }
207 } 208 }
208 209
diff --git a/shared/extra-utils/videos/blacklist-command.ts b/shared/server-commands/videos/blacklist-command.ts
index 3a2ef89ba..47e23ebc8 100644
--- a/shared/extra-utils/videos/blacklist-command.ts
+++ b/shared/server-commands/videos/blacklist-command.ts
@@ -1,6 +1,5 @@
1 1
2import { HttpStatusCode, ResultList } from '@shared/models' 2import { HttpStatusCode, ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models'
3import { VideoBlacklist, VideoBlacklistType } from '../../models/videos'
4import { AbstractCommand, OverrideCommandOptions } from '../shared' 3import { AbstractCommand, OverrideCommandOptions } from '../shared'
5 4
6export class BlacklistCommand extends AbstractCommand { 5export class BlacklistCommand extends AbstractCommand {
diff --git a/shared/extra-utils/videos/captions-command.ts b/shared/server-commands/videos/captions-command.ts
index a65ea99e3..62bf9c5e6 100644
--- a/shared/extra-utils/videos/captions-command.ts
+++ b/shared/server-commands/videos/captions-command.ts
@@ -1,5 +1,5 @@
1import { buildAbsoluteFixturePath } from '@shared/core-utils'
1import { HttpStatusCode, ResultList, VideoCaption } from '@shared/models' 2import { HttpStatusCode, ResultList, VideoCaption } from '@shared/models'
2import { buildAbsoluteFixturePath } from '../miscs'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 3import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 4
5export class CaptionsCommand extends AbstractCommand { 5export class CaptionsCommand extends AbstractCommand {
diff --git a/shared/extra-utils/videos/change-ownership-command.ts b/shared/server-commands/videos/change-ownership-command.ts
index ad4c726ef..ad4c726ef 100644
--- a/shared/extra-utils/videos/change-ownership-command.ts
+++ b/shared/server-commands/videos/change-ownership-command.ts
diff --git a/shared/extra-utils/videos/channels-command.ts b/shared/server-commands/videos/channels-command.ts
index e406e570b..8ab124658 100644
--- a/shared/extra-utils/videos/channels-command.ts
+++ b/shared/server-commands/videos/channels-command.ts
@@ -1,7 +1,13 @@
1import { pick } from '@shared/core-utils' 1import { pick } from '@shared/core-utils'
2import { ActorFollow, HttpStatusCode, ResultList, VideoChannel, VideoChannelCreateResult } from '@shared/models' 2import {
3import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' 3 ActorFollow,
4import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' 4 HttpStatusCode,
5 ResultList,
6 VideoChannel,
7 VideoChannelCreate,
8 VideoChannelCreateResult,
9 VideoChannelUpdate
10} from '@shared/models'
5import { unwrapBody } from '../requests' 11import { unwrapBody } from '../requests'
6import { AbstractCommand, OverrideCommandOptions } from '../shared' 12import { AbstractCommand, OverrideCommandOptions } from '../shared'
7 13
diff --git a/shared/extra-utils/videos/channels.ts b/shared/server-commands/videos/channels.ts
index 756c47453..756c47453 100644
--- a/shared/extra-utils/videos/channels.ts
+++ b/shared/server-commands/videos/channels.ts
diff --git a/shared/extra-utils/videos/comments-command.ts b/shared/server-commands/videos/comments-command.ts
index f0d163a07..f0d163a07 100644
--- a/shared/extra-utils/videos/comments-command.ts
+++ b/shared/server-commands/videos/comments-command.ts
diff --git a/shared/extra-utils/videos/history-command.ts b/shared/server-commands/videos/history-command.ts
index 13b7150c1..13b7150c1 100644
--- a/shared/extra-utils/videos/history-command.ts
+++ b/shared/server-commands/videos/history-command.ts
diff --git a/shared/extra-utils/videos/imports-command.ts b/shared/server-commands/videos/imports-command.ts
index e4944694d..e4944694d 100644
--- a/shared/extra-utils/videos/imports-command.ts
+++ b/shared/server-commands/videos/imports-command.ts
diff --git a/shared/extra-utils/videos/index.ts b/shared/server-commands/videos/index.ts
index 26e663f46..68a188b21 100644
--- a/shared/extra-utils/videos/index.ts
+++ b/shared/server-commands/videos/index.ts
@@ -1,6 +1,5 @@
1export * from './blacklist-command' 1export * from './blacklist-command'
2export * from './captions-command' 2export * from './captions-command'
3export * from './captions'
4export * from './change-ownership-command' 3export * from './change-ownership-command'
5export * from './channels' 4export * from './channels'
6export * from './channels-command' 5export * from './channels-command'
@@ -10,10 +9,7 @@ export * from './imports-command'
10export * from './live-command' 9export * from './live-command'
11export * from './live' 10export * from './live'
12export * from './playlists-command' 11export * from './playlists-command'
13export * from './playlists'
14export * from './services-command' 12export * from './services-command'
15export * from './streaming-playlists-command' 13export * from './streaming-playlists-command'
16export * from './streaming-playlists'
17export * from './comments-command' 14export * from './comments-command'
18export * from './videos-command' 15export * from './videos-command'
19export * from './videos'
diff --git a/shared/extra-utils/videos/live-command.ts b/shared/server-commands/videos/live-command.ts
index 74f5d3089..f7816eca0 100644
--- a/shared/extra-utils/videos/live-command.ts
+++ b/shared/server-commands/videos/live-command.ts
@@ -3,8 +3,8 @@
3import { readdir } from 'fs-extra' 3import { readdir } from 'fs-extra'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { join } from 'path' 5import { join } from 'path'
6import { wait } from '@shared/core-utils'
6import { HttpStatusCode, LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoCreateResult, VideoDetails, VideoState } from '@shared/models' 7import { HttpStatusCode, LiveVideo, LiveVideoCreate, LiveVideoUpdate, VideoCreateResult, VideoDetails, VideoState } from '@shared/models'
7import { wait } from '../miscs'
8import { unwrapBody } from '../requests' 8import { unwrapBody } from '../requests'
9import { AbstractCommand, OverrideCommandOptions } from '../shared' 9import { AbstractCommand, OverrideCommandOptions } from '../shared'
10import { sendRTMPStream, testFfmpegStreamError } from './live' 10import { sendRTMPStream, testFfmpegStreamError } from './live'
diff --git a/shared/extra-utils/videos/live.ts b/shared/server-commands/videos/live.ts
index d3665bc90..7a7faa911 100644
--- a/shared/extra-utils/videos/live.ts
+++ b/shared/server-commands/videos/live.ts
@@ -1,10 +1,5 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg' 1import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg'
5import { pathExists, readdir } from 'fs-extra' 2import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
6import { join } from 'path'
7import { buildAbsoluteFixturePath, wait } from '../miscs'
8import { PeerTubeServer } from '../server/server' 3import { PeerTubeServer } from '../server/server'
9 4
10function sendRTMPStream (options: { 5function sendRTMPStream (options: {
@@ -95,43 +90,11 @@ async function waitUntilLiveSavedOnAllServers (servers: PeerTubeServer[], videoI
95 } 90 }
96} 91}
97 92
98async function checkLiveCleanupAfterSave (server: PeerTubeServer, videoUUID: string, resolutions: number[] = []) {
99 const basePath = server.servers.buildDirectory('streaming-playlists')
100 const hlsPath = join(basePath, 'hls', videoUUID)
101
102 if (resolutions.length === 0) {
103 const result = await pathExists(hlsPath)
104 expect(result).to.be.false
105
106 return
107 }
108
109 const files = await readdir(hlsPath)
110
111 // fragmented file and playlist per resolution + master playlist + segments sha256 json file
112 expect(files).to.have.lengthOf(resolutions.length * 2 + 2)
113
114 for (const resolution of resolutions) {
115 const fragmentedFile = files.find(f => f.endsWith(`-${resolution}-fragmented.mp4`))
116 expect(fragmentedFile).to.exist
117
118 const playlistFile = files.find(f => f.endsWith(`${resolution}.m3u8`))
119 expect(playlistFile).to.exist
120 }
121
122 const masterPlaylistFile = files.find(f => f.endsWith('-master.m3u8'))
123 expect(masterPlaylistFile).to.exist
124
125 const shaFile = files.find(f => f.endsWith('-segments-sha256.json'))
126 expect(shaFile).to.exist
127}
128
129export { 93export {
130 sendRTMPStream, 94 sendRTMPStream,
131 waitFfmpegUntilError, 95 waitFfmpegUntilError,
132 testFfmpegStreamError, 96 testFfmpegStreamError,
133 stopFfmpeg, 97 stopFfmpeg,
134 waitUntilLivePublishedOnAllServers, 98 waitUntilLivePublishedOnAllServers,
135 waitUntilLiveSavedOnAllServers, 99 waitUntilLiveSavedOnAllServers
136 checkLiveCleanupAfterSave
137} 100}
diff --git a/shared/extra-utils/videos/playlists-command.ts b/shared/server-commands/videos/playlists-command.ts
index ce23900d3..ce23900d3 100644
--- a/shared/extra-utils/videos/playlists-command.ts
+++ b/shared/server-commands/videos/playlists-command.ts
diff --git a/shared/extra-utils/videos/services-command.ts b/shared/server-commands/videos/services-command.ts
index 06760df42..06760df42 100644
--- a/shared/extra-utils/videos/services-command.ts
+++ b/shared/server-commands/videos/services-command.ts
diff --git a/shared/extra-utils/videos/streaming-playlists-command.ts b/shared/server-commands/videos/streaming-playlists-command.ts
index 5d40d35cb..5d40d35cb 100644
--- a/shared/extra-utils/videos/streaming-playlists-command.ts
+++ b/shared/server-commands/videos/streaming-playlists-command.ts
diff --git a/shared/extra-utils/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts
index 7ec9c3647..21753ddc4 100644
--- a/shared/extra-utils/videos/videos-command.ts
+++ b/shared/server-commands/videos/videos-command.ts
@@ -5,9 +5,8 @@ import { createReadStream, stat } from 'fs-extra'
5import got, { Response as GotResponse } from 'got' 5import got, { Response as GotResponse } from 'got'
6import { omit } from 'lodash' 6import { omit } from 'lodash'
7import validator from 'validator' 7import validator from 'validator'
8import { buildUUID } from '@server/helpers/uuid' 8import { buildAbsoluteFixturePath, pick, wait } from '@shared/core-utils'
9import { loadLanguages } from '@server/initializers/constants' 9import { buildUUID } from '@shared/extra-utils'
10import { pick } from '@shared/core-utils'
11import { 10import {
12 HttpStatusCode, 11 HttpStatusCode,
13 ResultList, 12 ResultList,
@@ -21,9 +20,8 @@ import {
21 VideosCommonQuery, 20 VideosCommonQuery,
22 VideoTranscodingCreate 21 VideoTranscodingCreate
23} from '@shared/models' 22} from '@shared/models'
24import { buildAbsoluteFixturePath, wait } from '../miscs'
25import { unwrapBody } from '../requests' 23import { unwrapBody } from '../requests'
26import { PeerTubeServer, waitJobs } from '../server' 24import { waitJobs } from '../server'
27import { AbstractCommand, OverrideCommandOptions } from '../shared' 25import { AbstractCommand, OverrideCommandOptions } from '../shared'
28 26
29export type VideoEdit = Partial<Omit<VideoCreate, 'thumbnailfile' | 'previewfile'>> & { 27export type VideoEdit = Partial<Omit<VideoCreate, 'thumbnailfile' | 'previewfile'>> & {
@@ -33,13 +31,6 @@ export type VideoEdit = Partial<Omit<VideoCreate, 'thumbnailfile' | 'previewfile
33} 31}
34 32
35export class VideosCommand extends AbstractCommand { 33export class VideosCommand extends AbstractCommand {
36
37 constructor (server: PeerTubeServer) {
38 super(server)
39
40 loadLanguages()
41 }
42
43 getCategories (options: OverrideCommandOptions = {}) { 34 getCategories (options: OverrideCommandOptions = {}) {
44 const path = '/api/v1/videos/categories' 35 const path = '/api/v1/videos/categories'
45 36
diff --git a/shared/tsconfig.json b/shared/tsconfig.json
new file mode 100644
index 000000000..95892077b
--- /dev/null
+++ b/shared/tsconfig.json
@@ -0,0 +1,6 @@
1{
2 "extends": "../tsconfig.base.json",
3 "compilerOptions": {
4 "outDir": "../dist/shared"
5 }
6}
diff --git a/shared/tsconfig.types.json b/shared/tsconfig.types.json
new file mode 100644
index 000000000..6acfc05e1
--- /dev/null
+++ b/shared/tsconfig.types.json
@@ -0,0 +1,12 @@
1{
2 "extends": "./tsconfig.json",
3 "compilerOptions": {
4 "outDir": "../packages/types/dist/shared",
5 "stripInternal": true,
6 "removeComments": false,
7 "emitDeclarationOnly": true
8 },
9 "exclude": [
10 "server-commands/"
11 ]
12}
diff --git a/shared/typescript-utils/index.ts b/shared/typescript-utils/index.ts
new file mode 100644
index 000000000..c9f6f047d
--- /dev/null
+++ b/shared/typescript-utils/index.ts
@@ -0,0 +1 @@
export * from './types'
diff --git a/shared/core-utils/common/types.ts b/shared/typescript-utils/types.ts
index bd2a97b98..bd2a97b98 100644
--- a/shared/core-utils/common/types.ts
+++ b/shared/typescript-utils/types.ts