aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api')
-rw-r--r--server/controllers/api/abuse.ts18
-rw-r--r--server/controllers/api/blocklist.ts108
-rw-r--r--server/controllers/api/config.ts16
-rw-r--r--server/controllers/api/index.ts2
-rw-r--r--server/controllers/api/jobs.ts4
-rw-r--r--server/controllers/api/oauth-clients.ts5
-rw-r--r--server/controllers/api/plugins.ts7
-rw-r--r--server/controllers/api/server/stats.ts4
-rw-r--r--server/controllers/api/users/index.ts9
-rw-r--r--server/controllers/api/users/me.ts14
-rw-r--r--server/controllers/api/users/my-subscriptions.ts21
-rw-r--r--server/controllers/api/users/token.ts2
-rw-r--r--server/controllers/api/video-channel.ts26
-rw-r--r--server/controllers/api/video-playlist.ts2
-rw-r--r--server/controllers/api/videos/blacklist.ts3
-rw-r--r--server/controllers/api/videos/captions.ts5
-rw-r--r--server/controllers/api/videos/import.ts28
-rw-r--r--server/controllers/api/videos/live.ts9
-rw-r--r--server/controllers/api/videos/rate.ts3
-rw-r--r--server/controllers/api/videos/update.ts35
-rw-r--r--server/controllers/api/videos/upload.ts33
-rw-r--r--server/controllers/api/videos/watching.ts3
22 files changed, 260 insertions, 97 deletions
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts
index 72c418e74..d6211cc83 100644
--- a/server/controllers/api/abuse.ts
+++ b/server/controllers/api/abuse.ts
@@ -6,8 +6,7 @@ import { AbuseModel } from '@server/models/abuse/abuse'
6import { AbuseMessageModel } from '@server/models/abuse/abuse-message' 6import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
7import { getServerActor } from '@server/models/application/application' 7import { getServerActor } from '@server/models/application/application'
8import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' 8import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
9import { HttpStatusCode } from '@shared/models' 9import { AbuseCreate, AbuseState, HttpStatusCode, UserRight } from '@shared/models'
10import { AbuseCreate, AbuseState, UserRight } from '../../../shared'
11import { getFormattedObjects } from '../../helpers/utils' 10import { getFormattedObjects } from '../../helpers/utils'
12import { sequelizeTypescript } from '../../initializers/database' 11import { sequelizeTypescript } from '../../initializers/database'
13import { 12import {
@@ -167,7 +166,11 @@ async function reportAbuse (req: express.Request, res: express.Response) {
167 const body: AbuseCreate = req.body 166 const body: AbuseCreate = req.body
168 167
169 const { id } = await sequelizeTypescript.transaction(async t => { 168 const { id } = await sequelizeTypescript.transaction(async t => {
170 const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) 169 const user = res.locals.oauth.token.User
170 // Don't send abuse notification if reporter is an admin/moderator
171 const skipNotification = user.hasRight(UserRight.MANAGE_ABUSES)
172
173 const reporterAccount = await AccountModel.load(user.Account.id, t)
171 const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r]) 174 const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r])
172 175
173 const baseAbuse = { 176 const baseAbuse = {
@@ -184,7 +187,8 @@ async function reportAbuse (req: express.Request, res: express.Response) {
184 reporterAccount, 187 reporterAccount,
185 transaction: t, 188 transaction: t,
186 startAt: body.video.startAt, 189 startAt: body.video.startAt,
187 endAt: body.video.endAt 190 endAt: body.video.endAt,
191 skipNotification
188 }) 192 })
189 } 193 }
190 194
@@ -193,7 +197,8 @@ async function reportAbuse (req: express.Request, res: express.Response) {
193 baseAbuse, 197 baseAbuse,
194 commentInstance, 198 commentInstance,
195 reporterAccount, 199 reporterAccount,
196 transaction: t 200 transaction: t,
201 skipNotification
197 }) 202 })
198 } 203 }
199 204
@@ -202,7 +207,8 @@ async function reportAbuse (req: express.Request, res: express.Response) {
202 baseAbuse, 207 baseAbuse,
203 accountInstance, 208 accountInstance,
204 reporterAccount, 209 reporterAccount,
205 transaction: t 210 transaction: t,
211 skipNotification
206 }) 212 })
207 }) 213 })
208 214
diff --git a/server/controllers/api/blocklist.ts b/server/controllers/api/blocklist.ts
new file mode 100644
index 000000000..1e936ad10
--- /dev/null
+++ b/server/controllers/api/blocklist.ts
@@ -0,0 +1,108 @@
1import express from 'express'
2import { handleToNameAndHost } from '@server/helpers/actors'
3import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
4import { getServerActor } from '@server/models/application/application'
5import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
6import { MActorAccountId, MUserAccountId } from '@server/types/models'
7import { BlockStatus } from '@shared/models'
8import { asyncMiddleware, blocklistStatusValidator, optionalAuthenticate } from '../../middlewares'
9import { logger } from '@server/helpers/logger'
10
11const blocklistRouter = express.Router()
12
13blocklistRouter.get('/status',
14 optionalAuthenticate,
15 blocklistStatusValidator,
16 asyncMiddleware(getBlocklistStatus)
17)
18
19// ---------------------------------------------------------------------------
20
21export {
22 blocklistRouter
23}
24
25// ---------------------------------------------------------------------------
26
27async function getBlocklistStatus (req: express.Request, res: express.Response) {
28 const hosts = req.query.hosts as string[]
29 const accounts = req.query.accounts as string[]
30 const user = res.locals.oauth?.token.User
31
32 const serverActor = await getServerActor()
33
34 const byAccountIds = [ serverActor.Account.id ]
35 if (user) byAccountIds.push(user.Account.id)
36
37 const status: BlockStatus = {
38 accounts: {},
39 hosts: {}
40 }
41
42 const baseOptions = {
43 byAccountIds,
44 user,
45 serverActor,
46 status
47 }
48
49 await Promise.all([
50 populateServerBlocklistStatus({ ...baseOptions, hosts }),
51 populateAccountBlocklistStatus({ ...baseOptions, accounts })
52 ])
53
54 return res.json(status)
55}
56
57async function populateServerBlocklistStatus (options: {
58 byAccountIds: number[]
59 user?: MUserAccountId
60 serverActor: MActorAccountId
61 hosts: string[]
62 status: BlockStatus
63}) {
64 const { byAccountIds, user, serverActor, hosts, status } = options
65
66 if (!hosts || hosts.length === 0) return
67
68 const serverBlocklistStatus = await ServerBlocklistModel.getBlockStatus(byAccountIds, hosts)
69
70 logger.debug('Got server blocklist status.', { serverBlocklistStatus, byAccountIds, hosts })
71
72 for (const host of hosts) {
73 const block = serverBlocklistStatus.find(b => b.host === host)
74
75 status.hosts[host] = getStatus(block, serverActor, user)
76 }
77}
78
79async function populateAccountBlocklistStatus (options: {
80 byAccountIds: number[]
81 user?: MUserAccountId
82 serverActor: MActorAccountId
83 accounts: string[]
84 status: BlockStatus
85}) {
86 const { byAccountIds, user, serverActor, accounts, status } = options
87
88 if (!accounts || accounts.length === 0) return
89
90 const accountBlocklistStatus = await AccountBlocklistModel.getBlockStatus(byAccountIds, accounts)
91
92 logger.debug('Got account blocklist status.', { accountBlocklistStatus, byAccountIds, accounts })
93
94 for (const account of accounts) {
95 const sanitizedHandle = handleToNameAndHost(account)
96
97 const block = accountBlocklistStatus.find(b => b.name === sanitizedHandle.name && b.host === sanitizedHandle.host)
98
99 status.accounts[sanitizedHandle.handle] = getStatus(block, serverActor, user)
100 }
101}
102
103function getStatus (block: { accountId: number }, serverActor: MActorAccountId, user?: MUserAccountId) {
104 return {
105 blockedByServer: !!(block && block.accountId === serverActor.Account.id),
106 blockedByUser: !!(block && user && block.accountId === user.Account.id)
107 }
108}
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 805ad99c7..4e3dd4d80 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -3,9 +3,7 @@ import { remove, writeJSON } from 'fs-extra'
3import { snakeCase } from 'lodash' 3import { snakeCase } from 'lodash'
4import validator from 'validator' 4import validator from 'validator'
5import { ServerConfigManager } from '@server/lib/server-config-manager' 5import { ServerConfigManager } from '@server/lib/server-config-manager'
6import { UserRight } from '../../../shared' 6import { About, CustomConfig, UserRight } from '@shared/models'
7import { About } from '../../../shared/models/server/about.model'
8import { CustomConfig } from '../../../shared/models/server/custom-config.model'
9import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' 7import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
10import { objectConverter } from '../../helpers/core-utils' 8import { objectConverter } from '../../helpers/core-utils'
11import { CONFIG, reloadConfig } from '../../initializers/config' 9import { CONFIG, reloadConfig } from '../../initializers/config'
@@ -169,6 +167,18 @@ function customConfig (): CustomConfig {
169 whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED 167 whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
170 } 168 }
171 }, 169 },
170 client: {
171 videos: {
172 miniature: {
173 preferAuthorDisplayName: CONFIG.CLIENT.VIDEOS.MINIATURE.PREFER_AUTHOR_DISPLAY_NAME
174 }
175 },
176 menu: {
177 login: {
178 redirectOnSingleExternalAuth: CONFIG.CLIENT.MENU.LOGIN.REDIRECT_ON_SINGLE_EXTERNAL_AUTH
179 }
180 }
181 },
172 cache: { 182 cache: {
173 previews: { 183 previews: {
174 size: CONFIG.CACHE.PREVIEWS.SIZE 184 size: CONFIG.CACHE.PREVIEWS.SIZE
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 9949b378a..5f49336b1 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -6,6 +6,7 @@ import { badRequest } from '../../helpers/express-utils'
6import { CONFIG } from '../../initializers/config' 6import { CONFIG } from '../../initializers/config'
7import { abuseRouter } from './abuse' 7import { abuseRouter } from './abuse'
8import { accountsRouter } from './accounts' 8import { accountsRouter } from './accounts'
9import { blocklistRouter } from './blocklist'
9import { bulkRouter } from './bulk' 10import { bulkRouter } from './bulk'
10import { configRouter } from './config' 11import { configRouter } from './config'
11import { customPageRouter } from './custom-page' 12import { customPageRouter } from './custom-page'
@@ -49,6 +50,7 @@ apiRouter.use('/search', searchRouter)
49apiRouter.use('/overviews', overviewsRouter) 50apiRouter.use('/overviews', overviewsRouter)
50apiRouter.use('/plugins', pluginRouter) 51apiRouter.use('/plugins', pluginRouter)
51apiRouter.use('/custom-pages', customPageRouter) 52apiRouter.use('/custom-pages', customPageRouter)
53apiRouter.use('/blocklist', blocklistRouter)
52apiRouter.use('/ping', pong) 54apiRouter.use('/ping', pong)
53apiRouter.use('/*', badRequest) 55apiRouter.use('/*', badRequest)
54 56
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
index 7001674bb..eebd195b0 100644
--- a/server/controllers/api/jobs.ts
+++ b/server/controllers/api/jobs.ts
@@ -1,7 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { ResultList } from '../../../shared' 2import { Job, JobState, JobType, ResultList, UserRight } from '@shared/models'
3import { Job, JobState, JobType } from '../../../shared/models'
4import { UserRight } from '../../../shared/models/users'
5import { isArray } from '../../helpers/custom-validators/misc' 3import { isArray } from '../../helpers/custom-validators/misc'
6import { JobQueue } from '../../lib/job-queue' 4import { JobQueue } from '../../lib/job-queue'
7import { 5import {
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index 4990fb0df..2d847bdc1 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -1,10 +1,9 @@
1import express from 'express' 1import express from 'express'
2import { OAuthClientLocal } from '../../../shared' 2import { OAuthClientModel } from '@server/models/oauth/oauth-client'
3import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 3import { HttpStatusCode, OAuthClientLocal } from '@shared/models'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { CONFIG } from '../../initializers/config' 5import { CONFIG } from '../../initializers/config'
6import { asyncMiddleware, openapiOperationDoc } from '../../middlewares' 6import { asyncMiddleware, openapiOperationDoc } from '../../middlewares'
7import { OAuthClientModel } from '../../models/oauth/oauth-client'
8 7
9const oauthClientsRouter = express.Router() 8const oauthClientsRouter = express.Router()
10 9
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts
index 2de7fe41f..de9e055dc 100644
--- a/server/controllers/api/plugins.ts
+++ b/server/controllers/api/plugins.ts
@@ -144,8 +144,13 @@ async function installPlugin (req: express.Request, res: express.Response) {
144 144
145 const fromDisk = !!body.path 145 const fromDisk = !!body.path
146 const toInstall = body.npmName || body.path 146 const toInstall = body.npmName || body.path
147
148 const pluginVersion = body.pluginVersion && body.npmName
149 ? body.pluginVersion
150 : undefined
151
147 try { 152 try {
148 const plugin = await PluginManager.Instance.install(toInstall, undefined, fromDisk) 153 const plugin = await PluginManager.Instance.install(toInstall, pluginVersion, fromDisk)
149 154
150 return res.json(plugin.toFormattedJSON()) 155 return res.json(plugin.toFormattedJSON())
151 } catch (err) { 156 } catch (err) {
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index d661144ca..2ab398f4d 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -3,6 +3,7 @@ import { StatsManager } from '@server/lib/stat-manager'
3import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants' 3import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
4import { asyncMiddleware } from '../../../middlewares' 4import { asyncMiddleware } from '../../../middlewares'
5import { cacheRoute } from '../../../middlewares/cache/cache' 5import { cacheRoute } from '../../../middlewares/cache/cache'
6import { Hooks } from '@server/lib/plugins/hooks'
6 7
7const statsRouter = express.Router() 8const statsRouter = express.Router()
8 9
@@ -12,7 +13,8 @@ statsRouter.get('/stats',
12) 13)
13 14
14async function getStats (_req: express.Request, res: express.Response) { 15async function getStats (_req: express.Request, res: express.Response) {
15 const data = await StatsManager.Instance.getStats() 16 let data = await StatsManager.Instance.getStats()
17 data = await Hooks.wrapObject(data, 'filter:api.server.stats.get.result')
16 18
17 return res.json(data) 19 return res.json(data)
18} 20}
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 11d3525e4..7efc3a137 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -4,10 +4,7 @@ import { tokensRouter } from '@server/controllers/api/users/token'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { OAuthTokenModel } from '@server/models/oauth/oauth-token' 5import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
6import { MUser, MUserAccountDefault } from '@server/types/models' 6import { MUser, MUserAccountDefault } from '@server/types/models'
7import { UserCreate, UserCreateResult, UserRight, UserRole, UserUpdate } from '../../../../shared' 7import { HttpStatusCode, UserAdminFlag, UserCreate, UserCreateResult, UserRegister, UserRight, UserRole, UserUpdate } from '@shared/models'
8import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
9import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
10import { UserRegister } from '../../../../shared/models/users/user-register.model'
11import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 8import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
12import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
13import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' 10import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
@@ -183,6 +180,7 @@ async function createUser (req: express.Request, res: express.Response) {
183 password: body.password, 180 password: body.password,
184 email: body.email, 181 email: body.email,
185 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 182 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
183 p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED,
186 autoPlayVideo: true, 184 autoPlayVideo: true,
187 role: body.role, 185 role: body.role,
188 videoQuota: body.videoQuota, 186 videoQuota: body.videoQuota,
@@ -209,7 +207,7 @@ async function createUser (req: express.Request, res: express.Response) {
209 logger.info('Sending to user %s a create password email', body.username) 207 logger.info('Sending to user %s a create password email', body.username)
210 const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id) 208 const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id)
211 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString 209 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
212 await Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url) 210 Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url)
213 } 211 }
214 212
215 Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res }) 213 Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res })
@@ -232,6 +230,7 @@ async function registerUser (req: express.Request, res: express.Response) {
232 password: body.password, 230 password: body.password,
233 email: body.email, 231 email: body.email,
234 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 232 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
233 p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED,
235 autoPlayVideo: true, 234 autoPlayVideo: true,
236 role: UserRole.USER, 235 role: UserRole.USER,
237 videoQuota: CONFIG.USER.VIDEO_QUOTA, 236 videoQuota: CONFIG.USER.VIDEO_QUOTA,
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 6bacdbbb6..878dd5a84 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -2,10 +2,8 @@ import 'multer'
2import express from 'express' 2import express from 'express'
3import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger' 3import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { AttributesOnly } from '@shared/core-utils' 5import { ActorImageType, HttpStatusCode, UserUpdateMe, UserVideoQuota, UserVideoRate as FormattedUserVideoRate } from '@shared/models'
6import { ActorImageType, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' 6import { AttributesOnly } from '@shared/typescript-utils'
7import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
8import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model'
9import { createReqFiles } from '../../../helpers/express-utils' 7import { createReqFiles } from '../../../helpers/express-utils'
10import { getFormattedObjects } from '../../../helpers/utils' 8import { getFormattedObjects } from '../../../helpers/utils'
11import { CONFIG } from '../../../initializers/config' 9import { CONFIG } from '../../../initializers/config'
@@ -197,7 +195,7 @@ async function updateMe (req: express.Request, res: express.Response) {
197 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [ 195 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
198 'password', 196 'password',
199 'nsfwPolicy', 197 'nsfwPolicy',
200 'webTorrentEnabled', 198 'p2pEnabled',
201 'autoPlayVideo', 199 'autoPlayVideo',
202 'autoPlayNextVideo', 200 'autoPlayNextVideo',
203 'autoPlayNextVideoPlaylist', 201 'autoPlayNextVideoPlaylist',
@@ -213,6 +211,12 @@ async function updateMe (req: express.Request, res: express.Response) {
213 if (body[key] !== undefined) user.set(key, body[key]) 211 if (body[key] !== undefined) user.set(key, body[key])
214 } 212 }
215 213
214 if (body.p2pEnabled !== undefined) {
215 user.set('p2pEnabled', body.p2pEnabled)
216 } else if (body.webTorrentEnabled !== undefined) { // FIXME: deprecated in 4.1
217 user.set('p2pEnabled', body.webTorrentEnabled)
218 }
219
216 if (body.email !== undefined) { 220 if (body.email !== undefined) {
217 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { 221 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
218 user.pendingEmail = body.email 222 user.pendingEmail = body.email
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 6799ca8c5..fb1f68635 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -1,5 +1,6 @@
1import 'multer' 1import 'multer'
2import express from 'express' 2import express from 'express'
3import { handlesToNameAndHost } from '@server/helpers/actors'
3import { pickCommonVideoQuery } from '@server/helpers/query' 4import { pickCommonVideoQuery } from '@server/helpers/query'
4import { sendUndoFollow } from '@server/lib/activitypub/send' 5import { sendUndoFollow } from '@server/lib/activitypub/send'
5import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' 6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
@@ -7,7 +8,6 @@ import { VideoChannelModel } from '@server/models/video/video-channel'
7import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 8import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
8import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' 9import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
9import { getFormattedObjects } from '../../../helpers/utils' 10import { getFormattedObjects } from '../../../helpers/utils'
10import { WEBSERVER } from '../../../initializers/constants'
11import { sequelizeTypescript } from '../../../initializers/database' 11import { sequelizeTypescript } from '../../../initializers/database'
12import { JobQueue } from '../../../lib/job-queue' 12import { JobQueue } from '../../../lib/job-queue'
13import { 13import {
@@ -89,28 +89,23 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons
89 const uris = req.query.uris as string[] 89 const uris = req.query.uris as string[]
90 const user = res.locals.oauth.token.User 90 const user = res.locals.oauth.token.User
91 91
92 const handles = uris.map(u => { 92 const sanitizedHandles = handlesToNameAndHost(uris)
93 let [ name, host ] = u.split('@')
94 if (host === WEBSERVER.HOST) host = null
95 93
96 return { name, host, uri: u } 94 const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, sanitizedHandles)
97 })
98
99 const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, handles)
100 95
101 const existObject: { [id: string ]: boolean } = {} 96 const existObject: { [id: string ]: boolean } = {}
102 for (const handle of handles) { 97 for (const sanitizedHandle of sanitizedHandles) {
103 const obj = results.find(r => { 98 const obj = results.find(r => {
104 const server = r.ActorFollowing.Server 99 const server = r.ActorFollowing.Server
105 100
106 return r.ActorFollowing.preferredUsername === handle.name && 101 return r.ActorFollowing.preferredUsername === sanitizedHandle.name &&
107 ( 102 (
108 (!server && !handle.host) || 103 (!server && !sanitizedHandle.host) ||
109 (server.host === handle.host) 104 (server.host === sanitizedHandle.host)
110 ) 105 )
111 }) 106 })
112 107
113 existObject[handle.uri] = obj !== undefined 108 existObject[sanitizedHandle.handle] = obj !== undefined
114 } 109 }
115 110
116 return res.json(existObject) 111 return res.json(existObject)
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts
index 1d4004ce0..258b50fe9 100644
--- a/server/controllers/api/users/token.ts
+++ b/server/controllers/api/users/token.ts
@@ -1,13 +1,13 @@
1import express from 'express' 1import express from 'express'
2import RateLimit from 'express-rate-limit' 2import RateLimit from 'express-rate-limit'
3import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { buildUUID } from '@server/helpers/uuid'
5import { CONFIG } from '@server/initializers/config' 4import { CONFIG } from '@server/initializers/config'
6import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth' 5import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
7import { handleOAuthToken } from '@server/lib/auth/oauth' 6import { handleOAuthToken } from '@server/lib/auth/oauth'
8import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model' 7import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
9import { Hooks } from '@server/lib/plugins/hooks' 8import { Hooks } from '@server/lib/plugins/hooks'
10import { asyncMiddleware, authenticate, openapiOperationDoc } from '@server/middlewares' 9import { asyncMiddleware, authenticate, openapiOperationDoc } from '@server/middlewares'
10import { buildUUID } from '@shared/extra-utils'
11import { ScopedToken } from '@shared/models/users/user-scoped-token' 11import { ScopedToken } from '@shared/models/users/user-scoped-token'
12 12
13const tokensRouter = express.Router() 13const tokensRouter = express.Router()
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index d1a1e6473..e65550a22 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -5,8 +5,7 @@ import { ActorFollowModel } from '@server/models/actor/actor-follow'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' 6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
7import { MChannelBannerAccountDefault } from '@server/types/models' 7import { MChannelBannerAccountDefault } from '@server/types/models'
8import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 8import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate } from '@shared/models'
9import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
10import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 9import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
11import { resetSequelizeInstance } from '../../helpers/database-utils' 10import { resetSequelizeInstance } from '../../helpers/database-utils'
12import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 11import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
@@ -24,6 +23,7 @@ import {
24 asyncRetryTransactionMiddleware, 23 asyncRetryTransactionMiddleware,
25 authenticate, 24 authenticate,
26 commonVideosFiltersValidator, 25 commonVideosFiltersValidator,
26 ensureCanManageChannel,
27 optionalAuthenticate, 27 optionalAuthenticate,
28 paginationValidator, 28 paginationValidator,
29 setDefaultPagination, 29 setDefaultPagination,
@@ -36,7 +36,7 @@ import {
36 videoPlaylistsSortValidator 36 videoPlaylistsSortValidator
37} from '../../middlewares' 37} from '../../middlewares'
38import { 38import {
39 ensureAuthUserOwnsChannelValidator, 39 ensureIsLocalChannel,
40 videoChannelsFollowersSortValidator, 40 videoChannelsFollowersSortValidator,
41 videoChannelsListValidator, 41 videoChannelsListValidator,
42 videoChannelsNameWithHostValidator, 42 videoChannelsNameWithHostValidator,
@@ -74,7 +74,8 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick',
74 authenticate, 74 authenticate,
75 reqAvatarFile, 75 reqAvatarFile,
76 asyncMiddleware(videoChannelsNameWithHostValidator), 76 asyncMiddleware(videoChannelsNameWithHostValidator),
77 ensureAuthUserOwnsChannelValidator, 77 ensureIsLocalChannel,
78 ensureCanManageChannel,
78 updateAvatarValidator, 79 updateAvatarValidator,
79 asyncMiddleware(updateVideoChannelAvatar) 80 asyncMiddleware(updateVideoChannelAvatar)
80) 81)
@@ -83,7 +84,8 @@ videoChannelRouter.post('/:nameWithHost/banner/pick',
83 authenticate, 84 authenticate,
84 reqBannerFile, 85 reqBannerFile,
85 asyncMiddleware(videoChannelsNameWithHostValidator), 86 asyncMiddleware(videoChannelsNameWithHostValidator),
86 ensureAuthUserOwnsChannelValidator, 87 ensureIsLocalChannel,
88 ensureCanManageChannel,
87 updateBannerValidator, 89 updateBannerValidator,
88 asyncMiddleware(updateVideoChannelBanner) 90 asyncMiddleware(updateVideoChannelBanner)
89) 91)
@@ -91,27 +93,33 @@ videoChannelRouter.post('/:nameWithHost/banner/pick',
91videoChannelRouter.delete('/:nameWithHost/avatar', 93videoChannelRouter.delete('/:nameWithHost/avatar',
92 authenticate, 94 authenticate,
93 asyncMiddleware(videoChannelsNameWithHostValidator), 95 asyncMiddleware(videoChannelsNameWithHostValidator),
94 ensureAuthUserOwnsChannelValidator, 96 ensureIsLocalChannel,
97 ensureCanManageChannel,
95 asyncMiddleware(deleteVideoChannelAvatar) 98 asyncMiddleware(deleteVideoChannelAvatar)
96) 99)
97 100
98videoChannelRouter.delete('/:nameWithHost/banner', 101videoChannelRouter.delete('/:nameWithHost/banner',
99 authenticate, 102 authenticate,
100 asyncMiddleware(videoChannelsNameWithHostValidator), 103 asyncMiddleware(videoChannelsNameWithHostValidator),
101 ensureAuthUserOwnsChannelValidator, 104 ensureIsLocalChannel,
105 ensureCanManageChannel,
102 asyncMiddleware(deleteVideoChannelBanner) 106 asyncMiddleware(deleteVideoChannelBanner)
103) 107)
104 108
105videoChannelRouter.put('/:nameWithHost', 109videoChannelRouter.put('/:nameWithHost',
106 authenticate, 110 authenticate,
107 asyncMiddleware(videoChannelsNameWithHostValidator), 111 asyncMiddleware(videoChannelsNameWithHostValidator),
108 ensureAuthUserOwnsChannelValidator, 112 ensureIsLocalChannel,
113 ensureCanManageChannel,
109 videoChannelsUpdateValidator, 114 videoChannelsUpdateValidator,
110 asyncRetryTransactionMiddleware(updateVideoChannel) 115 asyncRetryTransactionMiddleware(updateVideoChannel)
111) 116)
112 117
113videoChannelRouter.delete('/:nameWithHost', 118videoChannelRouter.delete('/:nameWithHost',
114 authenticate, 119 authenticate,
120 asyncMiddleware(videoChannelsNameWithHostValidator),
121 ensureIsLocalChannel,
122 ensureCanManageChannel,
115 asyncMiddleware(videoChannelsRemoveValidator), 123 asyncMiddleware(videoChannelsRemoveValidator),
116 asyncRetryTransactionMiddleware(removeVideoChannel) 124 asyncRetryTransactionMiddleware(removeVideoChannel)
117) 125)
@@ -145,7 +153,7 @@ videoChannelRouter.get('/:nameWithHost/videos',
145videoChannelRouter.get('/:nameWithHost/followers', 153videoChannelRouter.get('/:nameWithHost/followers',
146 authenticate, 154 authenticate,
147 asyncMiddleware(videoChannelsNameWithHostValidator), 155 asyncMiddleware(videoChannelsNameWithHostValidator),
148 ensureAuthUserOwnsChannelValidator, 156 ensureCanManageChannel,
149 paginationValidator, 157 paginationValidator,
150 videoChannelsFollowersSortValidator, 158 videoChannelsFollowersSortValidator,
151 setDefaultSort, 159 setDefaultSort,
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index 8b7a76718..795e14e73 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -1,10 +1,10 @@
1import express from 'express' 1import express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import { uuidToShort } from '@server/helpers/uuid'
4import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists' 3import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists'
5import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
6import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
7import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models' 6import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models'
7import { uuidToShort } from '@shared/extra-utils'
8import { VideoPlaylistCreateResult, VideoPlaylistElementCreateResult } from '@shared/models' 8import { VideoPlaylistCreateResult, VideoPlaylistElementCreateResult } from '@shared/models'
9import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 9import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
10import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model' 10import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model'
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index de65c74f1..4103bb063 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -1,7 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { blacklistVideo, unblacklistVideo } from '@server/lib/video-blacklist' 2import { blacklistVideo, unblacklistVideo } from '@server/lib/video-blacklist'
3import { UserRight, VideoBlacklistCreate } from '../../../../shared' 3import { HttpStatusCode, UserRight, VideoBlacklistCreate } from '@shared/models'
4import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
5import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
6import { getFormattedObjects } from '../../../helpers/utils' 5import { getFormattedObjects } from '../../../helpers/utils'
7import { sequelizeTypescript } from '../../../initializers/database' 6import { sequelizeTypescript } from '../../../initializers/database'
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index aa7259ee9..2a9a9d233 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -12,6 +12,7 @@ import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
12import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' 12import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
13import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' 13import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
14import { VideoCaptionModel } from '../../../models/video/video-caption' 14import { VideoCaptionModel } from '../../../models/video/video-caption'
15import { Hooks } from '@server/lib/plugins/hooks'
15 16
16const reqVideoCaptionAdd = createReqFiles( 17const reqVideoCaptionAdd = createReqFiles(
17 [ 'captionfile' ], 18 [ 'captionfile' ],
@@ -75,6 +76,8 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
75 await federateVideoIfNeeded(video, false, t) 76 await federateVideoIfNeeded(video, false, t)
76 }) 77 })
77 78
79 Hooks.runAction('action:api.video-caption.created', { caption: videoCaption, req, res })
80
78 return res.status(HttpStatusCode.NO_CONTENT_204).end() 81 return res.status(HttpStatusCode.NO_CONTENT_204).end()
79} 82}
80 83
@@ -91,5 +94,7 @@ async function deleteVideoCaption (req: express.Request, res: express.Response)
91 94
92 logger.info('Video caption %s of video %s deleted.', videoCaption.language, video.uuid) 95 logger.info('Video caption %s of video %s deleted.', videoCaption.language, video.uuid)
93 96
97 Hooks.runAction('action:api.video-caption.deleted', { caption: videoCaption, req, res })
98
94 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() 99 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
95} 100}
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index eddb9b32d..08d69827b 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -4,6 +4,7 @@ import { decode } from 'magnet-uri'
4import parseTorrent, { Instance } from 'parse-torrent' 4import parseTorrent, { Instance } from 'parse-torrent'
5import { join } from 'path' 5import { join } from 'path'
6import { isVideoFileExtnameValid } from '@server/helpers/custom-validators/videos' 6import { isVideoFileExtnameValid } from '@server/helpers/custom-validators/videos'
7import { Hooks } from '@server/lib/plugins/hooks'
7import { ServerConfigManager } from '@server/lib/server-config-manager' 8import { ServerConfigManager } from '@server/lib/server-config-manager'
8import { setVideoTags } from '@server/lib/video' 9import { setVideoTags } from '@server/lib/video'
9import { FilteredModelAttributes } from '@server/types' 10import { FilteredModelAttributes } from '@server/types'
@@ -18,15 +19,14 @@ import {
18 MVideoWithBlacklistLight 19 MVideoWithBlacklistLight
19} from '@server/types/models' 20} from '@server/types/models'
20import { MVideoImportFormattable } from '@server/types/models/video/video-import' 21import { MVideoImportFormattable } from '@server/types/models/video/video-import'
21import { ServerErrorCode, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' 22import { ServerErrorCode, ThumbnailType, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '@shared/models'
22import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
23import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 23import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
24import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 24import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
25import { isArray } from '../../../helpers/custom-validators/misc' 25import { isArray } from '../../../helpers/custom-validators/misc'
26import { cleanUpReqFiles, createReqFiles } from '../../../helpers/express-utils' 26import { cleanUpReqFiles, createReqFiles } from '../../../helpers/express-utils'
27import { logger } from '../../../helpers/logger' 27import { logger } from '../../../helpers/logger'
28import { getSecureTorrentName } from '../../../helpers/utils' 28import { getSecureTorrentName } from '../../../helpers/utils'
29import { YoutubeDLWrapper, YoutubeDLInfo } from '../../../helpers/youtube-dl' 29import { YoutubeDLInfo, YoutubeDLWrapper } from '../../../helpers/youtube-dl'
30import { CONFIG } from '../../../initializers/config' 30import { CONFIG } from '../../../initializers/config'
31import { MIMETYPES } from '../../../initializers/constants' 31import { MIMETYPES } from '../../../initializers/constants'
32import { sequelizeTypescript } from '../../../initializers/database' 32import { sequelizeTypescript } from '../../../initializers/database'
@@ -94,7 +94,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
94 videoName = result.name 94 videoName = result.name
95 } 95 }
96 96
97 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) 97 const video = await buildVideo(res.locals.videoChannel.id, body, { name: videoName })
98 98
99 const thumbnailModel = await processThumbnail(req, video) 99 const thumbnailModel = await processThumbnail(req, video)
100 const previewModel = await processPreview(req, video) 100 const previewModel = await processPreview(req, video)
@@ -151,7 +151,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
151 }) 151 })
152 } 152 }
153 153
154 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) 154 const video = await buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
155 155
156 // Process video thumbnail from request.files 156 // Process video thumbnail from request.files
157 let thumbnailModel = await processThumbnail(req, video) 157 let thumbnailModel = await processThumbnail(req, video)
@@ -210,15 +210,15 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
210 return res.json(videoImport.toFormattedJSON()).end() 210 return res.json(videoImport.toFormattedJSON()).end()
211} 211}
212 212
213function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo): MVideoThumbnail { 213async function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo): Promise<MVideoThumbnail> {
214 const videoData = { 214 let videoData = {
215 name: body.name || importData.name || 'Unknown name', 215 name: body.name || importData.name || 'Unknown name',
216 remote: false, 216 remote: false,
217 category: body.category || importData.category, 217 category: body.category || importData.category,
218 licence: body.licence || importData.licence, 218 licence: body.licence ?? importData.licence ?? CONFIG.DEFAULTS.PUBLISH.LICENCE,
219 language: body.language || importData.language, 219 language: body.language || importData.language,
220 commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true" 220 commentsEnabled: body.commentsEnabled ?? CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
221 downloadEnabled: body.downloadEnabled !== false, 221 downloadEnabled: body.downloadEnabled ?? CONFIG.DEFAULTS.PUBLISH.DOWNLOAD_ENABLED,
222 waitTranscoding: body.waitTranscoding || false, 222 waitTranscoding: body.waitTranscoding || false,
223 state: VideoState.TO_IMPORT, 223 state: VideoState.TO_IMPORT,
224 nsfw: body.nsfw || importData.nsfw || false, 224 nsfw: body.nsfw || importData.nsfw || false,
@@ -231,6 +231,14 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
231 ? new Date(body.originallyPublishedAt) 231 ? new Date(body.originallyPublishedAt)
232 : importData.originallyPublishedAt 232 : importData.originallyPublishedAt
233 } 233 }
234
235 videoData = await Hooks.wrapObject(
236 videoData,
237 body.targetUrl
238 ? 'filter:api.video.import-url.video-attribute.result'
239 : 'filter:api.video.import-torrent.video-attribute.result'
240 )
241
234 const video = new VideoModel(videoData) 242 const video = new VideoModel(videoData)
235 video.url = getLocalVideoActivityPubUrl(video) 243 video.url = getLocalVideoActivityPubUrl(video)
236 244
diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts
index e29615ff5..8b8cacff9 100644
--- a/server/controllers/api/videos/live.ts
+++ b/server/controllers/api/videos/live.ts
@@ -1,6 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { createReqFiles } from '@server/helpers/express-utils' 2import { createReqFiles } from '@server/helpers/express-utils'
3import { buildUUID, uuidToShort } from '@server/helpers/uuid'
4import { CONFIG } from '@server/initializers/config' 3import { CONFIG } from '@server/initializers/config'
5import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' 4import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants'
6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' 5import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
@@ -10,8 +9,8 @@ import { buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } fro
10import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator } from '@server/middlewares/validators/videos/video-live' 9import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator } from '@server/middlewares/validators/videos/video-live'
11import { VideoLiveModel } from '@server/models/video/video-live' 10import { VideoLiveModel } from '@server/models/video/video-live'
12import { MVideoDetails, MVideoFullLight } from '@server/types/models' 11import { MVideoDetails, MVideoFullLight } from '@server/types/models'
13import { LiveVideoCreate, LiveVideoUpdate, VideoState } from '../../../../shared' 12import { buildUUID, uuidToShort } from '@shared/extra-utils'
14import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 13import { HttpStatusCode, LiveVideoCreate, LiveVideoUpdate, VideoState } from '@shared/models'
15import { logger } from '../../../helpers/logger' 14import { logger } from '../../../helpers/logger'
16import { sequelizeTypescript } from '../../../initializers/database' 15import { sequelizeTypescript } from '../../../initializers/database'
17import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' 16import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail'
@@ -83,7 +82,9 @@ async function addLiveVideo (req: express.Request, res: express.Response) {
83 const videoInfo: LiveVideoCreate = req.body 82 const videoInfo: LiveVideoCreate = req.body
84 83
85 // Prepare data so we don't block the transaction 84 // Prepare data so we don't block the transaction
86 const videoData = buildLocalVideoFromReq(videoInfo, res.locals.videoChannel.id) 85 let videoData = buildLocalVideoFromReq(videoInfo, res.locals.videoChannel.id)
86 videoData = await Hooks.wrapObject(videoData, 'filter:api.video.live.video-attribute.result')
87
87 videoData.isLive = true 88 videoData.isLive = true
88 videoData.state = VideoState.WAITING_FOR_LIVE 89 videoData.state = VideoState.WAITING_FOR_LIVE
89 videoData.duration = 0 90 videoData.duration = 0
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index c9cc16644..6b26a8eee 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,6 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { UserVideoRateUpdate } from '../../../../shared' 2import { HttpStatusCode, UserVideoRateUpdate } from '@shared/models'
3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
4import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
5import { VIDEO_RATE_TYPES } from '../../../initializers/constants' 4import { VIDEO_RATE_TYPES } from '../../../initializers/constants'
6import { sequelizeTypescript } from '../../../initializers/database' 5import { sequelizeTypescript } from '../../../initializers/database'
diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts
index 3fcff3e86..f600847d4 100644
--- a/server/controllers/api/videos/update.ts
+++ b/server/controllers/api/videos/update.ts
@@ -1,12 +1,12 @@
1import express from 'express' 1import express from 'express'
2import { Transaction } from 'sequelize/types' 2import { Transaction } from 'sequelize/types'
3import { updateTorrentMetadata } from '@server/helpers/webtorrent'
3import { changeVideoChannelShare } from '@server/lib/activitypub/share' 4import { changeVideoChannelShare } from '@server/lib/activitypub/share'
4import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' 5import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
5import { openapiOperationDoc } from '@server/middlewares/doc' 6import { openapiOperationDoc } from '@server/middlewares/doc'
6import { FilteredModelAttributes } from '@server/types' 7import { FilteredModelAttributes } from '@server/types'
7import { MVideoFullLight } from '@server/types/models' 8import { MVideoFullLight } from '@server/types/models'
8import { VideoUpdate } from '../../../../shared' 9import { HttpStatusCode, VideoUpdate } from '@shared/models'
9import { HttpStatusCode } from '../../../../shared/models'
10import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 10import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
11import { resetSequelizeInstance } from '../../../helpers/database-utils' 11import { resetSequelizeInstance } from '../../../helpers/database-utils'
12import { createReqFiles } from '../../../helpers/express-utils' 12import { createReqFiles } from '../../../helpers/express-utils'
@@ -68,7 +68,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
68 }) 68 })
69 69
70 try { 70 try {
71 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { 71 const { videoInstanceUpdated, isNewVideo } = await sequelizeTypescript.transaction(async t => {
72 // Refresh video since thumbnails to prevent concurrent updates 72 // Refresh video since thumbnails to prevent concurrent updates
73 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoFromReq.id, t) 73 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoFromReq.id, t)
74 74
@@ -137,8 +137,6 @@ async function updateVideo (req: express.Request, res: express.Response) {
137 transaction: t 137 transaction: t
138 }) 138 })
139 139
140 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
141
142 auditLogger.update( 140 auditLogger.update(
143 getAuditIdFromRes(res), 141 getAuditIdFromRes(res),
144 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), 142 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
@@ -146,12 +144,14 @@ async function updateVideo (req: express.Request, res: express.Response) {
146 ) 144 )
147 logger.info('Video with name %s and uuid %s updated.', video.name, video.uuid, lTags(video.uuid)) 145 logger.info('Video with name %s and uuid %s updated.', video.name, video.uuid, lTags(video.uuid))
148 146
149 return videoInstanceUpdated 147 return { videoInstanceUpdated, isNewVideo }
150 }) 148 })
151 149
152 if (wasConfidentialVideo) { 150 if (videoInfoToUpdate.name) await updateTorrentsMetadata(videoInstanceUpdated)
153 Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) 151
154 } 152 await sequelizeTypescript.transaction(t => federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t))
153
154 if (wasConfidentialVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated)
155 155
156 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body, req, res }) 156 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body, req, res })
157 } catch (err) { 157 } catch (err) {
@@ -199,3 +199,20 @@ function updateSchedule (videoInstance: MVideoFullLight, videoInfoToUpdate: Vide
199 return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction) 199 return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction)
200 } 200 }
201} 201}
202
203async function updateTorrentsMetadata (video: MVideoFullLight) {
204 for (const file of (video.VideoFiles || [])) {
205 await updateTorrentMetadata(video, file)
206
207 await file.save()
208 }
209
210 const hls = video.getHLSPlaylist()
211 if (!hls) return
212
213 for (const file of (hls.VideoFiles || [])) {
214 await updateTorrentMetadata(hls, file)
215
216 await file.save()
217 }
218}
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
index 6773b500f..89787f20b 100644
--- a/server/controllers/api/videos/upload.ts
+++ b/server/controllers/api/videos/upload.ts
@@ -1,13 +1,12 @@
1import express from 'express' 1import express from 'express'
2import { move } from 'fs-extra' 2import { move } from 'fs-extra'
3import { basename } from 'path' 3import { basename } from 'path'
4import { getLowercaseExtension } from '@server/helpers/core-utils'
5import { getResumableUploadPath } from '@server/helpers/upload' 4import { getResumableUploadPath } from '@server/helpers/upload'
6import { uuidToShort } from '@server/helpers/uuid'
7import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
8import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' 6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
9import { generateWebTorrentVideoFilename } from '@server/lib/paths' 7import { generateWebTorrentVideoFilename } from '@server/lib/paths'
10import { Redis } from '@server/lib/redis' 8import { Redis } from '@server/lib/redis'
9import { uploadx } from '@server/lib/uploadx'
11import { 10import {
12 addMoveToObjectStorageJob, 11 addMoveToObjectStorageJob,
13 addOptimizeOrMergeAudioJob, 12 addOptimizeOrMergeAudioJob,
@@ -19,16 +18,16 @@ import { VideoPathManager } from '@server/lib/video-path-manager'
19import { buildNextVideoState } from '@server/lib/video-state' 18import { buildNextVideoState } from '@server/lib/video-state'
20import { openapiOperationDoc } from '@server/middlewares/doc' 19import { openapiOperationDoc } from '@server/middlewares/doc'
21import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 20import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
22import { Uploadx } from '@uploadx/core' 21import { getLowercaseExtension } from '@shared/core-utils'
23import { VideoCreate, VideoState } from '../../../../shared' 22import { isAudioFile, uuidToShort } from '@shared/extra-utils'
24import { HttpStatusCode } from '../../../../shared/models' 23import { HttpStatusCode, VideoCreate, VideoResolution, VideoState } from '@shared/models'
25import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 24import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
26import { retryTransactionWrapper } from '../../../helpers/database-utils' 25import { retryTransactionWrapper } from '../../../helpers/database-utils'
27import { createReqFiles } from '../../../helpers/express-utils' 26import { createReqFiles } from '../../../helpers/express-utils'
28import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' 27import { ffprobePromise, getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
29import { logger, loggerTagsFactory } from '../../../helpers/logger' 28import { logger, loggerTagsFactory } from '../../../helpers/logger'
30import { CONFIG } from '../../../initializers/config' 29import { CONFIG } from '../../../initializers/config'
31import { DEFAULT_AUDIO_RESOLUTION, MIMETYPES } from '../../../initializers/constants' 30import { MIMETYPES } from '../../../initializers/constants'
32import { sequelizeTypescript } from '../../../initializers/database' 31import { sequelizeTypescript } from '../../../initializers/database'
33import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' 32import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
34import { Notifier } from '../../../lib/notifier' 33import { Notifier } from '../../../lib/notifier'
@@ -41,8 +40,8 @@ import {
41 authenticate, 40 authenticate,
42 videosAddLegacyValidator, 41 videosAddLegacyValidator,
43 videosAddResumableInitValidator, 42 videosAddResumableInitValidator,
44 videosResumableUploadIdValidator, 43 videosAddResumableValidator,
45 videosAddResumableValidator 44 videosResumableUploadIdValidator
46} from '../../../middlewares' 45} from '../../../middlewares'
47import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 46import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
48import { VideoModel } from '../../../models/video/video' 47import { VideoModel } from '../../../models/video/video'
@@ -52,9 +51,6 @@ const lTags = loggerTagsFactory('api', 'video')
52const auditLogger = auditLoggerFactory('videos') 51const auditLogger = auditLoggerFactory('videos')
53const uploadRouter = express.Router() 52const uploadRouter = express.Router()
54 53
55const uploadx = new Uploadx({ directory: getResumableUploadPath() })
56uploadx.getUserId = (_, res: express.Response) => res.locals.oauth?.token.user.id
57
58const reqVideoFileAdd = createReqFiles( 54const reqVideoFileAdd = createReqFiles(
59 [ 'videofile', 'thumbnailfile', 'previewfile' ], 55 [ 'videofile', 'thumbnailfile', 'previewfile' ],
60 Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), 56 Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
@@ -156,7 +152,8 @@ async function addVideo (options: {
156 const videoChannel = res.locals.videoChannel 152 const videoChannel = res.locals.videoChannel
157 const user = res.locals.oauth.token.User 153 const user = res.locals.oauth.token.User
158 154
159 const videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id) 155 let videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id)
156 videoData = await Hooks.wrapObject(videoData, 'filter:api.video.upload.video-attribute.result')
160 157
161 videoData.state = buildNextVideoState() 158 videoData.state = buildNextVideoState()
162 videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware 159 videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware
@@ -255,11 +252,13 @@ async function buildNewFile (videoPhysicalFile: express.VideoUploadFile) {
255 metadata: await getMetadataFromFile(videoPhysicalFile.path) 252 metadata: await getMetadataFromFile(videoPhysicalFile.path)
256 }) 253 })
257 254
258 if (videoFile.isAudio()) { 255 const probe = await ffprobePromise(videoPhysicalFile.path)
259 videoFile.resolution = DEFAULT_AUDIO_RESOLUTION 256
257 if (await isAudioFile(videoPhysicalFile.path, probe)) {
258 videoFile.resolution = VideoResolution.H_NOVIDEO
260 } else { 259 } else {
261 videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path) 260 videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path, probe)
262 videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).resolution 261 videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path, probe)).resolution
263 } 262 }
264 263
265 videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname) 264 videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname)
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts
index e8c28b613..3fd22caac 100644
--- a/server/controllers/api/videos/watching.ts
+++ b/server/controllers/api/videos/watching.ts
@@ -1,6 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { UserWatchingVideo } from '../../../../shared' 2import { HttpStatusCode, UserWatchingVideo } from '@shared/models'
3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
4import { 3import {
5 asyncMiddleware, 4 asyncMiddleware,
6 asyncRetryTransactionMiddleware, 5 asyncRetryTransactionMiddleware,