+@DefaultScope(() => ({
+ include: [
+ {
+ model: ActorModel,
+ required: true
+ }
+ ]
+}))
+@Scopes(() => ({
+ [ScopeNames.FOR_API]: (options: AvailableForListOptions) => {
+ // Only list local channels OR channels that are on an instance followed by actorId
+ const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId)
+
+ const whereActorAnd: WhereOptions[] = [
+ {
+ [Op.or]: [
+ {
+ serverId: null
+ },
+ {
+ serverId: {
+ [Op.in]: Sequelize.literal(inQueryInstanceFollow)
+ }
+ }
+ ]
+ }
+ ]
+
+ let serverRequired = false
+ let whereServer: WhereOptions
+
+ if (options.host && options.host !== WEBSERVER.HOST) {
+ serverRequired = true
+ whereServer = { host: options.host }
+ }
+
+ if (options.host === WEBSERVER.HOST) {
+ whereActorAnd.push({
+ serverId: null
+ })
+ }
+
+ if (Array.isArray(options.handles) && options.handles.length !== 0) {
+ const or: string[] = []
+
+ for (const handle of options.handles || []) {
+ const [ preferredUsername, host ] = handle.split('@')
+
+ if (!host || host === WEBSERVER.HOST) {
+ or.push(`("preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} AND "serverId" IS NULL)`)
+ } else {
+ or.push(
+ `(` +
+ `"preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} ` +
+ `AND "host" = ${VideoChannelModel.sequelize.escape(host)}` +
+ `)`
+ )
+ }
+ }
+
+ whereActorAnd.push({
+ id: {
+ [Op.in]: literal(`(SELECT "actor".id FROM actor LEFT JOIN server on server.id = actor."serverId" WHERE ${or.join(' OR ')})`)
+ }
+ })
+ }
+
+ const channelActorInclude: Includeable[] = []
+ const accountActorInclude: Includeable[] = []
+
+ if (options.forCount !== true) {
+ accountActorInclude.push({
+ model: ServerModel,
+ required: false
+ })
+
+ accountActorInclude.push({
+ model: ActorImageModel,
+ as: 'Avatars',
+ required: false
+ })
+
+ channelActorInclude.push({
+ model: ActorImageModel,
+ as: 'Avatars',
+ required: false
+ })
+
+ channelActorInclude.push({
+ model: ActorImageModel,
+ as: 'Banners',
+ required: false
+ })
+ }
+
+ if (options.forCount !== true || serverRequired) {
+ channelActorInclude.push({
+ model: ServerModel,
+ duplicating: false,
+ required: serverRequired,
+ where: whereServer
+ })
+ }
+
+ return {
+ include: [
+ {
+ attributes: {
+ exclude: unusedActorAttributesForAPI
+ },
+ model: ActorModel.unscoped(),
+ where: {
+ [Op.and]: whereActorAnd
+ },
+ include: channelActorInclude
+ },
+ {
+ model: AccountModel.unscoped(),
+ required: true,
+ include: [
+ {
+ attributes: {
+ exclude: unusedActorAttributesForAPI
+ },
+ model: ActorModel.unscoped(),
+ required: true,
+ include: accountActorInclude
+ }
+ ]
+ }
+ ]
+ }
+ },
+ [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
+ const include: Includeable[] = [
+ {
+ attributes: [ 'id', 'preferredUsername', 'url', 'serverId' ],
+ model: ActorModel.unscoped(),
+ required: options.actorRequired ?? true,
+ include: [
+ {
+ attributes: [ 'host' ],
+ model: ServerModel.unscoped(),
+ required: false
+ },
+ {
+ model: ActorImageModel,
+ as: 'Avatars',
+ required: false
+ }
+ ]
+ }
+ ]
+
+ const base: FindOptions = {
+ attributes: [ 'id', 'name', 'description', 'actorId' ]
+ }
+
+ if (options.withAccount === true) {
+ include.push({
+ model: AccountModel.scope({
+ method: [ AccountModelScopeNames.SUMMARY, { withAccountBlockerIds: options.withAccountBlockerIds } as AccountSummaryOptions ]
+ }),
+ required: true
+ })
+ }
+
+ base.include = include
+
+ return base
+ },