unoffical wafrn mirror
wafrn.net
atproto
social-network
activitypub
1import { Op } from "sequelize";
2import {
3 Blocks,
4 Emoji,
5 FederatedHost,
6 Media,
7 Post,
8 PostMentionsUserRelation,
9 ServerBlock,
10 PostTag,
11 User,
12 sequelize,
13 Ask,
14 Notification,
15 EmojiReaction,
16 PostAncestor,
17 PostReport,
18 QuestionPoll,
19 Quotes,
20 RemoteUserPostView,
21 SilencedPost,
22 UserBitesPostRelation,
23 UserBookmarkedPosts,
24 UserLikesPostRelations,
25} from "../../models/index.js";
26import { completeEnvironment } from "../backendOptions.js";
27import { logger } from "../logger.js";
28import { getRemoteActor } from "./getRemoteActor.js";
29import { getPetitionSigned } from "./getPetitionSigned.js";
30import { fediverseTag } from "../../interfaces/fediverse/tags.js";
31import { loadPoll } from "./loadPollFromPost.js";
32import { getApObjectPrivacy } from "./getPrivacy.js";
33import dompurify from "isomorphic-dompurify";
34import { Queue } from "bullmq";
35import { bulkCreateNotifications } from "../pushNotifications.js";
36import { getDeletedUser } from "../cacheGetters/getDeletedUser.js";
37import { Privacy } from "../../models/post.js";
38import {
39 getAtProtoThread,
40 getPostThreadSafe,
41 processSinglePost,
42} from "../../atproto/utils/getAtProtoThread.js";
43import * as cheerio from "cheerio";
44import {
45 PostView,
46 ThreadViewPost,
47} from "@atproto/api/dist/client/types/app/bsky/feed/defs.js";
48import { getAdminUser } from "../getAdminAndDeletedUser.js";
49
50const updateMediaDataQueue = new Queue("processRemoteMediaData", {
51 connection: completeEnvironment.bullmqConnection,
52 defaultJobOptions: {
53 removeOnComplete: true,
54 attempts: 3,
55 backoff: {
56 type: "exponential",
57 delay: 1000,
58 },
59 removeOnFail: true,
60 },
61});
62
63async function getPostThreadRecursive(
64 user: any,
65 remotePostId: string | null,
66 remotePostObject?: any,
67 localPostToForceUpdate?: string,
68 options?: any
69) {
70 if (remotePostId === null) return;
71
72 const deletedUser = getDeletedUser();
73 try {
74 remotePostId.startsWith(
75 `${completeEnvironment.frontendUrl}/fediverse/post/`
76 );
77 } catch (error) {
78 logger.info({
79 message: "Error with url on post",
80 object: remotePostId,
81 stack: new Error().stack,
82 });
83 return;
84 }
85 if (
86 remotePostId.startsWith(
87 `${completeEnvironment.frontendUrl}/fediverse/post/`
88 )
89 ) {
90 // we are looking at a local post
91 const partToRemove = `${completeEnvironment.frontendUrl}/fediverse/post/`;
92 const postId = remotePostId.substring(partToRemove.length);
93 return await Post.findOne({
94 where: {
95 id: postId,
96 },
97 });
98 }
99 if (completeEnvironment.enableBsky && remotePostId.startsWith("at://")) {
100 // Bluesky post. Likely coming from an import
101 const postInDatabase = await Post.findOne({
102 where: {
103 bskyUri: remotePostId,
104 },
105 });
106 if (postInDatabase) {
107 return postInDatabase;
108 } else if (!remotePostObject) {
109 const postId = await getAtProtoThread(remotePostId);
110 return await Post.findByPk(postId);
111 }
112 }
113 const postInDatabase = await Post.findOne({
114 where: {
115 remotePostId: remotePostId,
116 },
117 });
118 if (postInDatabase && !localPostToForceUpdate) {
119 if (postInDatabase.remotePostId) {
120 const parentPostPetition = await getPetitionSigned(
121 user,
122 postInDatabase.remotePostId
123 );
124 if (parentPostPetition) {
125 await loadPoll(parentPostPetition, postInDatabase, user);
126 }
127 }
128 return postInDatabase;
129 } else {
130 try {
131 const postPetition = remotePostObject
132 ? remotePostObject
133 : await getPetitionSigned(user, remotePostId);
134 if (postPetition && !localPostToForceUpdate) {
135 const remotePostInDatabase = await Post.findOne({
136 where: {
137 remotePostId: postPetition.id,
138 },
139 });
140 if (remotePostInDatabase) {
141 if (remotePostInDatabase.remotePostId) {
142 const parentPostPetition = await getPetitionSigned(
143 user,
144 remotePostInDatabase.remotePostId
145 );
146 if (parentPostPetition) {
147 await loadPoll(parentPostPetition, remotePostInDatabase, user);
148 }
149 }
150 return remotePostInDatabase;
151 }
152 }
153 // peertube: what the fuck
154 let actorUrl = postPetition.attributedTo;
155 if (Array.isArray(actorUrl)) {
156 actorUrl = actorUrl[0].id;
157 }
158 const remoteUser = await getRemoteActor(actorUrl, user);
159 if (remoteUser) {
160 const remoteHost = (await FederatedHost.findByPk(
161 remoteUser.federatedHostId as string
162 )) as FederatedHost;
163 const remoteUserServerBaned = remoteHost?.blocked
164 ? remoteHost.blocked
165 : false;
166 // HACK: some implementations (GTS IM LOOKING AT YOU) may send a single element instead of an array
167 // I should had used a funciton instead of this dirty thing, BUT you see, its late. Im eepy
168 // also this code is CRITICAL. A failure here is a big problem. So this hack it is
169 postPetition.tag = !Array.isArray(postPetition.tag)
170 ? [postPetition.tag].filter((elem) => elem)
171 : postPetition.tag;
172 const medias: any[] = [];
173 const fediTags: fediverseTag[] = [
174 ...new Set<fediverseTag>(
175 postPetition.tag
176 ?.filter((elem: fediverseTag) =>
177 [
178 postPetition.tag.some(
179 (tag: fediverseTag) => tag.type == "WafrnHashtag"
180 )
181 ? "WafrnHashtag"
182 : "Hashtag",
183 ].includes(elem.type)
184 )
185 .map((elem: fediverseTag) => {
186 return { href: elem.href, type: elem.type, name: elem.name };
187 })
188 ),
189 ];
190 let fediMentions: fediverseTag[] = postPetition.tag?.filter(
191 (elem: fediverseTag) => elem.type === "Mention"
192 );
193 if (fediMentions == undefined) {
194 fediMentions = postPetition.to.map((elem: string) => {
195 return { href: elem };
196 });
197 }
198 const fediEmojis: any[] = postPetition.tag?.filter(
199 (elem: fediverseTag) => elem.type === "Emoji"
200 );
201
202 const privacy = getApObjectPrivacy(postPetition, remoteUser);
203
204 let postTextContent = `${
205 postPetition.content ? postPetition.content : ""
206 }`; // Fix for bridgy giving this as undefined
207 if (postPetition.type == "Video") {
208 // peertube federation. We just add a link to the video, federating this is HELL
209 postTextContent =
210 postTextContent +
211 ` <a href="${postPetition.id}" target="_blank">${postPetition.id}</a>`;
212 }
213 if (
214 postPetition.tag &&
215 postPetition.tag.some(
216 (tag: fediverseTag) => tag.type === "WafrnHashtag"
217 )
218 ) {
219 // Ok we have wafrn hashtags with us, we are probably talking with another wafrn! Crazy, I know
220 const dom = cheerio.load(postTextContent);
221 const tags = dom("a.hashtag").html("");
222 postTextContent = dom.html();
223 }
224 if (
225 postPetition.attachment &&
226 postPetition.attachment.length > 0 &&
227 (!remoteUser.banned || options?.allowMediaFromBanned)
228 ) {
229 for await (const remoteFile of postPetition.attachment) {
230 if (remoteFile.type !== "Link") {
231 const wafrnMedia = await Media.create({
232 url: remoteFile.url,
233 NSFW: postPetition?.sensitive,
234 userId:
235 remoteUserServerBaned || remoteUser.banned
236 ? (
237 await deletedUser
238 )?.id
239 : remoteUser.id,
240 description: remoteFile.name,
241 ipUpload: "IMAGE_FROM_OTHER_FEDIVERSE_INSTANCE",
242 mediaOrder: postPetition.attachment.indexOf(remoteFile), // could be non consecutive but its ok
243 external: true,
244 mediaType: remoteFile.mediaType ? remoteFile.mediaType : "",
245 blurhash: remoteFile.blurhash ? remoteFile.blurhash : null,
246 height: remoteFile.height ? remoteFile.height : null,
247 width: remoteFile.width ? remoteFile.width : null,
248 });
249 if (
250 !wafrnMedia.mediaType ||
251 (wafrnMedia.mediaType?.startsWith("image") && !wafrnMedia.width)
252 ) {
253 await updateMediaDataQueue.add(`updateMedia:${wafrnMedia.id}`, {
254 mediaId: wafrnMedia.id,
255 });
256 }
257 medias.push(wafrnMedia);
258 } else {
259 postTextContent =
260 "" +
261 postTextContent +
262 `<a href="${remoteFile.href}" >${remoteFile.href}</a>`;
263 }
264 }
265 }
266 const lemmyName = postPetition.name ? postPetition.name : "";
267 postTextContent = postTextContent
268 ? postTextContent
269 : `<p>${lemmyName}</p>`;
270 let createdAt = new Date(postPetition.published);
271 if (createdAt.getTime() > new Date().getTime()) {
272 createdAt = new Date();
273 }
274
275 let bskyUri: string | undefined, bskyCid: string | undefined;
276 let existingBskyPost: Post | undefined;
277 // check if it's a bridgy post or a post from a wafrn by checking a valid FEP-fffd
278 if (postPetition.url && Array.isArray(postPetition.url)) {
279 const url = postPetition.url as Array<
280 string | { type: string; href: string }
281 >;
282 const firstFffd = url.find((x) => typeof x !== "string");
283 // check if it starts at at:// then its a bridged post, we do not touch it if it's not
284 if (firstFffd && firstFffd.href.startsWith("at://")) {
285 // get it's bsky counterparts first, we need the cid
286 const thread = await getPostThreadSafe({
287 uri: firstFffd.href,
288 });
289 if (thread && thread.success) {
290 try {
291 const threadView = thread.data.thread as ThreadViewPost;
292 bskyCid = threadView.post.cid;
293 bskyUri = threadView.post.uri;
294 // check if it cames from wafrn
295 if (
296 !(
297 threadView.post.record as {
298 fediverseId: string | undefined;
299 }
300 ).fediverseId
301 ) {
302 // this is a bridgy fed post, assume main post is on bsky, use bsky user
303 const postId = await processSinglePost(threadView.post);
304 if (postId) {
305 const post = await Post.findByPk(postId);
306 if (post) {
307 post.remotePostId = postPetition.id;
308 await post.save();
309 return post;
310 }
311 }
312 } else {
313 // now this is a wafrn post, where we do a thing little bit different
314 // first we going to check if the post is already on db because this can break everything
315 let existingPost = await Post.findOne({
316 where: {
317 bskyCid: threadView.post.cid,
318 remotePostId: null,
319 },
320 });
321 if (existingPost) {
322 existingBskyPost = existingPost;
323 // do not attempt to merge it right now, this will crash backend
324 bskyCid = undefined;
325 bskyUri = undefined;
326 }
327 }
328 } catch {}
329 }
330 }
331 }
332
333 const postToCreate: any = {
334 content: "" + postTextContent,
335 content_warning: postPetition.summary
336 ? postPetition.summary
337 : remoteUser.NSFW
338 ? "User is marked as NSFW by this instance staff. Possible NSFW without tagging"
339 : "",
340 createdAt: createdAt,
341 updatedAt: createdAt,
342 userId:
343 remoteUserServerBaned || remoteUser.banned
344 ? (await deletedUser)?.id
345 : remoteUser.id,
346 remotePostId: postPetition.id,
347 privacy: privacy,
348 bskyUri: postPetition.blueskyUri,
349 displayUrl: Array.isArray(postPetition.url)
350 ? postPetition.url[0]
351 : postPetition.url,
352 bskyCid: postPetition.blueskyCid,
353 ...(bskyCid && bskyUri
354 ? {
355 bskyCid,
356 bskyUri,
357 }
358 : {}),
359 };
360
361 if (postPetition.name) {
362 postToCreate.title = postPetition.name;
363 }
364
365 const mentionedUsersIds: string[] = [];
366 const quotes: any[] = [];
367 try {
368 if (!remoteUser.banned && !remoteUserServerBaned) {
369 for await (const mention of fediMentions) {
370 let mentionedUser;
371 if (
372 mention.href?.indexOf(completeEnvironment.frontendUrl) !== -1
373 ) {
374 const username = mention.href?.substring(
375 `${completeEnvironment.frontendUrl}/fediverse/blog/`.length
376 ) as string;
377 mentionedUser = await User.findOne({
378 where: sequelize.where(
379 sequelize.fn("lower", sequelize.col("url")),
380 username.toLowerCase()
381 ),
382 });
383 } else {
384 mentionedUser = await getRemoteActor(mention.href, user);
385 }
386 if (
387 mentionedUser?.id &&
388 mentionedUser.id != (await deletedUser)?.id &&
389 !mentionedUsersIds.includes(mentionedUser.id)
390 ) {
391 mentionedUsersIds.push(mentionedUser.id);
392 }
393 }
394 }
395 } catch (error) {
396 logger.info({ message: "problem processing mentions", error });
397 }
398
399 if (
400 postPetition.inReplyTo &&
401 postPetition.id !== postPetition.inReplyTo
402 ) {
403 const parent = await getPostThreadRecursive(
404 user,
405 postPetition.inReplyTo.id
406 ? postPetition.inReplyTo.id
407 : postPetition.inReplyTo
408 );
409 postToCreate.parentId = parent?.id;
410 }
411
412 const existingPost = localPostToForceUpdate
413 ? await Post.findByPk(localPostToForceUpdate)
414 : undefined;
415
416 if (existingPost) {
417 existingPost.set(postToCreate);
418 await existingPost.save();
419 await loadPoll(postPetition, existingPost, user);
420 }
421
422 const newPost = existingPost
423 ? existingPost
424 : await Post.create(postToCreate);
425 try {
426 if (!remoteUser.banned && !remoteUserServerBaned && fediEmojis) {
427 processEmojis(newPost, fediEmojis);
428 }
429 } catch (error) {
430 logger.debug("Problem processing emojis");
431 }
432 newPost.setMedias(medias);
433 try {
434 if (postPetition.quote || postPetition.quoteUrl) {
435 const urlQuote = postPetition.quoteUrl || postPetition.quote;
436 const postToQuote = await getPostThreadRecursive(user, urlQuote);
437 if (postToQuote && postToQuote.privacy != Privacy.DirectMessage) {
438 quotes.push(postToQuote);
439 }
440 if (!postToQuote) {
441 postToCreate.content =
442 postToCreate.content + `<p>RE: ${urlQuote}</p>`;
443 }
444 const postsToQuotePromise: any[] = [];
445 postPetition.tag
446 ?.filter((elem: fediverseTag) => elem.type === "Link")
447 .forEach((quote: fediverseTag) => {
448 postsToQuotePromise.push(
449 getPostThreadRecursive(user, quote.href as string)
450 );
451 postToCreate.content = postToCreate.content.replace(
452 quote.name,
453 ""
454 );
455 });
456 const quotesToAdd = await Promise.allSettled(postsToQuotePromise);
457 const quotesThatWillGetAdded = quotesToAdd.filter(
458 (elem) =>
459 elem.status === "fulfilled" &&
460 elem.value &&
461 elem.value.privacy !== 10
462 );
463 quotesThatWillGetAdded.forEach((quot) => {
464 if (
465 quot.status === "fulfilled" &&
466 !quotes.map((q) => q.id).includes(quot.value.id)
467 ) {
468 quotes.push(quot.value);
469 }
470 });
471 }
472 } catch (error) {
473 logger.info("Error processing quotes");
474 logger.debug(error);
475 }
476 newPost.setQuoted(quotes);
477
478 await newPost.save();
479
480 await bulkCreateNotifications(
481 quotes.map((quote) => ({
482 notificationType: "QUOTE",
483 notifiedUserId: quote.userId,
484 userId: newPost.userId,
485 postId: newPost.id,
486 createdAt: new Date(newPost.createdAt),
487 })),
488 {
489 postContent: newPost.content,
490 userUrl: remoteUser.url,
491 }
492 );
493 try {
494 if (!remoteUser.banned && !remoteUserServerBaned) {
495 await addTagsToPost(newPost, fediTags);
496 }
497 } catch (error) {
498 logger.info("problem processing tags");
499 }
500 try {
501 await addAsksToPost(newPost, fediTags);
502 } catch (error) {}
503 if (mentionedUsersIds.length != 0) {
504 await processMentions(newPost, mentionedUsersIds);
505 }
506 await loadPoll(remotePostObject, newPost, user);
507 const postCleanContent = dompurify
508 .sanitize(newPost.content, { ALLOWED_TAGS: [] })
509 .trim();
510 const mentions = await newPost.getMentionPost();
511 if (postCleanContent.startsWith("!ask") && mentions.length === 1) {
512 let askContent = postCleanContent.split(
513 `!ask @${mentions[0].url}`
514 )[1];
515 if (askContent.startsWith("@" + completeEnvironment.instanceUrl)) {
516 askContent = askContent.split(
517 "@" + completeEnvironment.instanceUrl
518 )[1];
519 }
520 await Ask.create({
521 question: askContent,
522 userAsker: newPost.userId,
523 userAsked: mentions[0].id,
524 answered: false,
525 apObject: JSON.stringify(postPetition),
526 });
527 }
528
529 if (existingBskyPost) {
530 // very expensive updates! but only happens when bsky
531 // post is already on db but the fedi post is not
532 await EmojiReaction.update(
533 {
534 postId: newPost.id,
535 },
536 {
537 where: {
538 postId: existingBskyPost.id,
539 },
540 }
541 );
542 await Notification.update(
543 {
544 postId: newPost.id,
545 },
546 {
547 where: {
548 postId: existingBskyPost.id,
549 },
550 }
551 );
552 await PostReport.update(
553 {
554 postId: newPost.id,
555 },
556 {
557 where: {
558 postId: existingBskyPost.id,
559 },
560 }
561 );
562 try {
563 await PostAncestor.update(
564 {
565 postsId: newPost.id,
566 },
567 {
568 where: {
569 postsId: existingBskyPost.id,
570 },
571 }
572 );
573 } catch {}
574 await QuestionPoll.update(
575 {
576 postId: newPost.id,
577 },
578 {
579 where: {
580 postId: existingBskyPost.id,
581 },
582 }
583 );
584 await Quotes.update(
585 {
586 quoterPostId: newPost.id,
587 },
588 {
589 where: {
590 quoterPostId: existingBskyPost.id,
591 },
592 }
593 );
594 if (
595 !(await Quotes.findOne({
596 where: {
597 quotedPostId: newPost.id,
598 },
599 }))
600 ) {
601 await Quotes.update(
602 {
603 quotedPostId: newPost.id,
604 },
605 {
606 where: {
607 quotedPostId: existingBskyPost.id,
608 },
609 }
610 );
611 }
612 await RemoteUserPostView.update(
613 {
614 postId: newPost.id,
615 },
616 {
617 where: {
618 postId: existingBskyPost.id,
619 },
620 }
621 );
622 await SilencedPost.update(
623 {
624 postId: newPost.id,
625 },
626 {
627 where: {
628 postId: existingBskyPost.id,
629 },
630 }
631 );
632 await SilencedPost.update(
633 {
634 postId: newPost.id,
635 },
636 {
637 where: {
638 postId: existingBskyPost.id,
639 },
640 }
641 );
642 await UserBitesPostRelation.update(
643 {
644 postId: newPost.id,
645 },
646 {
647 where: {
648 postId: existingBskyPost.id,
649 },
650 }
651 );
652 await UserBookmarkedPosts.update(
653 {
654 postId: newPost.id,
655 },
656 {
657 where: {
658 postId: existingBskyPost.id,
659 },
660 }
661 );
662 await UserLikesPostRelations.update(
663 {
664 postId: newPost.id,
665 },
666 {
667 where: {
668 postId: existingBskyPost.id,
669 },
670 }
671 );
672 await Post.update(
673 {
674 parentId: newPost.id,
675 },
676 {
677 where: {
678 parentId: existingBskyPost.id,
679 },
680 }
681 );
682
683 // now we delete the existing bsky post
684 await existingBskyPost.destroy();
685
686 // THEN we merge it
687 newPost.bskyCid = existingBskyPost.bskyCid;
688 newPost.bskyUri = existingBskyPost.bskyUri;
689 await newPost.save();
690 }
691
692 return newPost;
693 }
694 } catch (error) {
695 logger.trace({
696 message: "error getting remote post",
697 url: remotePostId,
698 user: user.url,
699 problem: error,
700 });
701 return null;
702 }
703 }
704}
705
706async function addAsksToPost(post: Post, tags: fediverseTag[]) {
707 const asks = tags.filter((elem) => elem.type === "AskQuestion");
708 if (asks.length) {
709 const ask = asks[0];
710 const userAsker = await getRemoteActor(
711 ask.actor as string,
712 await getAdminUser()
713 );
714 const textToRemove = ask.representation as string;
715 const askText = ask.name;
716 if (textToRemove) {
717 post.content = post.content.replace(textToRemove, "");
718 await Ask.create({
719 answered: true,
720 postId: post.id,
721 userAsker: userAsker ? userAsker.id : undefined,
722 userAsked: post.userId,
723 });
724 await post.save();
725 }
726 }
727}
728
729async function addTagsToPost(post: any, originalTags: fediverseTag[]) {
730 let tags = [...originalTags];
731 const res = await post.setPostTags([]);
732 if (tags.some((elem) => elem.name == "WafrnHashtag")) {
733 tags = tags.filter((elem) => elem.name == "WafrnHashtag");
734 }
735 return await PostTag.bulkCreate(
736 tags
737 .filter((elem) => elem && post && elem.name && post.id)
738 .map((elem) => {
739 return {
740 tagName: elem?.name?.replace("#", ""),
741 postId: post.id,
742 };
743 })
744 );
745}
746
747async function processMentions(post: any, userIds: string[]) {
748 await post.setMentionPost([]);
749 await Notification.destroy({
750 where: {
751 notificationType: "MENTION",
752 postId: post.id,
753 },
754 });
755 const blocks = await Blocks.findAll({
756 where: {
757 blockerId: {
758 [Op.in]: userIds,
759 },
760 blockedId: post.userId,
761 },
762 });
763 const remoteUser = await User.findByPk(post.userId, {
764 attributes: ["url", "federatedHostId"],
765 });
766 const userServerBlocks = await ServerBlock.findAll({
767 where: {
768 userBlockerId: {
769 [Op.in]: userIds,
770 },
771 blockedServerId: remoteUser?.federatedHostId || "",
772 },
773 });
774 const blockerIds: string[] = blocks
775 .map((block: any) => block.blockerId)
776 .concat(userServerBlocks.map((elem: any) => elem.userBlockerId));
777
778 await bulkCreateNotifications(
779 userIds.map((mentionedUserId) => ({
780 notificationType: "MENTION",
781 notifiedUserId: mentionedUserId,
782 userId: post.userId,
783 postId: post.id,
784 createdAt: new Date(post.createdAt),
785 })),
786 {
787 postContent: post.content,
788 userUrl: remoteUser?.url,
789 }
790 );
791
792 return await PostMentionsUserRelation.bulkCreate(
793 userIds
794 .filter((elem) => !blockerIds.includes(elem))
795 .map((elem) => {
796 return {
797 postId: post.id,
798 userId: elem,
799 };
800 }),
801 {
802 ignoreDuplicates: true,
803 }
804 );
805}
806
807async function processEmojis(post: any, fediEmojis: any[]) {
808 let emojis: any[] = [];
809 let res: any;
810 const emojiIds: string[] = Array.from(
811 new Set(fediEmojis.map((emoji: any) => emoji.id))
812 );
813 const foundEmojis = await Emoji.findAll({
814 where: {
815 id: {
816 [Op.in]: emojiIds,
817 },
818 },
819 });
820 foundEmojis.forEach((emoji: any) => {
821 const newData = fediEmojis.find(
822 (foundEmoji: any) => foundEmoji.id === emoji.id
823 );
824 if (newData && newData.icon?.url) {
825 emoji.set({
826 url: newData.icon.url,
827 });
828 emoji.save();
829 } else {
830 logger.debug("issue with emoji");
831 logger.debug(emoji);
832 logger.debug(newData);
833 }
834 });
835 emojis = emojis.concat(foundEmojis);
836 const notFoundEmojis = fediEmojis.filter(
837 (elem: any) => !foundEmojis.find((found: any) => found.id === elem.id)
838 );
839 if (fediEmojis && notFoundEmojis && notFoundEmojis.length > 0) {
840 try {
841 const newEmojis = notFoundEmojis.map((newEmoji: any) => {
842 return {
843 id: newEmoji.id ? newEmoji.id : newEmoji.name + newEmoji.icon?.url,
844 name: newEmoji.name,
845 external: true,
846 url: newEmoji.icon?.url,
847 };
848 });
849 emojis = emojis.concat(
850 await Emoji.bulkCreate(newEmojis, { ignoreDuplicates: true })
851 );
852 } catch (error) {
853 logger.debug("Error with emojis");
854 logger.debug(error);
855 }
856 }
857
858 return await post.setEmojis(emojis);
859}
860
861export { getPostThreadRecursive };