aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/job-queue/handlers/activitypub-cleaner.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/job-queue/handlers/activitypub-cleaner.ts')
-rw-r--r--server/lib/job-queue/handlers/activitypub-cleaner.ts202
1 files changed, 0 insertions, 202 deletions
diff --git a/server/lib/job-queue/handlers/activitypub-cleaner.ts b/server/lib/job-queue/handlers/activitypub-cleaner.ts
deleted file mode 100644
index 6ee9e2429..000000000
--- a/server/lib/job-queue/handlers/activitypub-cleaner.ts
+++ /dev/null
@@ -1,202 +0,0 @@
1import { map } from 'bluebird'
2import { Job } from 'bullmq'
3import {
4 isAnnounceActivityValid,
5 isDislikeActivityValid,
6 isLikeActivityValid
7} from '@server/helpers/custom-validators/activitypub/activity'
8import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments'
9import { PeerTubeRequestError } from '@server/helpers/requests'
10import { AP_CLEANER } from '@server/initializers/constants'
11import { fetchAP } from '@server/lib/activitypub/activity'
12import { checkUrlsSameHost } from '@server/lib/activitypub/url'
13import { Redis } from '@server/lib/redis'
14import { VideoModel } from '@server/models/video/video'
15import { VideoCommentModel } from '@server/models/video/video-comment'
16import { VideoShareModel } from '@server/models/video/video-share'
17import { HttpStatusCode } from '@shared/models'
18import { logger, loggerTagsFactory } from '../../../helpers/logger'
19import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
20
21const lTags = loggerTagsFactory('ap-cleaner')
22
23// Job to clean remote interactions off local videos
24
25async function processActivityPubCleaner (_job: Job) {
26 logger.info('Processing ActivityPub cleaner.', lTags())
27
28 {
29 const rateUrls = await AccountVideoRateModel.listRemoteRateUrlsOfLocalVideos()
30 const { bodyValidator, deleter, updater } = rateOptionsFactory()
31
32 await map(rateUrls, async rateUrl => {
33 // TODO: remove when https://github.com/mastodon/mastodon/issues/13571 is fixed
34 if (rateUrl.includes('#')) return
35
36 const result = await updateObjectIfNeeded({ url: rateUrl, bodyValidator, updater, deleter })
37
38 if (result?.status === 'deleted') {
39 const { videoId, type } = result.data
40
41 await VideoModel.syncLocalRates(videoId, type, undefined)
42 }
43 }, { concurrency: AP_CLEANER.CONCURRENCY })
44 }
45
46 {
47 const shareUrls = await VideoShareModel.listRemoteShareUrlsOfLocalVideos()
48 const { bodyValidator, deleter, updater } = shareOptionsFactory()
49
50 await map(shareUrls, async shareUrl => {
51 await updateObjectIfNeeded({ url: shareUrl, bodyValidator, updater, deleter })
52 }, { concurrency: AP_CLEANER.CONCURRENCY })
53 }
54
55 {
56 const commentUrls = await VideoCommentModel.listRemoteCommentUrlsOfLocalVideos()
57 const { bodyValidator, deleter, updater } = commentOptionsFactory()
58
59 await map(commentUrls, async commentUrl => {
60 await updateObjectIfNeeded({ url: commentUrl, bodyValidator, updater, deleter })
61 }, { concurrency: AP_CLEANER.CONCURRENCY })
62 }
63}
64
65// ---------------------------------------------------------------------------
66
67export {
68 processActivityPubCleaner
69}
70
71// ---------------------------------------------------------------------------
72
73async function updateObjectIfNeeded <T> (options: {
74 url: string
75 bodyValidator: (body: any) => boolean
76 updater: (url: string, newUrl: string) => Promise<T>
77 deleter: (url: string) => Promise<T> }
78): Promise<{ data: T, status: 'deleted' | 'updated' } | null> {
79 const { url, bodyValidator, updater, deleter } = options
80
81 const on404OrTombstone = async () => {
82 logger.info('Removing remote AP object %s.', url, lTags(url))
83 const data = await deleter(url)
84
85 return { status: 'deleted' as 'deleted', data }
86 }
87
88 try {
89 const { body } = await fetchAP<any>(url)
90
91 // If not same id, check same host and update
92 if (!body?.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`)
93
94 if (body.type === 'Tombstone') {
95 return on404OrTombstone()
96 }
97
98 const newUrl = body.id
99 if (newUrl !== url) {
100 if (checkUrlsSameHost(newUrl, url) !== true) {
101 throw new Error(`New url ${newUrl} has not the same host than old url ${url}`)
102 }
103
104 logger.info('Updating remote AP object %s.', url, lTags(url))
105 const data = await updater(url, newUrl)
106
107 return { status: 'updated', data }
108 }
109
110 return null
111 } catch (err) {
112 // Does not exist anymore, remove entry
113 if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
114 return on404OrTombstone()
115 }
116
117 logger.debug('Remote AP object %s is unavailable.', url, lTags(url))
118
119 const unavailability = await Redis.Instance.addAPUnavailability(url)
120 if (unavailability >= AP_CLEANER.UNAVAILABLE_TRESHOLD) {
121 logger.info('Removing unavailable AP resource %s.', url, lTags(url))
122 return on404OrTombstone()
123 }
124
125 return null
126 }
127}
128
129function rateOptionsFactory () {
130 return {
131 bodyValidator: (body: any) => isLikeActivityValid(body) || isDislikeActivityValid(body),
132
133 updater: async (url: string, newUrl: string) => {
134 const rate = await AccountVideoRateModel.loadByUrl(url, undefined)
135 rate.url = newUrl
136
137 const videoId = rate.videoId
138 const type = rate.type
139
140 await rate.save()
141
142 return { videoId, type }
143 },
144
145 deleter: async (url) => {
146 const rate = await AccountVideoRateModel.loadByUrl(url, undefined)
147
148 const videoId = rate.videoId
149 const type = rate.type
150
151 await rate.destroy()
152
153 return { videoId, type }
154 }
155 }
156}
157
158function shareOptionsFactory () {
159 return {
160 bodyValidator: (body: any) => isAnnounceActivityValid(body),
161
162 updater: async (url: string, newUrl: string) => {
163 const share = await VideoShareModel.loadByUrl(url, undefined)
164 share.url = newUrl
165
166 await share.save()
167
168 return undefined
169 },
170
171 deleter: async (url) => {
172 const share = await VideoShareModel.loadByUrl(url, undefined)
173
174 await share.destroy()
175
176 return undefined
177 }
178 }
179}
180
181function commentOptionsFactory () {
182 return {
183 bodyValidator: (body: any) => sanitizeAndCheckVideoCommentObject(body),
184
185 updater: async (url: string, newUrl: string) => {
186 const comment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(url)
187 comment.url = newUrl
188
189 await comment.save()
190
191 return undefined
192 },
193
194 deleter: async (url) => {
195 const comment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(url)
196
197 await comment.destroy()
198
199 return undefined
200 }
201 }
202}