aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/accounts.ts37
-rw-r--r--server/controllers/api/index.ts2
-rw-r--r--server/controllers/api/users.ts3
-rw-r--r--server/controllers/api/videos/index.ts19
-rw-r--r--server/controllers/feeds.ts29
-rw-r--r--server/helpers/express-utils.ts77
-rw-r--r--server/helpers/utils.ts62
-rw-r--r--server/middlewares/servers.ts2
-rw-r--r--server/middlewares/validators/webfinger.ts2
-rw-r--r--server/models/video/video.ts73
10 files changed, 174 insertions, 132 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 4dc0cc16d..06ab04033 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,8 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects } from '../../helpers/utils' 2import { getFormattedObjects } from '../../helpers/utils'
3import { asyncMiddleware, paginationValidator, setDefaultSort, setDefaultPagination } from '../../middlewares' 3import { asyncMiddleware, optionalAuthenticate, paginationValidator, setDefaultPagination, setDefaultSort } from '../../middlewares'
4import { accountsGetValidator, accountsSortValidator } from '../../middlewares/validators' 4import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators'
5import { AccountModel } from '../../models/account/account' 5import { AccountModel } from '../../models/account/account'
6import { VideoModel } from '../../models/video/video'
7import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
8import { isNSFWHidden } from '../../helpers/express-utils'
6 9
7const accountsRouter = express.Router() 10const accountsRouter = express.Router()
8 11
@@ -19,6 +22,16 @@ accountsRouter.get('/:id',
19 getAccount 22 getAccount
20) 23)
21 24
25accountsRouter.get('/:id/videos',
26 asyncMiddleware(accountsGetValidator),
27 paginationValidator,
28 videosSortValidator,
29 setDefaultSort,
30 setDefaultPagination,
31 optionalAuthenticate,
32 asyncMiddleware(getAccountVideos)
33)
34
22// --------------------------------------------------------------------------- 35// ---------------------------------------------------------------------------
23 36
24export { 37export {
@@ -28,7 +41,9 @@ export {
28// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
29 42
30function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { 43function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
31 return res.json(res.locals.account.toFormattedJSON()) 44 const account: AccountModel = res.locals.account
45
46 return res.json(account.toFormattedJSON())
32} 47}
33 48
34async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) { 49async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -36,3 +51,19 @@ async function listAccounts (req: express.Request, res: express.Response, next:
36 51
37 return res.json(getFormattedObjects(resultList.data, resultList.total)) 52 return res.json(getFormattedObjects(resultList.data, resultList.total))
38} 53}
54
55async function getAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
56 const account: AccountModel = res.locals.account
57
58 const resultList = await VideoModel.listForApi(
59 req.query.start as number,
60 req.query.count as number,
61 req.query.sort as VideoSortField,
62 isNSFWHidden(res),
63 null,
64 false,
65 account.id
66 )
67
68 return res.json(getFormattedObjects(resultList.data, resultList.total))
69}
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 3b499f3b7..964d5d04c 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import { badRequest } from '../../helpers/utils'
3import { configRouter } from './config' 2import { configRouter } from './config'
4import { jobsRouter } from './jobs' 3import { jobsRouter } from './jobs'
5import { oauthClientsRouter } from './oauth-clients' 4import { oauthClientsRouter } from './oauth-clients'
@@ -7,6 +6,7 @@ import { serverRouter } from './server'
7import { usersRouter } from './users' 6import { usersRouter } from './users'
8import { accountsRouter } from './accounts' 7import { accountsRouter } from './accounts'
9import { videosRouter } from './videos' 8import { videosRouter } from './videos'
9import { badRequest } from '../../helpers/express-utils'
10 10
11const apiRouter = express.Router() 11const apiRouter = express.Router()
12 12
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 6540adb1c..474329b58 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -7,7 +7,7 @@ import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRat
7import { retryTransactionWrapper } from '../../helpers/database-utils' 7import { retryTransactionWrapper } from '../../helpers/database-utils'
8import { processImage } from '../../helpers/image-utils' 8import { processImage } from '../../helpers/image-utils'
9import { logger } from '../../helpers/logger' 9import { logger } from '../../helpers/logger'
10import { createReqFiles, getFormattedObjects } from '../../helpers/utils' 10import { getFormattedObjects } from '../../helpers/utils'
11import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers' 11import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers'
12import { updateActorAvatarInstance } from '../../lib/activitypub' 12import { updateActorAvatarInstance } from '../../lib/activitypub'
13import { sendUpdateActor } from '../../lib/activitypub/send' 13import { sendUpdateActor } from '../../lib/activitypub/send'
@@ -43,6 +43,7 @@ import { UserModel } from '../../models/account/user'
43import { OAuthTokenModel } from '../../models/oauth/oauth-token' 43import { OAuthTokenModel } from '../../models/oauth/oauth-token'
44import { VideoModel } from '../../models/video/video' 44import { VideoModel } from '../../models/video/video'
45import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type' 45import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
46import { createReqFiles } from '../../helpers/express-utils'
46 47
47const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) 48const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
48const loginRateLimiter = new RateLimit({ 49const loginRateLimiter = new RateLimit({
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 6e8601fa1..61b6c5826 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -6,7 +6,7 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils'
6import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 6import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
7import { processImage } from '../../../helpers/image-utils' 7import { processImage } from '../../../helpers/image-utils'
8import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
9import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' 9import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
10import { 10import {
11 CONFIG, 11 CONFIG,
12 IMAGE_MIMETYPE_EXT, 12 IMAGE_MIMETYPE_EXT,
@@ -19,11 +19,7 @@ import {
19 VIDEO_MIMETYPE_EXT, 19 VIDEO_MIMETYPE_EXT,
20 VIDEO_PRIVACIES 20 VIDEO_PRIVACIES
21} from '../../../initializers' 21} from '../../../initializers'
22import { 22import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub'
23 fetchRemoteVideoDescription,
24 getVideoActivityPubUrl,
25 shareVideoByServerAndChannel
26} from '../../../lib/activitypub'
27import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send' 23import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send'
28import { JobQueue } from '../../../lib/job-queue' 24import { JobQueue } from '../../../lib/job-queue'
29import { Redis } from '../../../lib/redis' 25import { Redis } from '../../../lib/redis'
@@ -49,9 +45,9 @@ import { blacklistRouter } from './blacklist'
49import { videoChannelRouter } from './channel' 45import { videoChannelRouter } from './channel'
50import { videoCommentRouter } from './comment' 46import { videoCommentRouter } from './comment'
51import { rateVideoRouter } from './rate' 47import { rateVideoRouter } from './rate'
52import { User } from '../../../../shared/models/users'
53import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 48import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
54import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' 49import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type'
50import { isNSFWHidden, createReqFiles } from '../../../helpers/express-utils'
55 51
56const videosRouter = express.Router() 52const videosRouter = express.Router()
57 53
@@ -444,12 +440,3 @@ async function searchVideos (req: express.Request, res: express.Response, next:
444 440
445 return res.json(getFormattedObjects(resultList.data, resultList.total)) 441 return res.json(getFormattedObjects(resultList.data, resultList.total))
446} 442}
447
448function isNSFWHidden (res: express.Response) {
449 if (res.locals.oauth) {
450 const user: User = res.locals.oauth.token.User
451 if (user) return user.nsfwPolicy === 'do_not_list'
452 }
453
454 return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
455}
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index 4a4dc3820..6a6af3e09 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -30,29 +30,18 @@ async function generateFeed (req: express.Request, res: express.Response, next:
30 let feed = initFeed() 30 let feed = initFeed()
31 const start = 0 31 const start = 0
32 32
33 let resultList: ResultList<VideoModel>
34 const account: AccountModel = res.locals.account 33 const account: AccountModel = res.locals.account
35 const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' 34 const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
36 35
37 if (account) { 36 const resultList = await VideoModel.listForApi(
38 resultList = await VideoModel.listAccountVideosForApi( 37 start,
39 account.id, 38 FEEDS.COUNT,
40 start, 39 req.query.sort as VideoSortField,
41 FEEDS.COUNT, 40 hideNSFW,
42 req.query.sort as VideoSortField, 41 req.query.filter,
43 hideNSFW, 42 true,
44 true 43 account ? account.id : null
45 ) 44 )
46 } else {
47 resultList = await VideoModel.listForApi(
48 start,
49 FEEDS.COUNT,
50 req.query.sort as VideoSortField,
51 hideNSFW,
52 req.query.filter,
53 true
54 )
55 }
56 45
57 // Adding video items to the feed, one at a time 46 // Adding video items to the feed, one at a time
58 resultList.data.forEach(video => { 47 resultList.data.forEach(video => {
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
new file mode 100644
index 000000000..d023117a8
--- /dev/null
+++ b/server/helpers/express-utils.ts
@@ -0,0 +1,77 @@
1import * as express from 'express'
2import * as multer from 'multer'
3import { CONFIG, REMOTE_SCHEME } from '../initializers'
4import { logger } from './logger'
5import { User } from '../../shared/models/users'
6import { generateRandomString } from './utils'
7
8function isNSFWHidden (res: express.Response) {
9 if (res.locals.oauth) {
10 const user: User = res.locals.oauth.token.User
11 if (user) return user.nsfwPolicy === 'do_not_list'
12 }
13
14 return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
15}
16
17function getHostWithPort (host: string) {
18 const splitted = host.split(':')
19
20 // The port was not specified
21 if (splitted.length === 1) {
22 if (REMOTE_SCHEME.HTTP === 'https') return host + ':443'
23
24 return host + ':80'
25 }
26
27 return host
28}
29
30function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
31 return res.type('json').status(400).end()
32}
33
34function createReqFiles (
35 fieldNames: string[],
36 mimeTypes: { [ id: string ]: string },
37 destinations: { [ fieldName: string ]: string }
38) {
39 const storage = multer.diskStorage({
40 destination: (req, file, cb) => {
41 cb(null, destinations[ file.fieldname ])
42 },
43
44 filename: async (req, file, cb) => {
45 const extension = mimeTypes[ file.mimetype ]
46 let randomString = ''
47
48 try {
49 randomString = await generateRandomString(16)
50 } catch (err) {
51 logger.error('Cannot generate random string for file name.', { err })
52 randomString = 'fake-random-string'
53 }
54
55 cb(null, randomString + extension)
56 }
57 })
58
59 const fields = []
60 for (const fieldName of fieldNames) {
61 fields.push({
62 name: fieldName,
63 maxCount: 1
64 })
65 }
66
67 return multer({ storage }).fields(fields)
68}
69
70// ---------------------------------------------------------------------------
71
72export {
73 isNSFWHidden,
74 getHostWithPort,
75 badRequest,
76 createReqFiles
77}
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index c58117219..058c3211e 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,68 +1,13 @@
1import * as express from 'express'
2import * as multer from 'multer'
3import { Model } from 'sequelize-typescript' 1import { Model } from 'sequelize-typescript'
4import { ResultList } from '../../shared' 2import { ResultList } from '../../shared'
5import { VideoResolution } from '../../shared/models/videos' 3import { VideoResolution } from '../../shared/models/videos'
6import { CONFIG, REMOTE_SCHEME } from '../initializers' 4import { CONFIG } from '../initializers'
7import { UserModel } from '../models/account/user' 5import { UserModel } from '../models/account/user'
8import { ActorModel } from '../models/activitypub/actor' 6import { ActorModel } from '../models/activitypub/actor'
9import { ApplicationModel } from '../models/application/application' 7import { ApplicationModel } from '../models/application/application'
10import { pseudoRandomBytesPromise } from './core-utils' 8import { pseudoRandomBytesPromise } from './core-utils'
11import { logger } from './logger' 9import { logger } from './logger'
12 10
13function getHostWithPort (host: string) {
14 const splitted = host.split(':')
15
16 // The port was not specified
17 if (splitted.length === 1) {
18 if (REMOTE_SCHEME.HTTP === 'https') return host + ':443'
19
20 return host + ':80'
21 }
22
23 return host
24}
25
26function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
27 return res.type('json').status(400).end()
28}
29
30function createReqFiles (
31 fieldNames: string[],
32 mimeTypes: { [ id: string ]: string },
33 destinations: { [ fieldName: string ]: string }
34) {
35 const storage = multer.diskStorage({
36 destination: (req, file, cb) => {
37 cb(null, destinations[file.fieldname])
38 },
39
40 filename: async (req, file, cb) => {
41 const extension = mimeTypes[file.mimetype]
42 let randomString = ''
43
44 try {
45 randomString = await generateRandomString(16)
46 } catch (err) {
47 logger.error('Cannot generate random string for file name.', { err })
48 randomString = 'fake-random-string'
49 }
50
51 cb(null, randomString + extension)
52 }
53 })
54
55 const fields = []
56 for (const fieldName of fieldNames) {
57 fields.push({
58 name: fieldName,
59 maxCount: 1
60 })
61 }
62
63 return multer({ storage }).fields(fields)
64}
65
66async function generateRandomString (size: number) { 11async function generateRandomString (size: number) {
67 const raw = await pseudoRandomBytesPromise(size) 12 const raw = await pseudoRandomBytesPromise(size)
68 13
@@ -151,14 +96,11 @@ type SortType = { sortModel: any, sortValue: string }
151// --------------------------------------------------------------------------- 96// ---------------------------------------------------------------------------
152 97
153export { 98export {
154 badRequest,
155 generateRandomString, 99 generateRandomString,
156 getFormattedObjects, 100 getFormattedObjects,
157 isSignupAllowed, 101 isSignupAllowed,
158 computeResolutionsToTranscode, 102 computeResolutionsToTranscode,
159 resetSequelizeInstance, 103 resetSequelizeInstance,
160 getServerActor, 104 getServerActor,
161 SortType, 105 SortType
162 getHostWithPort,
163 createReqFiles
164} 106}
diff --git a/server/middlewares/servers.ts b/server/middlewares/servers.ts
index a9dcad2d4..c52f4685b 100644
--- a/server/middlewares/servers.ts
+++ b/server/middlewares/servers.ts
@@ -1,6 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'express-validator' 2import 'express-validator'
3import { getHostWithPort } from '../helpers/utils' 3import { getHostWithPort } from '../helpers/express-utils'
4 4
5function setBodyHostsPort (req: express.Request, res: express.Response, next: express.NextFunction) { 5function setBodyHostsPort (req: express.Request, res: express.Response, next: express.NextFunction) {
6 if (!req.body.hosts) return next() 6 if (!req.body.hosts) return next()
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index 3dbec6e44..3b9645048 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -2,9 +2,9 @@ import * as express from 'express'
2import { query } from 'express-validator/check' 2import { query } from 'express-validator/check'
3import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' 3import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { getHostWithPort } from '../../helpers/utils'
6import { ActorModel } from '../../models/activitypub/actor' 5import { ActorModel } from '../../models/activitypub/actor'
7import { areValidationErrors } from './utils' 6import { areValidationErrors } from './utils'
7import { getHostWithPort } from '../../helpers/express-utils'
8 8
9const webfingerValidator = [ 9const webfingerValidator = [
10 query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'), 10 query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'),
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index b0fff6526..2ad9c00dd 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -95,7 +95,33 @@ enum ScopeNames {
95} 95}
96 96
97@Scopes({ 97@Scopes({
98 [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean) => { 98 [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean, accountId?: number) => {
99 const accountInclude = {
100 attributes: [ 'name' ],
101 model: AccountModel.unscoped(),
102 required: true,
103 where: {},
104 include: [
105 {
106 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
107 model: ActorModel.unscoped(),
108 required: true,
109 where: VideoModel.buildActorWhereWithFilter(filter),
110 include: [
111 {
112 attributes: [ 'host' ],
113 model: ServerModel.unscoped(),
114 required: false
115 },
116 {
117 model: AvatarModel.unscoped(),
118 required: false
119 }
120 ]
121 }
122 ]
123 }
124
99 const query: IFindOptions<VideoModel> = { 125 const query: IFindOptions<VideoModel> = {
100 where: { 126 where: {
101 id: { 127 id: {
@@ -125,30 +151,7 @@ enum ScopeNames {
125 model: VideoChannelModel.unscoped(), 151 model: VideoChannelModel.unscoped(),
126 required: true, 152 required: true,
127 include: [ 153 include: [
128 { 154 accountInclude
129 attributes: [ 'name' ],
130 model: AccountModel.unscoped(),
131 required: true,
132 include: [
133 {
134 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
135 model: ActorModel.unscoped(),
136 required: true,
137 where: VideoModel.buildActorWhereWithFilter(filter),
138 include: [
139 {
140 attributes: [ 'host' ],
141 model: ServerModel.unscoped(),
142 required: false
143 },
144 {
145 model: AvatarModel.unscoped(),
146 required: false
147 }
148 ]
149 }
150 ]
151 }
152 ] 155 ]
153 } 156 }
154 ] 157 ]
@@ -166,6 +169,12 @@ enum ScopeNames {
166 query.where['nsfw'] = false 169 query.where['nsfw'] = false
167 } 170 }
168 171
172 if (accountId) {
173 accountInclude.where = {
174 id: accountId
175 }
176 }
177
169 return query 178 return query
170 }, 179 },
171 [ScopeNames.WITH_ACCOUNT_DETAILS]: { 180 [ScopeNames.WITH_ACCOUNT_DETAILS]: {
@@ -688,7 +697,15 @@ export class VideoModel extends Model<VideoModel> {
688 }) 697 })
689 } 698 }
690 699
691 static async listForApi (start: number, count: number, sort: string, hideNSFW: boolean, filter?: VideoFilter, withFiles = false) { 700 static async listForApi (
701 start: number,
702 count: number,
703 sort: string,
704 hideNSFW: boolean,
705 filter?: VideoFilter,
706 withFiles = false,
707 accountId?: number
708 ) {
692 const query = { 709 const query = {
693 offset: start, 710 offset: start,
694 limit: count, 711 limit: count,
@@ -696,7 +713,7 @@ export class VideoModel extends Model<VideoModel> {
696 } 713 }
697 714
698 const serverActor = await getServerActor() 715 const serverActor = await getServerActor()
699 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles ] }) 716 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles, accountId ] })
700 .findAndCountAll(query) 717 .findAndCountAll(query)
701 .then(({ rows, count }) => { 718 .then(({ rows, count }) => {
702 return { 719 return {
@@ -879,8 +896,6 @@ export class VideoModel extends Model<VideoModel> {
879 896
880 private static getLanguageLabel (id: string) { 897 private static getLanguageLabel (id: string) {
881 let languageLabel = VIDEO_LANGUAGES[id] 898 let languageLabel = VIDEO_LANGUAGES[id]
882 console.log(VIDEO_LANGUAGES)
883 console.log(id)
884 if (!languageLabel) languageLabel = 'Unknown' 899 if (!languageLabel) languageLabel = 'Unknown'
885 900
886 return languageLabel 901 return languageLabel