]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/activitypub/process/process-update.ts
Unlisted videos are not displayed on Mastodon now
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / process / process-update.ts
1 import * as Bluebird from 'bluebird'
2 import { ActivityUpdate } from '../../../../shared/models/activitypub'
3 import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
4 import { VideoTorrentObject } from '../../../../shared/models/activitypub/objects'
5 import { retryTransactionWrapper } from '../../../helpers/database-utils'
6 import { logger } from '../../../helpers/logger'
7 import { resetSequelizeInstance } from '../../../helpers/utils'
8 import { sequelizeTypescript } from '../../../initializers'
9 import { AccountModel } from '../../../models/account/account'
10 import { ActorModel } from '../../../models/activitypub/actor'
11 import { TagModel } from '../../../models/video/tag'
12 import { VideoChannelModel } from '../../../models/video/video-channel'
13 import { VideoFileModel } from '../../../models/video/video-file'
14 import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
15 import {
16 generateThumbnailFromUrl,
17 getOrCreateAccountAndVideoAndChannel,
18 videoActivityObjectToDBAttributes,
19 videoFileActivityUrlToDBAttributes
20 } from '../videos'
21
22 async function processUpdateActivity (activity: ActivityUpdate) {
23 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
24 const objectType = activity.object.type
25
26 if (objectType === 'Video') {
27 return processUpdateVideo(actor, activity)
28 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
29 return processUpdateActor(actor, activity)
30 }
31
32 return
33 }
34
35 // ---------------------------------------------------------------------------
36
37 export {
38 processUpdateActivity
39 }
40
41 // ---------------------------------------------------------------------------
42
43 function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
44 const options = {
45 arguments: [ actor, activity ],
46 errorMessage: 'Cannot update the remote video with many retries'
47 }
48
49 return retryTransactionWrapper(updateRemoteVideo, options)
50 }
51
52 async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
53 const videoAttributesToUpdate = activity.object as VideoTorrentObject
54
55 const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id)
56
57 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
58 let videoInstance = res.video
59 let videoFieldsSave: any
60
61 try {
62 await sequelizeTypescript.transaction(async t => {
63 const sequelizeOptions = {
64 transaction: t
65 }
66
67 videoFieldsSave = videoInstance.toJSON()
68
69 const videoChannel = videoInstance.VideoChannel
70 if (videoChannel.Account.Actor.id !== actor.id) {
71 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
72 }
73
74 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to)
75 videoInstance.set('name', videoData.name)
76 videoInstance.set('uuid', videoData.uuid)
77 videoInstance.set('url', videoData.url)
78 videoInstance.set('category', videoData.category)
79 videoInstance.set('licence', videoData.licence)
80 videoInstance.set('language', videoData.language)
81 videoInstance.set('description', videoData.description)
82 videoInstance.set('support', videoData.support)
83 videoInstance.set('nsfw', videoData.nsfw)
84 videoInstance.set('commentsEnabled', videoData.commentsEnabled)
85 videoInstance.set('duration', videoData.duration)
86 videoInstance.set('createdAt', videoData.createdAt)
87 videoInstance.set('updatedAt', videoData.updatedAt)
88 videoInstance.set('views', videoData.views)
89 videoInstance.set('privacy', videoData.privacy)
90
91 await videoInstance.save(sequelizeOptions)
92
93 // Don't block on request
94 generateThumbnailFromUrl(videoInstance, videoAttributesToUpdate.icon)
95 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoAttributesToUpdate.id, err))
96
97 // Remove old video files
98 const videoFileDestroyTasks: Bluebird<void>[] = []
99 for (const videoFile of videoInstance.VideoFiles) {
100 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
101 }
102 await Promise.all(videoFileDestroyTasks)
103
104 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
105 const tasks = videoFileAttributes.map(f => VideoFileModel.create(f))
106 await Promise.all(tasks)
107
108 const tags = videoAttributesToUpdate.tag.map(t => t.name)
109 const tagInstances = await TagModel.findOrCreateTags(tags, t)
110 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
111 })
112
113 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
114 } catch (err) {
115 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
116 resetSequelizeInstance(videoInstance, videoFieldsSave)
117 }
118
119 // This is just a debug because we will retry the insert
120 logger.debug('Cannot update the remote video.', err)
121 throw err
122 }
123 }
124
125 function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
126 const options = {
127 arguments: [ actor, activity ],
128 errorMessage: 'Cannot update the remote actor with many retries'
129 }
130
131 return retryTransactionWrapper(updateRemoteActor, options)
132 }
133
134 async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
135 const actorAttributesToUpdate = activity.object as ActivityPubActor
136
137 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
138 let accountOrChannelInstance: AccountModel | VideoChannelModel
139 let actorFieldsSave: object
140 let accountOrChannelFieldsSave: object
141
142 // Fetch icon?
143 const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
144
145 try {
146 await sequelizeTypescript.transaction(async t => {
147 actorFieldsSave = actor.toJSON()
148
149 if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
150 else accountOrChannelInstance = actor.Account
151
152 accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
153
154 await updateActorInstance(actor, actorAttributesToUpdate)
155
156 if (avatarName !== undefined) {
157 await updateActorAvatarInstance(actor, avatarName, t)
158 }
159
160 await actor.save({ transaction: t })
161
162 accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername)
163 accountOrChannelInstance.set('description', actorAttributesToUpdate.summary)
164 accountOrChannelInstance.set('support', actorAttributesToUpdate.support)
165 await accountOrChannelInstance.save({ transaction: t })
166 })
167
168 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
169 } catch (err) {
170 if (actor !== undefined && actorFieldsSave !== undefined) {
171 resetSequelizeInstance(actor, actorFieldsSave)
172 }
173
174 if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
175 resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
176 }
177
178 // This is just a debug because we will retry the insert
179 logger.debug('Cannot update the remote account.', err)
180 throw err
181 }
182 }