import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChannelValidator, signatureValidator } from '../../middlewares'
import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
import { queue } from 'async'
-import { ActorModel } from '../../models/activitypub/actor'
import { MActorDefault, MActorSignature } from '../../typings/models'
const inboxRouter = express.Router()
import { peertubeTruncate } from '@server/helpers/core-utils'
function isActorEndpointsObjectValid (endpointObject: any) {
- return isActivityPubUrlValid(endpointObject.sharedInbox)
+ if (endpointObject && endpointObject.sharedInbox) {
+ return isActivityPubUrlValid(endpointObject.sharedInbox)
+ }
+
+ // Shared inbox is optional
+ return true
}
function isActorPublicKeyObjectValid (publicKeyObject: any) {
}
function isActorTypeValid (type: string) {
- return type === 'Person' || type === 'Application' || type === 'Group'
+ return type === 'Person' || type === 'Application' || type === 'Group' || type === 'Service' || type === 'Organization'
}
function isActorPublicKeyValid (publicKey: string) {
}
function normalizeActor (actor: any) {
- if (!actor || !actor.url) return
+ if (!actor) return
- if (typeof actor.url !== 'string') {
+ if (!actor.url) {
+ actor.url = actor.id
+ } else if (typeof actor.url !== 'string') {
actor.url = actor.url.href || actor.url.url
}
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 440
+const LAST_MIGRATION_VERSION = 445
// ---------------------------------------------------------------------------
const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
GROUP: 'Group',
PERSON: 'Person',
- APPLICATION: 'Application'
+ APPLICATION: 'Application',
+ ORGANIZATION: 'Organization',
+ SERVICE: 'Service'
}
const HTTP_SIGNATURE = {
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction,
+ queryInterface: Sequelize.QueryInterface,
+ sequelize: Sequelize.Sequelize,
+ db: any
+}): Promise<void> {
+ {
+ const data = {
+ type: Sequelize.STRING,
+ allowNull: true
+ }
+
+ await utils.queryInterface.changeColumn('actor', 'sharedInboxUrl', data)
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
actorInstance.followingCount = followingCount
actorInstance.inboxUrl = attributes.inbox
actorInstance.outboxUrl = attributes.outbox
- actorInstance.sharedInboxUrl = attributes.endpoints.sharedInbox
actorInstance.followersUrl = attributes.followers
actorInstance.followingUrl = attributes.following
+
+ if (attributes.endpoints && attributes.endpoints.sharedInbox) {
+ actorInstance.sharedInboxUrl = attributes.endpoints.sharedInbox
+ }
}
type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string }
followingCount: followingCount,
inboxUrl: actorJSON.inbox,
outboxUrl: actorJSON.outbox,
- sharedInboxUrl: actorJSON.endpoints.sharedInbox,
followersUrl: actorJSON.followers,
- followingUrl: actorJSON.following
+ followingUrl: actorJSON.following,
+
+ sharedInboxUrl: actorJSON.endpoints && actorJSON.endpoints.sharedInbox
+ ? actorJSON.endpoints.sharedInbox
+ : null,
})
const avatarInfo = await getAvatarInfoIfExists(actorJSON)
if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException)
// Send to origin
- t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
+ t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.getSharedInbox()))
}
function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
if (isVideoOrigin) return broadcastToFollowers(activity, byActor, actorsInvolvedInComment, t, actorsException)
// Send to origin
- t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
+ t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.getSharedInbox()))
}
async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) {
const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] }
const flagActivity = buildFlagActivity(url, byActor, videoAbuse, audience)
- t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl))
+ t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.getSharedInbox()))
}
function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag {
import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
import { getServerActor } from '../../../helpers/utils'
import { afterCommitIfTransaction } from '../../../helpers/database-utils'
-import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
+import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
byActor: MActorLight,
const activity = activityBuilder(audience)
return afterCommitIfTransaction(transaction, () => {
- return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
+ return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox())
})
}
async function forwardVideoRelatedActivity (
activity: Activity,
t: Transaction,
- followersException: MActorFollowerException[] = [],
+ followersException: MActorWithInboxes[] = [],
video: MVideo
) {
// Mastodon does not add our announces in audience, so we forward to them manually
async function forwardActivity (
activity: Activity,
t: Transaction,
- followersException: MActorFollowerException[] = [],
+ followersException: MActorWithInboxes[] = [],
additionalFollowerUrls: string[] = []
) {
logger.info('Forwarding activity %s.', activity.id)
byActor: MActorId,
toFollowersOf: MActorId[],
t: Transaction,
- actorsException: MActorFollowerException[] = []
+ actorsException: MActorWithInboxes[] = []
) {
const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
byActor: MActorId,
toActors: MActor[],
t?: Transaction,
- actorsException: MActorFollowerException[] = []
+ actorsException: MActorWithInboxes[] = []
) {
const uris = await computeUris(toActors, actorsException)
return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
// ---------------------------------------------------------------------------
-async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) {
+async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorWithInboxes[], t: Transaction) {
const toActorFollowerIds = toFollowersOf.map(a => a.id)
const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
}
-async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) {
+async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) {
const serverActor = await getServerActor()
const targetUrls = toActors
.filter(a => a.id !== serverActor.id) // Don't send to ourselves
- .map(a => a.sharedInboxUrl || a.inboxUrl)
+ .map(a => a.getSharedInbox())
const toActorSharedInboxesSet = new Set(targetUrls)
.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
}
-async function buildSharedInboxesException (actorsException: MActorFollowerException[]) {
+async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) {
const serverActor = await getServerActor()
return actorsException
- .map(f => f.sharedInboxUrl || f.inboxUrl)
+ .map(f => f.getSharedInbox())
.concat([ serverActor.sharedInboxUrl ])
}
}
const selections: string[] = []
- if (distinct === true) selections.push('DISTINCT("Follows"."' + columnUrl + '") AS "url"')
- else selections.push('"Follows"."' + columnUrl + '" AS "url"')
+ if (distinct === true) selections.push('DISTINCT("Follows"."' + columnUrl + '") AS "selectionUrl"')
+ else selections.push('"Follows"."' + columnUrl + '" AS "selectionUrl"')
selections.push('COUNT(*) AS "total"')
let query = 'SELECT ' + selection + ' FROM "actor" ' +
'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' +
'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' +
- 'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' '
+ 'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' AND "selectionUrl" IS NOT NULL '
if (count !== undefined) query += 'LIMIT ' + count
if (start !== undefined) query += ' OFFSET ' + start
}
const [ followers, [ dataTotal ] ] = await Promise.all(tasks)
- const urls: string[] = followers.map(f => f.url)
+ const urls: string[] = followers.map(f => f.selectionUrl)
return {
data: urls,
MActorFull,
MActorHost,
MActorServer,
- MActorSummaryFormattable
+ MActorSummaryFormattable,
+ MActorWithInboxes
} from '../../typings/models'
import * as Bluebird from 'bluebird'
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
outboxUrl: string
- @AllowNull(false)
- @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
+ @AllowNull(true)
+ @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url', true))
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
sharedInboxUrl: string
})
}
+ getSharedInbox (this: MActorWithInboxes) {
+ return this.sharedInboxUrl || this.inboxUrl
+ }
+
toFormattedSummaryJSON (this: MActorSummaryFormattable) {
let avatar: Avatar = null
if (this.Avatar) {
export type MActorFollowersUrl = Pick<MActor, 'followersUrl'>
export type MActorAudience = MActorUrl & MActorFollowersUrl
-export type MActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'>
+export type MActorWithInboxes = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl' | 'getSharedInbox'>
export type MActorSignature = MActorAccountChannelId
export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'>
import { ActivityPubAttributedTo } from './objects/common-objects'
-export type ActivityPubActorType = 'Person' | 'Application' | 'Group'
+export type ActivityPubActorType = 'Person' | 'Application' | 'Group' | 'Service' | 'Organization'
export interface ActivityPubActor {
'@context': any[]