process-undo.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo, CacheFileObject } from '../../../../shared/models/activitypub'
  2. import { DislikeObject } from '../../../../shared/models/activitypub/objects'
  3. import { retryTransactionWrapper } from '../../../helpers/database-utils'
  4. import { logger } from '../../../helpers/logger'
  5. import { sequelizeTypescript } from '../../../initializers'
  6. import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
  7. import { ActorModel } from '../../../models/activitypub/actor'
  8. import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
  9. import { forwardVideoRelatedActivity } from '../send/utils'
  10. import { getOrCreateVideoAndAccountAndChannel } from '../videos'
  11. import { VideoShareModel } from '../../../models/video/video-share'
  12. import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
  13. import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
  14. import { MActorSignature } from '../../../typings/models'
  15. async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
  16. const { activity, byActor } = options
  17. const activityToUndo = activity.object
  18. if (activityToUndo.type === 'Like') {
  19. return retryTransactionWrapper(processUndoLike, byActor, activity)
  20. }
  21. if (activityToUndo.type === 'Create') {
  22. if (activityToUndo.object.type === 'Dislike') {
  23. return retryTransactionWrapper(processUndoDislike, byActor, activity)
  24. } else if (activityToUndo.object.type === 'CacheFile') {
  25. return retryTransactionWrapper(processUndoCacheFile, byActor, activity)
  26. }
  27. }
  28. if (activityToUndo.type === 'Dislike') {
  29. return retryTransactionWrapper(processUndoDislike, byActor, activity)
  30. }
  31. if (activityToUndo.type === 'Follow') {
  32. return retryTransactionWrapper(processUndoFollow, byActor, activityToUndo)
  33. }
  34. if (activityToUndo.type === 'Announce') {
  35. return retryTransactionWrapper(processUndoAnnounce, byActor, activityToUndo)
  36. }
  37. logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
  38. return undefined
  39. }
  40. // ---------------------------------------------------------------------------
  41. export {
  42. processUndoActivity
  43. }
  44. // ---------------------------------------------------------------------------
  45. async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) {
  46. const likeActivity = activity.object as ActivityLike
  47. const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object })
  48. return sequelizeTypescript.transaction(async t => {
  49. if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
  50. const rate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byActor.Account.id, video.id, likeActivity.id, t)
  51. if (!rate || rate.type !== 'like') throw new Error(`Unknown like by account ${byActor.Account.id} for video ${video.id}.`)
  52. await rate.destroy({ transaction: t })
  53. await video.decrement('likes', { transaction: t })
  54. if (video.isOwned()) {
  55. // Don't resend the activity to the sender
  56. const exceptions = [ byActor ]
  57. await forwardVideoRelatedActivity(activity, t, exceptions, video)
  58. }
  59. })
  60. }
  61. async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) {
  62. const dislike = activity.object.type === 'Dislike'
  63. ? activity.object
  64. : activity.object.object as DislikeObject
  65. const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object })
  66. return sequelizeTypescript.transaction(async t => {
  67. if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
  68. const rate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byActor.Account.id, video.id, dislike.id, t)
  69. if (!rate || rate.type !== 'dislike') throw new Error(`Unknown dislike by account ${byActor.Account.id} for video ${video.id}.`)
  70. await rate.destroy({ transaction: t })
  71. await video.decrement('dislikes', { transaction: t })
  72. if (video.isOwned()) {
  73. // Don't resend the activity to the sender
  74. const exceptions = [ byActor ]
  75. await forwardVideoRelatedActivity(activity, t, exceptions, video)
  76. }
  77. })
  78. }
  79. async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) {
  80. const cacheFileObject = activity.object.object as CacheFileObject
  81. const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object })
  82. return sequelizeTypescript.transaction(async t => {
  83. const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id)
  84. if (!cacheFile) {
  85. logger.debug('Cannot undo unknown video cache %s.', cacheFileObject.id)
  86. return
  87. }
  88. if (cacheFile.actorId !== byActor.id) throw new Error('Cannot delete redundancy ' + cacheFile.url + ' of another actor.')
  89. await cacheFile.destroy()
  90. if (video.isOwned()) {
  91. // Don't resend the activity to the sender
  92. const exceptions = [ byActor ]
  93. await forwardVideoRelatedActivity(activity, t, exceptions, video)
  94. }
  95. })
  96. }
  97. function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) {
  98. return sequelizeTypescript.transaction(async t => {
  99. const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t)
  100. const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
  101. if (!actorFollow) throw new Error(`'Unknown actor follow ${follower.id} -> ${following.id}.`)
  102. await actorFollow.destroy({ transaction: t })
  103. return undefined
  104. })
  105. }
  106. function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) {
  107. return sequelizeTypescript.transaction(async t => {
  108. const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
  109. if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)
  110. if (share.actorId !== byActor.id) throw new Error(`${share.url} is not shared by ${byActor.url}.`)
  111. await share.destroy({ transaction: t })
  112. if (share.Video.isOwned()) {
  113. // Don't resend the activity to the sender
  114. const exceptions = [ byActor ]
  115. await forwardVideoRelatedActivity(announceActivity, t, exceptions, share.Video)
  116. }
  117. })
  118. }