unoffical wafrn mirror wafrn.net
atproto social-network activitypub
at angular21 393 lines 13 kB view raw
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 };