unoffical wafrn mirror
wafrn.net
atproto
social-network
activitypub
1import { Job } from "bullmq";
2import {
3 Blocks,
4 EmojiReaction,
5 FederatedHost,
6 Follows,
7 Mutes,
8 Post,
9 PostMentionsUserRelation,
10 User,
11 UserLikesPostRelations,
12 UserOptions,
13 sequelize,
14} from "../../models/index.js";
15import { completeEnvironment } from "../backendOptions.js";
16import { getUserIdFromRemoteId } from "../cacheGetters/getUserIdFromRemoteId.js";
17import { getPetitionSigned } from "../activitypub/getPetitionSigned.js";
18import { processUserEmojis } from "../activitypub/processUserEmojis.js";
19import { fediverseTag } from "../../interfaces/fediverse/tags.js";
20import { logger } from "../logger.js";
21import { redisCache } from "../redis.js";
22import { Op } from "sequelize";
23import { getDeletedUser } from "../cacheGetters/getDeletedUser.js";
24import processExternalCustomCss from "../processExternalCustomCss.js";
25import { unlink, writeFile } from "fs/promises";
26import { existsSync } from "fs";
27
28// This function will return userid after processing it.
29async function getRemoteActorIdProcessor(job: Job) {
30 const actorUrl: string = job.data.actorUrl;
31 const forceUpdate: boolean = job.data.forceUpdate;
32 let res: string | User | undefined | null = await getUserIdFromRemoteId(
33 actorUrl
34 );
35 let url = undefined;
36 try {
37 url = new URL(actorUrl);
38 } catch (error) {
39 res = await getDeletedUser();
40 url = undefined;
41 logger.debug({
42 message: `Invalid url ${actorUrl}`,
43 url: actorUrl,
44 stack: new Error().stack,
45 });
46 }
47 if (res === "" || (forceUpdate && url != undefined)) {
48 let federatedHost = await FederatedHost.findOne({
49 where: sequelize.where(
50 sequelize.fn("lower", sequelize.col("displayName")),
51 url?.host ? url.host.toLowerCase() : ""
52 ),
53 });
54 const hostBanned = federatedHost?.blocked;
55 if (hostBanned) {
56 res = await getDeletedUser();
57 } else {
58 const user = (await User.findByPk(job.data.userId)) as User;
59 const userPetition = await getPetitionSigned(user, actorUrl);
60 if (userPetition) {
61 if (!federatedHost && url) {
62 const federatedHostToCreate = {
63 displayName: url.host.toLocaleLowerCase(),
64 publicInbox: userPetition.endpoints?.sharedInbox
65 ? userPetition.endpoints?.sharedInbox
66 : "",
67 };
68 federatedHost = (
69 await FederatedHost.findOrCreate({ where: federatedHostToCreate })
70 )[0];
71 }
72 if (!url || !federatedHost) {
73 logger.warn({
74 message: "Url is not valid wtf",
75 trace: new Error().stack,
76 });
77 return await getDeletedUser();
78 }
79 const remoteMentionUrl =
80 typeof userPetition.url === "string" ? userPetition.url : "";
81 let followers = 0;
82 let followed = 0;
83 if (userPetition.followers) {
84 const followersPetition = await getPetitionSigned(
85 user,
86 userPetition.followers
87 );
88 if (followersPetition && followersPetition.totalItems) {
89 followers = followersPetition.totalItems;
90 }
91 }
92 if (userPetition.following) {
93 const followingPetition = await getPetitionSigned(
94 user,
95 userPetition.following
96 );
97 if (followingPetition && followingPetition.totalItems) {
98 followed = followingPetition.totalItems;
99 }
100 }
101 const userData = {
102 hideFollows: false,
103 hideProfileNotLoggedIn: false,
104 url: `@${userPetition.preferredUsername}@${url?.host}`,
105 name: userPetition.name
106 ? userPetition.name
107 : userPetition.preferredUsername,
108 email: null,
109 description: userPetition.summary ? userPetition.summary : "",
110 avatar: userPetition.icon?.url
111 ? userPetition.icon.url
112 : `${completeEnvironment.mediaUrl}/uploads/default.webp`,
113 headerImage: userPetition?.image?.url
114 ? userPetition.image.url.toString()
115 : ``,
116 password: "NOT_A_WAFRN_USER_NOT_REAL_PASSWORD",
117 publicKey: userPetition.publicKey?.publicKeyPem,
118 remoteInbox: userPetition.inbox,
119 remoteId: actorUrl,
120 activated: true,
121 federatedHostId: federatedHost.id,
122 remoteMentionUrl: remoteMentionUrl,
123 followersCollectionUrl: userPetition.followers,
124 followingCollectionUrl: userPetition.following,
125 isBot: userPetition.type != "Person",
126 followerCount: followers,
127 followingCount: followed,
128 createdAt: userPetition.published
129 ? new Date(userPetition.published)
130 : new Date(),
131 updatedAt: new Date(),
132 NSFW: false,
133 birthDate: new Date(),
134 userMigratedTo: userPetition.movedTo || "",
135 displayUrl: Array.isArray(userPetition.url)
136 ? userPetition.url[0]
137 : userPetition.url,
138 };
139 federatedHost.publicInbox = userPetition.endpoints?.sharedInbox;
140 await federatedHost.save();
141 let userRes;
142 const existingUsers = await User.findAll({
143 where: {
144 [Op.or]: [
145 sequelize.where(
146 sequelize.fn("lower", sequelize.col("url")),
147 userData.url.toLowerCase()
148 ),
149 {
150 remoteId: userData.remoteId,
151 },
152 ],
153 },
154 });
155 if (res) {
156 if (res !== (await getDeletedUser())) {
157 userRes = await User.findByPk(res as string);
158 if (existingUsers.length > 1) {
159 logger.debug({
160 message: `Multiple fedi users found for ${userData.url} (${userData.remoteId}): ${existingUsers.length}`,
161 });
162 for await (const userWithDuplicatedData of existingUsers.slice(
163 1
164 )) {
165 userWithDuplicatedData.url =
166 userWithDuplicatedData.url +
167 "_DUPLICATED_" +
168 new Date().getTime();
169 userWithDuplicatedData.remoteId =
170 userWithDuplicatedData.remoteId +
171 "_DUPLICATED_" +
172 new Date().getTime();
173 }
174 }
175 if (
176 existingUsers &&
177 existingUsers.length > 0 &&
178 existingUsers[0] &&
179 userRes?.id !== existingUsers[0]?.id
180 ) {
181 const existingUser = existingUsers[0];
182 existingUser.activated = false;
183 existingUser.remoteId = `${existingUser.remoteId
184 }_OVERWRITTEN_ON${new Date().getTime()}`;
185 existingUser.url = `${existingUser.url
186 }_OVERWRITTEN_ON${new Date().getTime()}`;
187 await existingUser.save();
188 if (userRes) {
189 const updates = [
190 Follows.update(
191 {
192 followerId: userRes.id,
193 },
194 {
195 where: {
196 followerId: existingUser.id,
197 },
198 }
199 ),
200 Follows.update(
201 {
202 followedId: userRes.id,
203 },
204 {
205 where: {
206 followedId: existingUser.id,
207 },
208 }
209 ),
210 Post.update(
211 {
212 userId: userRes.id,
213 },
214 {
215 where: {
216 userId: existingUser.id,
217 },
218 }
219 ),
220 UserLikesPostRelations.update(
221 {
222 userId: userRes.id,
223 },
224 {
225 where: {
226 userId: existingUser.id,
227 },
228 }
229 ),
230 EmojiReaction.update(
231 {
232 userId: userRes.id,
233 },
234 {
235 where: {
236 userId: existingUser.id,
237 },
238 }
239 ),
240 Blocks.update(
241 {
242 blockedId: userRes.id,
243 },
244 {
245 where: {
246 blockedId: existingUser.id,
247 },
248 }
249 ),
250 Blocks.update(
251 {
252 blockerId: userRes.id,
253 },
254 {
255 where: {
256 blockerId: existingUser.id,
257 },
258 }
259 ),
260 Mutes.update(
261 {
262 muterId: userRes.id,
263 },
264 {
265 where: {
266 muterId: existingUser.id,
267 },
268 }
269 ),
270 Mutes.update(
271 {
272 mutedId: userRes.id,
273 },
274 {
275 where: {
276 mutedId: existingUser.id,
277 },
278 }
279 ),
280 PostMentionsUserRelation.update(
281 {
282 userId: userRes.id,
283 },
284 {
285 where: {
286 userId: existingUser.id,
287 },
288 }
289 ),
290 ];
291 await Promise.all(updates);
292 }
293 await redisCache.del("userRemoteId:" + existingUser.remoteId);
294 }
295 if (userRes) {
296 userRes.set(userData);
297 await userRes.save();
298 } else {
299 redisCache.del("userRemoteId:" + actorUrl.toLocaleLowerCase());
300 }
301 }
302 } else {
303 if (existingUsers && existingUsers[0]) {
304 existingUsers[0].set(userData);
305 await existingUsers[0].save();
306 } else {
307 userRes = await User.create(userData);
308 }
309 }
310 if (
311 userRes &&
312 userRes.id &&
313 userRes.url != completeEnvironment.deletedUser &&
314 userPetition
315 ) {
316 try {
317 if (userPetition._wafrn_customCSS) {
318 let customCSS: string | undefined = undefined
319 logger.info({ id: userPetition.id }, "found custom css for this user");
320 if (URL.canParse(userPetition._wafrn_customCSS)) {
321 const cssRes = await fetch(userPetition._wafrn_customCSS)
322 if (cssRes.ok)
323 customCSS = await cssRes.text()
324 } else {
325 customCSS = userPetition._wafrn_customCSS
326 }
327 if (customCSS) {
328 const css = await processExternalCustomCss(userRes.id, customCSS)
329 await writeFile(`uploads/themes/${userRes.id}.css`, css)
330 }
331 } else if (existsSync(`uploads/themes/${userRes.id}.css`)) {
332 await unlink(`uploads/themes/${userRes.id}.css`)
333 }
334 } catch (e) {
335 logger.warn(e)
336 }
337 }
338 if (
339 userRes &&
340 userRes.id &&
341 userRes.url != completeEnvironment.deletedUser &&
342 userPetition &&
343 userPetition.attachment &&
344 userPetition.attachment.length
345 ) {
346 await UserOptions.destroy({
347 where: {
348 userId: userRes.id,
349 optionName: {
350 [Op.like]: "fediverse.public.attachment",
351 },
352 },
353 });
354 const properties = userPetition.attachment.filter(
355 (elem: any) => elem.type === "PropertyValue"
356 );
357 await UserOptions.create({
358 userId: userRes.id,
359 optionName: `fediverse.public.attachment`,
360 optionValue: JSON.stringify(properties),
361 public: true,
362 });
363 }
364 res = userRes?.id ? userRes.id : await getDeletedUser();
365 try {
366 if (userRes) {
367 const tags = userPetition?.tag
368 ? Array.isArray(userPetition.tag)
369 ? userPetition.tag
370 : [userPetition.tag]
371 : [];
372 const emojis = [
373 ...new Set(
374 tags.filter((elem: fediverseTag) => elem.type === "Emoji")
375 ),
376 ];
377 await processUserEmojis(userRes, emojis);
378 }
379 } catch (error) {
380 logger.info({
381 message: `Error processing emojis from user ${userRes?.url}`,
382 error: error,
383 tags: userPetition?.tag,
384 userPetition: userPetition,
385 });
386 }
387 }
388 }
389 }
390 return res;
391}
392
393export { getRemoteActorIdProcessor };