aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/activitypub/process/process-create.ts
blob: 99a5f6f9c0b73b40a2d5d948f011357e0b732aa4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import { ActivityCreate, VideoTorrentObject } from '../../../../shared'
import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { logger } from '../../../helpers/logger'
import { sequelizeTypescript } from '../../../initializers'
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
import { ActorModel } from '../../../models/activitypub/actor'
import { VideoAbuseModel } from '../../../models/video/video-abuse'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { getOrCreateActorAndServerAndModel } from '../actor'
import { getActorsInvolvedInVideo } from '../audience'
import { resolveThread } from '../video-comments'
import { getOrCreateAccountAndVideoAndChannel } from '../videos'
import { forwardActivity } from '../send/utils'

async function processCreateActivity (activity: ActivityCreate) {
  const activityObject = activity.object
  const activityType = activityObject.type
  const actor = await getOrCreateActorAndServerAndModel(activity.actor)

  if (activityType === 'View') {
    return processCreateView(actor, activity)
  } else if (activityType === 'Dislike') {
    return processCreateDislike(actor, activity)
  } else if (activityType === 'Video') {
    return processCreateVideo(actor, activity)
  } else if (activityType === 'Flag') {
    return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject)
  } else if (activityType === 'Note') {
    return processCreateVideoComment(actor, activity)
  }

  logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
  return Promise.resolve(undefined)
}

// ---------------------------------------------------------------------------

export {
  processCreateActivity
}

// ---------------------------------------------------------------------------

async function processCreateVideo (
  actor: ActorModel,
  activity: ActivityCreate
) {
  const videoToCreateData = activity.object as VideoTorrentObject

  const { video } = await getOrCreateAccountAndVideoAndChannel(videoToCreateData, actor)

  return video
}

async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
  const options = {
    arguments: [ byActor, activity ],
    errorMessage: 'Cannot dislike the video with many retries.'
  }

  return retryTransactionWrapper(createVideoDislike, options)
}

async function createVideoDislike (byActor: ActorModel, activity: ActivityCreate) {
  const dislike = activity.object as DislikeObject
  const byAccount = byActor.Account

  if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)

  const { video } = await getOrCreateAccountAndVideoAndChannel(dislike.object)

  return sequelizeTypescript.transaction(async t => {
    const rate = {
      type: 'dislike' as 'dislike',
      videoId: video.id,
      accountId: byAccount.id
    }
    const [ , created ] = await AccountVideoRateModel.findOrCreate({
      where: rate,
      defaults: rate,
      transaction: t
    })
    if (created === true) await video.increment('dislikes', { transaction: t })

    if (video.isOwned() && created === true) {
      // Don't resend the activity to the sender
      const exceptions = [ byActor ]
      await forwardActivity(activity, t, exceptions)
    }
  })
}

async function processCreateView (byActor: ActorModel, activity: ActivityCreate) {
  const view = activity.object as ViewObject

  const { video } = await getOrCreateAccountAndVideoAndChannel(view.object)

  const actor = await ActorModel.loadByUrl(view.actor)
  if (!actor) throw new Error('Unknown actor ' + view.actor)

  await video.increment('views')

  if (video.isOwned()) {
    // Don't resend the activity to the sender
    const exceptions = [ byActor ]
    await forwardActivity(activity, undefined, exceptions)
  }
}

function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
  const options = {
    arguments: [ actor, videoAbuseToCreateData ],
    errorMessage: 'Cannot insert the remote video abuse with many retries.'
  }

  return retryTransactionWrapper(addRemoteVideoAbuse, options)
}

async function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
  logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)

  const account = actor.Account
  if (!account) throw new Error('Cannot create dislike with the non account actor ' + actor.url)

  const { video } = await getOrCreateAccountAndVideoAndChannel(videoAbuseToCreateData.object)

  return sequelizeTypescript.transaction(async t => {
    const videoAbuseData = {
      reporterAccountId: account.id,
      reason: videoAbuseToCreateData.content,
      videoId: video.id
    }

    await VideoAbuseModel.create(videoAbuseData)

    logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
  })
}

function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) {
  const options = {
    arguments: [ byActor, activity ],
    errorMessage: 'Cannot create video comment with many retries.'
  }

  return retryTransactionWrapper(createVideoComment, options)
}

async function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
  const comment = activity.object as VideoCommentObject
  const byAccount = byActor.Account

  if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)

  const { video, parents } = await resolveThread(comment.inReplyTo)

  return sequelizeTypescript.transaction(async t => {
    let originCommentId = null
    let inReplyToCommentId = null

    if (parents.length !== 0) {
      const parent = parents[0]

      originCommentId = parent.getThreadId()
      inReplyToCommentId = parent.id
    }

    // This is a new thread
    const objectToCreate = {
      url: comment.id,
      text: comment.content,
      originCommentId,
      inReplyToCommentId,
      videoId: video.id,
      accountId: byAccount.id
    }

    const options = {
      where: {
        url: objectToCreate.url
      },
      defaults: objectToCreate,
      transaction: t
    }
    const [ ,created ] = await VideoCommentModel.findOrCreate(options)

    if (video.isOwned() && created === true) {
      // Don't resend the activity to the sender
      const exceptions = [ byActor ]

      // Mastodon does not add our announces in audience, so we forward to them manually
      const additionalActors = await getActorsInvolvedInVideo(video, t)
      const additionalFollowerUrls = additionalActors.map(a => a.followersUrl)

      await forwardActivity(activity, t, exceptions, additionalFollowerUrls)
    }
  })
}