unoffical wafrn mirror
wafrn.net
atproto
social-network
activitypub
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 };