unoffical wafrn mirror wafrn.net
atproto social-network activitypub
at angular21 302 lines 10 kB view raw
1import { Op, Sequelize } from "sequelize"; 2import { 3 Emoji, 4 EmojiReaction, 5 FederatedHost, 6 Post, 7 User, 8 sequelize, 9} from "../../models/index.js"; 10import { completeEnvironment } from "../backendOptions.js"; 11import { activityPubObject } from "../../interfaces/fediverse/activityPubObject.js"; 12import { postPetitionSigned } from "./postPetitionSigned.js"; 13import { logger } from "../logger.js"; 14import { Queue } from "bullmq"; 15import _ from "underscore"; 16import { emojiToAPTag } from "./emojiToAPTag.js"; 17import { Privacy } from "../../models/post.js"; 18import { getAllLocalUserIds } from "../cacheGetters/getAllLocalUserIds.js"; 19 20const sendPostQueue = new Queue("sendPostToInboxes", { 21 connection: completeEnvironment.bullmqConnection, 22 defaultJobOptions: { 23 removeOnComplete: true, 24 attempts: 3, 25 backoff: { 26 type: "exponential", 27 delay: 1000, 28 }, 29 removeOnFail: true, 30 }, 31}); 32 33async function likePostRemote(like: any, dislike = false) { 34 const user = await User.findOne({ 35 where: { 36 id: like.userId, 37 }, 38 }); 39 const likedPost = await Post.findOne({ 40 where: { 41 id: like.postId, 42 }, 43 include: [ 44 { 45 model: User, 46 as: "user", 47 }, 48 ], 49 }); 50 51 if (!user || !likedPost) return; 52 53 const stringMyFollowers = `${ 54 completeEnvironment.frontendUrl 55 }/fediverse/blog/${user.url.toLowerCase()}/followers`; 56 const ownerOfLikedPost = likedPost.user.remoteId 57 ? likedPost.user.remoteId 58 : `${completeEnvironment.frontendUrl}/fediverse/blog/${likedPost.user.url}`; 59 const likeObject: activityPubObject = !dislike 60 ? { 61 "@context": [ 62 "https://www.w3.org/ns/activitystreams", 63 `${completeEnvironment.frontendUrl}/contexts/litepub-0.1.jsonld`, 64 ], 65 actor: `${ 66 completeEnvironment.frontendUrl 67 }/fediverse/blog/${user.url.toLowerCase()}`, 68 to: 69 likedPost.privacy / 1 === Privacy.DirectMessage 70 ? [ownerOfLikedPost] 71 : likedPost.privacy / 1 === Privacy.Public 72 ? [ 73 "https://www.w3.org/ns/activitystreams#Public", 74 stringMyFollowers, 75 ] 76 : [stringMyFollowers], 77 cc: likedPost.privacy / 1 === Privacy.Public ? [ownerOfLikedPost] : [], 78 id: `${completeEnvironment.frontendUrl}/fediverse/likes/${like.userId}/${like.postId}`, 79 object: likedPost.remotePostId 80 ? likedPost.remotePostId 81 : `${completeEnvironment.frontendUrl}/fediverse/post/${likedPost.id}`, 82 type: "Like", 83 } 84 : { 85 "@context": [ 86 "https://www.w3.org/ns/activitystreams", 87 `${completeEnvironment.frontendUrl}/contexts/litepub-0.1.jsonld`, 88 ], 89 actor: `${ 90 completeEnvironment.frontendUrl 91 }/fediverse/blog/${user.url.toLowerCase()}`, 92 to: 93 likedPost.privacy / 1 === Privacy.DirectMessage 94 ? [ownerOfLikedPost] 95 : likedPost.privacy / 1 === Privacy.Public 96 ? [ 97 "https://www.w3.org/ns/activitystreams#Public", 98 stringMyFollowers, 99 ] 100 : [stringMyFollowers], 101 cc: likedPost.privacy / 1 === Privacy.Public ? [ownerOfLikedPost] : [], 102 id: `${completeEnvironment.frontendUrl}/fediverse/undo/likes/${like.userId}/${like.postId}`, 103 object: `${completeEnvironment.frontendUrl}/fediverse/likes/${like.userId}/${like.postId}`, 104 type: "Undo", 105 }; 106 // petition to owner of the post: 107 const ownerOfPostLikePromise = likedPost.user.remoteInbox 108 ? postPetitionSigned(likeObject, user, likedPost.user.remoteInbox) 109 : true; 110 // servers with shared inbox 111 let serversToSendThePost = await FederatedHost.findAll({ 112 where: { 113 publicInbox: { [Op.ne]: null }, 114 blocked: { [Op.ne]: true }, 115 116 [Op.or]: [ 117 sequelize.literal( 118 `"id" in (SELECT "federatedHostId" from "users" where "users"."id" IN (SELECT "followerId" from "follows" where "followedId" = '${like.userId}') and "federatedHostId" is not NULL)` 119 ), 120 { 121 friendServer: true, 122 }, 123 ], 124 }, 125 }); 126 // remote user 127 const localUsers = await getAllLocalUserIds(); 128 let usersToSendThePost = [await User.findByPk(likedPost.userId)].filter( 129 (elem) => elem && !localUsers.includes(elem.id) 130 ); 131 132 try { 133 const ownerOfPostLikeResponse = await ownerOfPostLikePromise; 134 } catch (error) { 135 logger.debug(error); 136 } 137 138 await Promise.all([serversToSendThePost, usersToSendThePost]); 139 serversToSendThePost = await serversToSendThePost; 140 // TODO convert this into a function. Code is repeated and a better thing should be made 141 if (serversToSendThePost?.length > 0 || usersToSendThePost?.length > 0) { 142 let inboxes: string[] = []; 143 inboxes = inboxes.concat( 144 usersToSendThePost.map((elem: any) => 145 elem.remoteInbox ? elem.remoteInbox : "" 146 ) 147 ); 148 inboxes = inboxes.concat( 149 serversToSendThePost.map((elem: any) => elem.publicInbox) 150 ); 151 152 for await (const inboxChunk of inboxes) { 153 await sendPostQueue.add( 154 "sendChunk", 155 { 156 objectToSend: likeObject, 157 petitionBy: user.dataValues, 158 inboxList: inboxChunk, 159 }, 160 { 161 priority: 2097152, 162 delay: 500, 163 } 164 ); 165 } 166 } 167} 168 169async function emojiReactRemote(react: EmojiReaction, undo = false) { 170 const user = await User.findOne({ 171 where: { 172 id: react.userId, 173 }, 174 }); 175 const reactedPost = await Post.findOne({ 176 where: { 177 id: react.postId, 178 }, 179 include: [ 180 { 181 model: User, 182 as: "user", 183 }, 184 ], 185 }); 186 if (!user || !reactedPost) return; 187 188 const emoji = await Emoji.findByPk(react.emojiId); 189 const stringMyFollowers = `${ 190 completeEnvironment.frontendUrl 191 }/fediverse/blog/${user.url.toLowerCase()}/followers`; 192 const ownerOfreactedPost = reactedPost.user.remoteId 193 ? reactedPost.user.remoteId 194 : `${completeEnvironment.frontendUrl}/fediverse/blog/${reactedPost.user.url}`; 195 let emojireactObject: activityPubObject = { 196 "@context": [ 197 "https://www.w3.org/ns/activitystreams", 198 `${completeEnvironment.frontendUrl}/contexts/litepub-0.1.jsonld`, 199 ], 200 actor: `${ 201 completeEnvironment.frontendUrl 202 }/fediverse/blog/${user.url.toLowerCase()}`, 203 to: 204 reactedPost.privacy / 1 === Privacy.DirectMessage 205 ? [ownerOfreactedPost] 206 : reactedPost.privacy / 1 === Privacy.Public 207 ? ["https://www.w3.org/ns/activitystreams#Public", stringMyFollowers] 208 : [stringMyFollowers], 209 cc: reactedPost.privacy / 1 === Privacy.Public ? [ownerOfreactedPost] : [], 210 id: `${completeEnvironment.frontendUrl}/fediverse/emojiReact/${react.userId}/${react.postId}/${react.emojiId}`, 211 object: reactedPost.remotePostId 212 ? reactedPost.remotePostId 213 : `${completeEnvironment.frontendUrl}/fediverse/post/${reactedPost.id}`, 214 tag: emoji ? [emojiToAPTag(emoji)] : undefined, 215 content: emoji ? emoji.name : react.content, 216 type: "EmojiReact", 217 }; 218 if (undo) { 219 emojireactObject = { 220 "@context": [ 221 "https://www.w3.org/ns/activitystreams", 222 `${completeEnvironment.frontendUrl}/contexts/litepub-0.1.jsonld`, 223 ], 224 actor: `${ 225 completeEnvironment.frontendUrl 226 }/fediverse/blog/${user.url.toLowerCase()}`, 227 to: 228 reactedPost.privacy / 1 === Privacy.DirectMessage 229 ? [ownerOfreactedPost] 230 : reactedPost.privacy / 1 === Privacy.Public 231 ? ["https://www.w3.org/ns/activitystreams#Public", stringMyFollowers] 232 : [stringMyFollowers], 233 cc: 234 reactedPost.privacy / 1 === Privacy.Public ? [ownerOfreactedPost] : [], 235 id: `${completeEnvironment.frontendUrl}/fediverse/undo/emojiReact/${react.userId}/${react.postId}/${react.emojiId}`, 236 object: emojireactObject, 237 type: "Undo", 238 }; 239 } 240 // petition to owner of the post: 241 const ownerOfPostLikePromise = reactedPost.user.remoteInbox 242 ? postPetitionSigned(emojireactObject, user, reactedPost.user.remoteInbox) 243 : true; 244 // servers with shared inbox 245 let serversToSendThePost = await FederatedHost.findAll({ 246 where: { 247 publicInbox: { [Op.ne]: null }, 248 blocked: { [Op.ne]: true }, 249 250 [Op.or]: [ 251 sequelize.literal( 252 `"id" in (SELECT "federatedHostId" from "users" where "users"."id" IN (SELECT "followerId" from "follows" where "followedId" = '${react.userId}') and "federatedHostId" is not NULL)` 253 ), 254 { 255 friendServer: true, 256 }, 257 ], 258 }, 259 }); 260 // user who reacted to the post. if no shared inbox we assume no emojireact on software. 261 const localUsersIds = await getAllLocalUserIds(); 262 let usersToSendThePost = [await User.findByPk(reactedPost.userId)].filter( 263 (elem) => elem && !localUsersIds.includes(elem.id) 264 ); 265 266 try { 267 const ownerOfPostLikeResponse = await ownerOfPostLikePromise; 268 } catch (error) { 269 logger.debug(error); 270 } 271 272 await Promise.all([serversToSendThePost, usersToSendThePost]); 273 serversToSendThePost = await serversToSendThePost; 274 usersToSendThePost = await usersToSendThePost; 275 // TODO convert this into a function. Code is repeated and a better thing should be made 276 if (serversToSendThePost?.length > 0 || usersToSendThePost.length > 0) { 277 let inboxes: string[] = []; 278 inboxes = inboxes.concat( 279 usersToSendThePost.map((elem: any) => 280 elem.remoteInbox ? elem.remoteInbox : "" 281 ) 282 ); 283 inboxes = inboxes.concat( 284 serversToSendThePost.map((elem: any) => elem.publicInbox) 285 ); 286 for await (const inboxChunk of inboxes) { 287 await sendPostQueue.add( 288 "sendChunk", 289 { 290 objectToSend: emojireactObject, 291 petitionBy: user.dataValues, 292 inboxList: inboxChunk, 293 }, 294 { 295 priority: 2097152, 296 } 297 ); 298 } 299 } 300} 301 302export { likePostRemote, emojiReactRemote };