process-create.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
  2. import { isRedundancyAccepted } from '@server/lib/redundancy'
  3. import { VideoModel } from '@server/models/video/video'
  4. import { ActivityCreate, CacheFileObject, PlaylistObject, VideoCommentObject, VideoObject, WatchActionObject } from '@shared/models'
  5. import { retryTransactionWrapper } from '../../../helpers/database-utils'
  6. import { logger } from '../../../helpers/logger'
  7. import { sequelizeTypescript } from '../../../initializers/database'
  8. import { APProcessorOptions } from '../../../types/activitypub-processor.model'
  9. import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
  10. import { Notifier } from '../../notifier'
  11. import { createOrUpdateCacheFile } from '../cache-file'
  12. import { createOrUpdateLocalVideoViewer } from '../local-video-viewer'
  13. import { createOrUpdateVideoPlaylist } from '../playlists'
  14. import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
  15. import { resolveThread } from '../video-comments'
  16. import { getOrCreateAPVideo } from '../videos'
  17. async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
  18. const { activity, byActor } = options
  19. // Only notify if it is not from a fetcher job
  20. const notify = options.fromFetch !== true
  21. const activityObject = activity.object
  22. const activityType = activityObject.type
  23. if (activityType === 'Video') {
  24. return processCreateVideo(activity, notify)
  25. }
  26. if (activityType === 'Note') {
  27. // Comments will be fetched from videos
  28. if (options.fromFetch) return
  29. return retryTransactionWrapper(processCreateVideoComment, activity, byActor, notify)
  30. }
  31. if (activityType === 'WatchAction') {
  32. return retryTransactionWrapper(processCreateWatchAction, activity)
  33. }
  34. if (activityType === 'CacheFile') {
  35. return retryTransactionWrapper(processCreateCacheFile, activity, byActor)
  36. }
  37. if (activityType === 'Playlist') {
  38. return retryTransactionWrapper(processCreatePlaylist, activity, byActor)
  39. }
  40. logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
  41. return Promise.resolve(undefined)
  42. }
  43. // ---------------------------------------------------------------------------
  44. export {
  45. processCreateActivity
  46. }
  47. // ---------------------------------------------------------------------------
  48. async function processCreateVideo (activity: ActivityCreate, notify: boolean) {
  49. const videoToCreateData = activity.object as VideoObject
  50. const syncParam = { rates: false, shares: false, comments: false, thumbnail: true, refreshVideo: false }
  51. const { video, created } = await getOrCreateAPVideo({ videoObject: videoToCreateData, syncParam })
  52. if (created && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
  53. return video
  54. }
  55. async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) {
  56. if (await isRedundancyAccepted(activity, byActor) !== true) return
  57. const cacheFile = activity.object as CacheFileObject
  58. const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
  59. await sequelizeTypescript.transaction(async t => {
  60. return createOrUpdateCacheFile(cacheFile, video, byActor, t)
  61. })
  62. if (video.isOwned()) {
  63. // Don't resend the activity to the sender
  64. const exceptions = [ byActor ]
  65. await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
  66. }
  67. }
  68. async function processCreateWatchAction (activity: ActivityCreate) {
  69. const watchAction = activity.object as WatchActionObject
  70. if (watchAction.actionStatus !== 'CompletedActionStatus') return
  71. const video = await VideoModel.loadByUrl(watchAction.object)
  72. if (video.remote) return
  73. await sequelizeTypescript.transaction(async t => {
  74. return createOrUpdateLocalVideoViewer(watchAction, video, t)
  75. })
  76. }
  77. async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) {
  78. const commentObject = activity.object as VideoCommentObject
  79. const byAccount = byActor.Account
  80. if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
  81. let video: MVideoAccountLightBlacklistAllFiles
  82. let created: boolean
  83. let comment: MCommentOwnerVideo
  84. try {
  85. const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false })
  86. video = resolveThreadResult.video
  87. created = resolveThreadResult.commentCreated
  88. comment = resolveThreadResult.comment
  89. } catch (err) {
  90. logger.debug(
  91. 'Cannot process video comment because we could not resolve thread %s. Maybe it was not a video thread, so skip it.',
  92. commentObject.inReplyTo,
  93. { err }
  94. )
  95. return
  96. }
  97. // Try to not forward unwanted comments on our videos
  98. if (video.isOwned()) {
  99. if (await isBlockedByServerOrAccount(comment.Account, video.VideoChannel.Account)) {
  100. logger.info('Skip comment forward from blocked account or server %s.', comment.Account.Actor.url)
  101. return
  102. }
  103. if (created === true) {
  104. // Don't resend the activity to the sender
  105. const exceptions = [ byActor ]
  106. await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
  107. }
  108. }
  109. if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
  110. }
  111. async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) {
  112. const playlistObject = activity.object as PlaylistObject
  113. const byAccount = byActor.Account
  114. if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url)
  115. await createOrUpdateVideoPlaylist(playlistObject, activity.to)
  116. }