diff options
author | Chocobozzz <me@florianbigard.com> | 2021-07-29 10:27:24 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-07-29 10:27:24 +0200 |
commit | b033851fb54241bb703f86add025229e68cc6f59 (patch) | |
tree | d60dac1499a95bf9dc902dee24f325fe0f8b6acb | |
parent | fbd67e7f386504e50f2504cb6386700a58906f16 (diff) | |
download | PeerTube-b033851fb54241bb703f86add025229e68cc6f59.tar.gz PeerTube-b033851fb54241bb703f86add025229e68cc6f59.tar.zst PeerTube-b033851fb54241bb703f86add025229e68cc6f59.zip |
Search channels against handles and not names
-rw-r--r-- | server/controllers/api/search/search-video-channels.ts | 2 | ||||
-rw-r--r-- | server/helpers/custom-validators/misc.ts | 5 | ||||
-rw-r--r-- | server/middlewares/validators/search.ts | 7 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 36 | ||||
-rw-r--r-- | server/tests/api/check-params/search.ts | 4 | ||||
-rw-r--r-- | server/tests/api/search/search-channels.ts | 17 | ||||
-rw-r--r-- | shared/core-utils/index.ts | 1 | ||||
-rw-r--r-- | shared/core-utils/utils/index.ts | 1 | ||||
-rw-r--r-- | shared/core-utils/utils/object.ts | 15 | ||||
-rw-r--r-- | shared/extra-utils/moderation/abuses-command.ts | 6 | ||||
-rw-r--r-- | shared/extra-utils/server/follows-command.ts | 8 | ||||
-rw-r--r-- | shared/extra-utils/server/jobs-command.ts | 2 | ||||
-rw-r--r-- | shared/extra-utils/users/users-command.ts | 3 | ||||
-rw-r--r-- | shared/extra-utils/videos/channels-command.ts | 2 | ||||
-rw-r--r-- | shared/extra-utils/videos/playlists-command.ts | 3 | ||||
-rw-r--r-- | shared/extra-utils/videos/videos-command.ts | 3 | ||||
-rw-r--r-- | shared/models/search/video-channels-search-query.model.ts | 2 |
17 files changed, 85 insertions, 32 deletions
diff --git a/server/controllers/api/search/search-video-channels.ts b/server/controllers/api/search/search-video-channels.ts index 9fc2d53a5..ae32a6726 100644 --- a/server/controllers/api/search/search-video-channels.ts +++ b/server/controllers/api/search/search-video-channels.ts | |||
@@ -100,7 +100,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr | |||
100 | count: query.count, | 100 | count: query.count, |
101 | sort: query.sort, | 101 | sort: query.sort, |
102 | host: query.host, | 102 | host: query.host, |
103 | names: query.names | 103 | handles: query.handles |
104 | }, 'filter:api.search.video-channels.local.list.params') | 104 | }, 'filter:api.search.video-channels.local.list.params') |
105 | 105 | ||
106 | const resultList = await Hooks.wrapPromiseFun( | 106 | const resultList = await Hooks.wrapPromiseFun( |
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index f8f168149..c19a3e5eb 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -23,6 +23,10 @@ function isNotEmptyIntArray (value: any) { | |||
23 | return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 | 23 | return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 |
24 | } | 24 | } |
25 | 25 | ||
26 | function isNotEmptyStringArray (value: any) { | ||
27 | return Array.isArray(value) && value.every(v => typeof v === 'string' && v.length !== 0) && value.length !== 0 | ||
28 | } | ||
29 | |||
26 | function isArrayOf (value: any, validator: (value: any) => boolean) { | 30 | function isArrayOf (value: any, validator: (value: any) => boolean) { |
27 | return isArray(value) && value.every(v => validator(v)) | 31 | return isArray(value) && value.every(v => validator(v)) |
28 | } | 32 | } |
@@ -187,6 +191,7 @@ export { | |||
187 | isIntOrNull, | 191 | isIntOrNull, |
188 | isIdValid, | 192 | isIdValid, |
189 | isSafePath, | 193 | isSafePath, |
194 | isNotEmptyStringArray, | ||
190 | isUUIDValid, | 195 | isUUIDValid, |
191 | toCompleteUUIDs, | 196 | toCompleteUUIDs, |
192 | toCompleteUUID, | 197 | toCompleteUUID, |
diff --git a/server/middlewares/validators/search.ts b/server/middlewares/validators/search.ts index cde300968..27d0e541d 100644 --- a/server/middlewares/validators/search.ts +++ b/server/middlewares/validators/search.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
3 | import { isSearchTargetValid } from '@server/helpers/custom-validators/search' | 3 | import { isSearchTargetValid } from '@server/helpers/custom-validators/search' |
4 | import { isHostValid } from '@server/helpers/custom-validators/servers' | 4 | import { isHostValid } from '@server/helpers/custom-validators/servers' |
5 | import { areUUIDsValid, isDateValid, toCompleteUUIDs } from '../../helpers/custom-validators/misc' | 5 | import { areUUIDsValid, isDateValid, isNotEmptyStringArray, toCompleteUUIDs } from '../../helpers/custom-validators/misc' |
6 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
7 | import { areValidationErrors } from './shared' | 7 | import { areValidationErrors } from './shared' |
8 | 8 | ||
@@ -64,9 +64,10 @@ const videoChannelsListSearchValidator = [ | |||
64 | .optional() | 64 | .optional() |
65 | .custom(isSearchTargetValid).withMessage('Should have a valid search target'), | 65 | .custom(isSearchTargetValid).withMessage('Should have a valid search target'), |
66 | 66 | ||
67 | query('names') | 67 | query('handles') |
68 | .optional() | 68 | .optional() |
69 | .toArray(), | 69 | .toArray() |
70 | .custom(isNotEmptyStringArray).withMessage('Should have valid handles'), | ||
70 | 71 | ||
71 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 72 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
72 | logger.debug('Checking video channels search query', { parameters: req.query }) | 73 | logger.debug('Checking video channels search query', { parameters: req.query }) |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 327f49304..e4b12c517 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -18,7 +18,7 @@ import { | |||
18 | UpdatedAt | 18 | UpdatedAt |
19 | } from 'sequelize-typescript' | 19 | } from 'sequelize-typescript' |
20 | import { MAccountActor } from '@server/types/models' | 20 | import { MAccountActor } from '@server/types/models' |
21 | import { AttributesOnly } from '@shared/core-utils' | 21 | import { AttributesOnly, pick } from '@shared/core-utils' |
22 | import { ActivityPubActor } from '../../../shared/models/activitypub' | 22 | import { ActivityPubActor } from '../../../shared/models/activitypub' |
23 | import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' | 23 | import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' |
24 | import { | 24 | import { |
@@ -59,7 +59,7 @@ type AvailableForListOptions = { | |||
59 | actorId: number | 59 | actorId: number |
60 | search?: string | 60 | search?: string |
61 | host?: string | 61 | host?: string |
62 | names?: string[] | 62 | handles?: string[] |
63 | } | 63 | } |
64 | 64 | ||
65 | type AvailableWithStatsOptions = { | 65 | type AvailableWithStatsOptions = { |
@@ -114,15 +114,33 @@ export type SummaryOptions = { | |||
114 | }) | 114 | }) |
115 | } | 115 | } |
116 | 116 | ||
117 | if (options.names) { | 117 | let rootWhere: WhereOptions |
118 | whereActorAnd.push({ | 118 | if (options.handles) { |
119 | preferredUsername: { | 119 | const or: WhereOptions[] = [] |
120 | [Op.in]: options.names | 120 | |
121 | for (const handle of options.handles || []) { | ||
122 | const [ preferredUsername, host ] = handle.split('@') | ||
123 | |||
124 | if (!host) { | ||
125 | or.push({ | ||
126 | '$Actor.preferredUsername$': preferredUsername, | ||
127 | '$Actor.serverId$': null | ||
128 | }) | ||
129 | } else { | ||
130 | or.push({ | ||
131 | '$Actor.preferredUsername$': preferredUsername, | ||
132 | '$Actor.Server.host$': host | ||
133 | }) | ||
121 | } | 134 | } |
122 | }) | 135 | } |
136 | |||
137 | rootWhere = { | ||
138 | [Op.or]: or | ||
139 | } | ||
123 | } | 140 | } |
124 | 141 | ||
125 | return { | 142 | return { |
143 | where: rootWhere, | ||
126 | include: [ | 144 | include: [ |
127 | { | 145 | { |
128 | attributes: { | 146 | attributes: { |
@@ -473,7 +491,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
473 | sort: string | 491 | sort: string |
474 | 492 | ||
475 | host?: string | 493 | host?: string |
476 | names?: string[] | 494 | handles?: string[] |
477 | }) { | 495 | }) { |
478 | let attributesInclude: any[] = [ literal('0 as similarity') ] | 496 | let attributesInclude: any[] = [ literal('0 as similarity') ] |
479 | let where: WhereOptions | 497 | let where: WhereOptions |
@@ -507,7 +525,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
507 | 525 | ||
508 | return VideoChannelModel | 526 | return VideoChannelModel |
509 | .scope({ | 527 | .scope({ |
510 | method: [ ScopeNames.FOR_API, { actorId: options.actorId, host: options.host, names: options.names } as AvailableForListOptions ] | 528 | method: [ ScopeNames.FOR_API, pick(options, [ 'actorId', 'host', 'handles' ]) as AvailableForListOptions ] |
511 | }) | 529 | }) |
512 | .findAndCountAll(query) | 530 | .findAndCountAll(query) |
513 | .then(({ rows, count }) => { | 531 | .then(({ rows, count }) => { |
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts index 789ea7754..cc15d2593 100644 --- a/server/tests/api/check-params/search.ts +++ b/server/tests/api/check-params/search.ts | |||
@@ -216,6 +216,10 @@ describe('Test videos API validator', function () { | |||
216 | await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | 216 | await makeGetRequest({ url: server.url, path, query: { ...query, host: '6565' }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
217 | }) | 217 | }) |
218 | 218 | ||
219 | it('Should fail with invalid handles', async function () { | ||
220 | await makeGetRequest({ url: server.url, path, query: { ...query, handles: [ '' ] }, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
221 | }) | ||
222 | |||
219 | it('Should succeed with the correct parameters', async function () { | 223 | it('Should succeed with the correct parameters', async function () { |
220 | await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 }) | 224 | await makeGetRequest({ url: server.url, path, query, expectedStatus: HttpStatusCode.OK_200 }) |
221 | }) | 225 | }) |
diff --git a/server/tests/api/search/search-channels.ts b/server/tests/api/search/search-channels.ts index ef78c0f67..4485c424e 100644 --- a/server/tests/api/search/search-channels.ts +++ b/server/tests/api/search/search-channels.ts | |||
@@ -122,18 +122,25 @@ describe('Test channels search', function () { | |||
122 | 122 | ||
123 | it('Should filter by names', async function () { | 123 | it('Should filter by names', async function () { |
124 | { | 124 | { |
125 | const body = await command.advancedChannelSearch({ search: { names: [ 'squall_channel', 'zell_channel' ] } }) | 125 | const body = await command.advancedChannelSearch({ search: { handles: [ 'squall_channel', 'zell_channel' ] } }) |
126 | expect(body.total).to.equal(2) | 126 | expect(body.total).to.equal(1) |
127 | expect(body.data).to.have.lengthOf(2) | 127 | expect(body.data).to.have.lengthOf(1) |
128 | expect(body.data[0].displayName).to.equal('Squall channel') | 128 | expect(body.data[0].displayName).to.equal('Squall channel') |
129 | expect(body.data[1].displayName).to.equal('Zell channel') | ||
130 | } | 129 | } |
131 | 130 | ||
132 | { | 131 | { |
133 | const body = await command.advancedChannelSearch({ search: { names: [ 'chocobozzz_channel' ] } }) | 132 | const body = await command.advancedChannelSearch({ search: { handles: [ 'chocobozzz_channel' ] } }) |
134 | expect(body.total).to.equal(0) | 133 | expect(body.total).to.equal(0) |
135 | expect(body.data).to.have.lengthOf(0) | 134 | expect(body.data).to.have.lengthOf(0) |
136 | } | 135 | } |
136 | |||
137 | { | ||
138 | const body = await command.advancedChannelSearch({ search: { handles: [ 'squall_channel', 'zell_channel@' + remoteServer.host ] } }) | ||
139 | expect(body.total).to.equal(2) | ||
140 | expect(body.data).to.have.lengthOf(2) | ||
141 | expect(body.data[0].displayName).to.equal('Squall channel') | ||
142 | expect(body.data[1].displayName).to.equal('Zell channel') | ||
143 | } | ||
137 | }) | 144 | }) |
138 | 145 | ||
139 | after(async function () { | 146 | after(async function () { |
diff --git a/shared/core-utils/index.ts b/shared/core-utils/index.ts index 66d50ef93..2a7d4d982 100644 --- a/shared/core-utils/index.ts +++ b/shared/core-utils/index.ts | |||
@@ -4,3 +4,4 @@ export * from './i18n' | |||
4 | export * from './plugins' | 4 | export * from './plugins' |
5 | export * from './renderer' | 5 | export * from './renderer' |
6 | export * from './users' | 6 | export * from './users' |
7 | export * from './utils' | ||
diff --git a/shared/core-utils/utils/index.ts b/shared/core-utils/utils/index.ts new file mode 100644 index 000000000..a71977d88 --- /dev/null +++ b/shared/core-utils/utils/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './object' | |||
diff --git a/shared/core-utils/utils/object.ts b/shared/core-utils/utils/object.ts new file mode 100644 index 000000000..7b2bb81d0 --- /dev/null +++ b/shared/core-utils/utils/object.ts | |||
@@ -0,0 +1,15 @@ | |||
1 | function pick <T extends object> (object: T, keys: (keyof T)[]) { | ||
2 | const result: Partial<T> = {} | ||
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 | |||
13 | export { | ||
14 | pick | ||
15 | } | ||
diff --git a/shared/extra-utils/moderation/abuses-command.ts b/shared/extra-utils/moderation/abuses-command.ts index 7b3abb056..0db32ba46 100644 --- a/shared/extra-utils/moderation/abuses-command.ts +++ b/shared/extra-utils/moderation/abuses-command.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { pick } from 'lodash' | 1 | import { pick } from '@shared/core-utils' |
2 | import { | 2 | import { |
3 | AbuseFilter, | 3 | AbuseFilter, |
4 | AbuseMessage, | 4 | AbuseMessage, |
@@ -81,7 +81,7 @@ export class AbusesCommand extends AbstractCommand { | |||
81 | searchVideo?: string | 81 | searchVideo?: string |
82 | searchVideoChannel?: string | 82 | searchVideoChannel?: string |
83 | } = {}) { | 83 | } = {}) { |
84 | const toPick = [ | 84 | const toPick: (keyof typeof options)[] = [ |
85 | 'count', | 85 | 'count', |
86 | 'filter', | 86 | 'filter', |
87 | 'id', | 87 | 'id', |
@@ -121,7 +121,7 @@ export class AbusesCommand extends AbstractCommand { | |||
121 | search?: string | 121 | search?: string |
122 | state?: AbuseState | 122 | state?: AbuseState |
123 | }) { | 123 | }) { |
124 | const toPick = [ | 124 | const toPick: (keyof typeof options)[] = [ |
125 | 'id', | 125 | 'id', |
126 | 'search', | 126 | 'search', |
127 | 'state', | 127 | 'state', |
diff --git a/shared/extra-utils/server/follows-command.ts b/shared/extra-utils/server/follows-command.ts index 2b889cf66..01ef6f179 100644 --- a/shared/extra-utils/server/follows-command.ts +++ b/shared/extra-utils/server/follows-command.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { pick } from 'lodash' | 1 | import { pick } from '@shared/core-utils' |
2 | import { ActivityPubActorType, ActorFollow, FollowState, HttpStatusCode, ResultList, ServerFollowCreate } from '@shared/models' | 2 | import { ActivityPubActorType, ActorFollow, FollowState, HttpStatusCode, ResultList, ServerFollowCreate } from '@shared/models' |
3 | import { AbstractCommand, OverrideCommandOptions } from '../shared' | 3 | import { AbstractCommand, OverrideCommandOptions } from '../shared' |
4 | import { PeerTubeServer } from './server' | 4 | import { PeerTubeServer } from './server' |
@@ -15,8 +15,7 @@ export class FollowsCommand extends AbstractCommand { | |||
15 | }) { | 15 | }) { |
16 | const path = '/api/v1/server/followers' | 16 | const path = '/api/v1/server/followers' |
17 | 17 | ||
18 | const toPick = [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ] | 18 | const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]) |
19 | const query = pick(options, toPick) | ||
20 | 19 | ||
21 | return this.getRequestBody<ResultList<ActorFollow>>({ | 20 | return this.getRequestBody<ResultList<ActorFollow>>({ |
22 | ...options, | 21 | ...options, |
@@ -38,8 +37,7 @@ export class FollowsCommand extends AbstractCommand { | |||
38 | } = {}) { | 37 | } = {}) { |
39 | const path = '/api/v1/server/following' | 38 | const path = '/api/v1/server/following' |
40 | 39 | ||
41 | const toPick = [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ] | 40 | const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]) |
42 | const query = pick(options, toPick) | ||
43 | 41 | ||
44 | return this.getRequestBody<ResultList<ActorFollow>>({ | 42 | return this.getRequestBody<ResultList<ActorFollow>>({ |
45 | ...options, | 43 | ...options, |
diff --git a/shared/extra-utils/server/jobs-command.ts b/shared/extra-utils/server/jobs-command.ts index 09a299e5b..c4eb12dc2 100644 --- a/shared/extra-utils/server/jobs-command.ts +++ b/shared/extra-utils/server/jobs-command.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { pick } from 'lodash' | 1 | import { pick } from '@shared/core-utils' |
2 | import { HttpStatusCode } from '@shared/models' | 2 | import { HttpStatusCode } from '@shared/models' |
3 | import { Job, JobState, JobType, ResultList } from '../../models' | 3 | import { Job, JobState, JobType, ResultList } from '../../models' |
4 | import { AbstractCommand, OverrideCommandOptions } from '../shared' | 4 | import { AbstractCommand, OverrideCommandOptions } from '../shared' |
diff --git a/shared/extra-utils/users/users-command.ts b/shared/extra-utils/users/users-command.ts index d66ad15f2..ddd20d041 100644 --- a/shared/extra-utils/users/users-command.ts +++ b/shared/extra-utils/users/users-command.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { omit, pick } from 'lodash' | 1 | import { omit } from 'lodash' |
2 | import { pick } from '@shared/core-utils' | ||
2 | import { | 3 | import { |
3 | HttpStatusCode, | 4 | HttpStatusCode, |
4 | MyUser, | 5 | MyUser, |
diff --git a/shared/extra-utils/videos/channels-command.ts b/shared/extra-utils/videos/channels-command.ts index f8eb3f885..255e1d62d 100644 --- a/shared/extra-utils/videos/channels-command.ts +++ b/shared/extra-utils/videos/channels-command.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { pick } from 'lodash' | 1 | import { pick } from '@shared/core-utils' |
2 | import { HttpStatusCode, ResultList, VideoChannel, VideoChannelCreateResult } from '@shared/models' | 2 | import { HttpStatusCode, ResultList, VideoChannel, VideoChannelCreateResult } from '@shared/models' |
3 | import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' | 3 | import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' |
4 | import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' | 4 | import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' |
diff --git a/shared/extra-utils/videos/playlists-command.ts b/shared/extra-utils/videos/playlists-command.ts index 6f329800e..ce23900d3 100644 --- a/shared/extra-utils/videos/playlists-command.ts +++ b/shared/extra-utils/videos/playlists-command.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { omit, pick } from 'lodash' | 1 | import { omit } from 'lodash' |
2 | import { pick } from '@shared/core-utils' | ||
2 | import { | 3 | import { |
3 | BooleanBothQuery, | 4 | BooleanBothQuery, |
4 | HttpStatusCode, | 5 | HttpStatusCode, |
diff --git a/shared/extra-utils/videos/videos-command.ts b/shared/extra-utils/videos/videos-command.ts index 98465e8f6..33725bfdc 100644 --- a/shared/extra-utils/videos/videos-command.ts +++ b/shared/extra-utils/videos/videos-command.ts | |||
@@ -3,10 +3,11 @@ | |||
3 | import { expect } from 'chai' | 3 | import { expect } from 'chai' |
4 | import { createReadStream, stat } from 'fs-extra' | 4 | import { createReadStream, stat } from 'fs-extra' |
5 | import got, { Response as GotResponse } from 'got' | 5 | import got, { Response as GotResponse } from 'got' |
6 | import { omit, pick } from 'lodash' | 6 | import { omit } from 'lodash' |
7 | import validator from 'validator' | 7 | import validator from 'validator' |
8 | import { buildUUID } from '@server/helpers/uuid' | 8 | import { buildUUID } from '@server/helpers/uuid' |
9 | import { loadLanguages } from '@server/initializers/constants' | 9 | import { loadLanguages } from '@server/initializers/constants' |
10 | import { pick } from '@shared/core-utils' | ||
10 | import { | 11 | import { |
11 | HttpStatusCode, | 12 | HttpStatusCode, |
12 | ResultList, | 13 | ResultList, |
diff --git a/shared/models/search/video-channels-search-query.model.ts b/shared/models/search/video-channels-search-query.model.ts index 50c59d41d..77cea4a59 100644 --- a/shared/models/search/video-channels-search-query.model.ts +++ b/shared/models/search/video-channels-search-query.model.ts | |||
@@ -8,5 +8,5 @@ export interface VideoChannelsSearchQuery extends SearchTargetQuery { | |||
8 | sort?: string | 8 | sort?: string |
9 | 9 | ||
10 | host?: string | 10 | host?: string |
11 | names?: string[] | 11 | handles?: string[] |
12 | } | 12 | } |