2024-05-29 02:59:49 +02:00
import { mentionValidator } from "@/api" ;
import { dualLogger } from "@/loggers" ;
import { sanitizeHtml , sanitizeHtmlInline } from "@/sanitization" ;
2024-04-25 05:40:27 +02:00
import markdownItTaskLists from "@hackmd/markdown-it-task-lists" ;
2024-05-15 02:35:13 +02:00
import type { EntityValidator } from "@lysand-org/federation" ;
2024-04-07 07:30:49 +02:00
import { config } from "config-manager" ;
2024-04-13 14:20:12 +02:00
import {
type InferSelectModel ,
and ,
eq ,
inArray ,
isNull ,
or ,
sql ,
} from "drizzle-orm" ;
2024-04-07 07:30:49 +02:00
import linkifyHtml from "linkify-html" ;
2024-04-14 05:49:32 +02:00
import {
anyOf ,
charIn ,
createRegExp ,
digit ,
exactly ,
global ,
letter ,
maybe ,
oneOrMore ,
2024-04-14 12:36:25 +02:00
} from "magic-regexp" ;
2024-04-25 05:40:27 +02:00
import MarkdownIt from "markdown-it" ;
import markdownItAnchor from "markdown-it-anchor" ;
import markdownItContainer from "markdown-it-container" ;
import markdownItTocDoneRight from "markdown-it-toc-done-right" ;
2024-05-29 02:59:49 +02:00
import { db } from "~/drizzle/db" ;
import { type Attachments , Instances , Notes , Users } from "~/drizzle/schema" ;
import { Note } from "~/packages/database-interface/note" ;
import { User } from "~/packages/database-interface/user" ;
import { LogLevel } from "~/packages/log-manager" ;
import type { Status as APIStatus } from "~/types/mastodon/status" ;
2024-04-17 06:09:21 +02:00
import type { Application } from "./Application" ;
2024-04-25 05:40:27 +02:00
import { attachmentFromLysand } from "./Attachment" ;
import { type EmojiWithInstance , fetchEmoji } from "./Emoji" ;
2024-04-13 14:20:12 +02:00
import { objectToInboxRequest } from "./Federation" ;
2024-04-11 13:39:07 +02:00
import {
2024-04-17 06:09:21 +02:00
type UserWithInstance ,
2024-04-13 14:20:12 +02:00
type UserWithRelations ,
2024-04-11 13:39:07 +02:00
resolveWebFinger ,
2024-04-13 14:20:12 +02:00
transformOutputToUserWithRelations ,
2024-04-11 13:39:07 +02:00
userExtrasTemplate ,
2024-04-13 14:20:12 +02:00
userRelations ,
2024-04-11 13:39:07 +02:00
} from "./User" ;
2024-04-17 08:36:01 +02:00
export type Status = InferSelectModel < typeof Notes > ;
2024-04-11 13:39:07 +02:00
export type StatusWithRelations = Status & {
author : UserWithRelations ;
2024-04-17 06:09:21 +02:00
mentions : UserWithInstance [ ] ;
2024-04-17 08:36:01 +02:00
attachments : InferSelectModel < typeof Attachments > [ ] ;
2024-04-11 13:39:07 +02:00
reblog : StatusWithoutRecursiveRelations | null ;
emojis : EmojiWithInstance [ ] ;
2024-04-17 08:36:01 +02:00
reply : Status | null ;
quote : Status | null ;
2024-04-17 06:09:21 +02:00
application : Application | null ;
2024-04-11 13:39:07 +02:00
reblogCount : number ;
likeCount : number ;
replyCount : number ;
2024-05-09 01:19:53 +02:00
pinned : boolean ;
reblogged : boolean ;
muted : boolean ;
liked : boolean ;
2024-04-11 13:39:07 +02:00
} ;
2023-09-12 22:48:10 +02:00
2024-04-11 13:39:07 +02:00
export type StatusWithoutRecursiveRelations = Omit <
StatusWithRelations ,
2024-04-17 08:36:01 +02:00
"reply" | "quote" | "reblog"
2023-11-27 01:56:16 +01:00
> ;
2023-10-23 07:39:42 +02:00
2023-11-11 03:36:06 +01:00
/ * *
2024-04-17 06:09:21 +02:00
* Wrapper against the Status object to make it easier to work with
* @param query
* @returns
2023-11-11 03:36:06 +01:00
* /
2024-04-17 06:09:21 +02:00
export const findManyNotes = async (
2024-04-17 08:36:01 +02:00
query : Parameters < typeof db.query.Notes.findMany > [ 0 ] ,
2024-05-09 01:19:53 +02:00
userId? : string ,
2024-04-11 13:39:07 +02:00
) : Promise < StatusWithRelations [ ] > = > {
2024-04-17 08:36:01 +02:00
const output = await db . query . Notes . findMany ( {
2024-04-11 13:39:07 +02:00
. . . query ,
with : {
. . . query ? . with ,
2024-05-08 23:51:47 +02:00
attachments : true ,
2024-04-11 14:12:16 +02:00
emojis : {
with : {
emoji : {
with : {
instance : true ,
} ,
} ,
} ,
} ,
2024-04-11 13:39:07 +02:00
author : {
with : {
. . . userRelations ,
} ,
2024-04-17 08:36:01 +02:00
extras : userExtrasTemplate ( "Notes_author" ) ,
2024-04-11 13:39:07 +02:00
} ,
mentions : {
with : {
user : {
2024-04-17 06:09:21 +02:00
with : {
instance : true ,
} ,
2024-04-11 13:39:07 +02:00
} ,
} ,
} ,
reblog : {
with : {
attachments : true ,
emojis : {
with : {
emoji : {
with : {
instance : true ,
} ,
} ,
} ,
} ,
likes : true ,
application : true ,
mentions : {
with : {
user : {
2024-04-12 01:55:58 +02:00
with : userRelations ,
extras : userExtrasTemplate (
2024-04-17 08:36:01 +02:00
"Notes_reblog_mentions_user" ,
2024-04-12 01:55:58 +02:00
) ,
2024-04-11 13:39:07 +02:00
} ,
} ,
} ,
author : {
with : {
. . . userRelations ,
} ,
2024-04-17 08:36:01 +02:00
extras : userExtrasTemplate ( "Notes_reblog_author" ) ,
2024-04-11 13:39:07 +02:00
} ,
} ,
2024-04-17 06:09:21 +02:00
extras : {
2024-05-08 23:51:47 +02:00
reblogCount :
sql ` (SELECT COUNT(*) FROM "Notes" WHERE "Notes"."reblogId" = "Notes_reblog".id) ` . as (
"reblog_count" ,
) ,
likeCount :
sql ` (SELECT COUNT(*) FROM "Likes" WHERE "Likes"."likedId" = "Notes_reblog".id) ` . as (
"like_count" ,
) ,
replyCount :
sql ` (SELECT COUNT(*) FROM "Notes" WHERE "Notes"."replyId" = "Notes_reblog".id) ` . as (
"reply_count" ,
) ,
2024-05-09 01:19:53 +02:00
pinned : userId
? sql ` EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = "Notes_reblog".id AND "UserToPinnedNotes"."userId" = ${ userId } ) ` . as (
"pinned" ,
)
: sql ` false ` . as ( "pinned" ) ,
reblogged : userId
? sql ` EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."authorId" = ${ userId } AND "Notes"."reblogId" = "Notes_reblog".id) ` . as (
"reblogged" ,
)
: sql ` false ` . as ( "reblogged" ) ,
muted : userId
? sql ` EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."ownerId" = ${ userId } AND "Relationships"."subjectId" = "Notes_reblog"."authorId" AND "Relationships"."muting" = true) ` . as (
"muted" ,
)
: sql ` false ` . as ( "muted" ) ,
liked : userId
? sql ` EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = "Notes_reblog".id AND "Likes"."likerId" = ${ userId } ) ` . as (
"liked" ,
)
: sql ` false ` . as ( "liked" ) ,
2024-04-11 13:39:07 +02:00
} ,
} ,
2024-04-17 08:36:01 +02:00
reply : true ,
quote : true ,
2024-04-11 13:39:07 +02:00
} ,
extras : {
2024-05-08 23:51:47 +02:00
reblogCount :
sql ` (SELECT COUNT(*) FROM "Notes" WHERE "Notes"."reblogId" = "Notes".id) ` . as (
"reblog_count" ,
) ,
likeCount :
sql ` (SELECT COUNT(*) FROM "Likes" WHERE "Likes"."likedId" = "Notes".id) ` . as (
"like_count" ,
) ,
replyCount :
sql ` (SELECT COUNT(*) FROM "Notes" WHERE "Notes"."replyId" = "Notes".id) ` . as (
"reply_count" ,
) ,
2024-05-09 01:19:53 +02:00
pinned : userId
? sql ` EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = "Notes".id AND "UserToPinnedNotes"."userId" = ${ userId } ) ` . as (
"pinned" ,
)
: sql ` false ` . as ( "pinned" ) ,
reblogged : userId
? sql ` EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."authorId" = ${ userId } AND "Notes"."reblogId" = "Notes".id) ` . as (
"reblogged" ,
)
: sql ` false ` . as ( "reblogged" ) ,
muted : userId
? sql ` EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."ownerId" = ${ userId } AND "Relationships"."subjectId" = "Notes"."authorId" AND "Relationships"."muting" = true) ` . as (
"muted" ,
)
: sql ` false ` . as ( "muted" ) ,
liked : userId
? sql ` EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = "Notes".id AND "Likes"."likerId" = ${ userId } ) ` . as (
"liked" ,
)
: sql ` false ` . as ( "liked" ) ,
2024-04-11 13:39:07 +02:00
. . . query ? . extras ,
} ,
} ) ;
return output . map ( ( post ) = > ( {
. . . post ,
author : transformOutputToUserWithRelations ( post . author ) ,
2024-04-17 06:09:21 +02:00
mentions : post.mentions.map ( ( mention ) = > ( {
. . . mention . user ,
2024-04-17 08:36:01 +02:00
endpoints : mention.user.endpoints ,
2024-04-17 06:09:21 +02:00
} ) ) ,
2024-04-17 08:36:01 +02:00
emojis : ( post . emojis ? ? [ ] ) . map ( ( emoji ) = > emoji . emoji ) ,
2024-04-11 13:39:07 +02:00
reblog : post.reblog && {
. . . post . reblog ,
author : transformOutputToUserWithRelations ( post . reblog . author ) ,
2024-04-17 06:09:21 +02:00
mentions : post.reblog.mentions.map ( ( mention ) = > ( {
. . . mention . user ,
2024-04-17 08:36:01 +02:00
endpoints : mention.user.endpoints ,
2024-04-17 06:09:21 +02:00
} ) ) ,
2024-04-17 08:36:01 +02:00
emojis : ( post . reblog . emojis ? ? [ ] ) . map ( ( emoji ) = > emoji . emoji ) ,
2024-04-17 06:09:21 +02:00
reblogCount : Number ( post . reblog . reblogCount ) ,
likeCount : Number ( post . reblog . likeCount ) ,
replyCount : Number ( post . reblog . replyCount ) ,
2024-05-09 01:19:53 +02:00
pinned : Boolean ( post . reblog . pinned ) ,
reblogged : Boolean ( post . reblog . reblogged ) ,
muted : Boolean ( post . reblog . muted ) ,
liked : Boolean ( post . reblog . liked ) ,
2024-04-11 13:39:07 +02:00
} ,
reblogCount : Number ( post . reblogCount ) ,
likeCount : Number ( post . likeCount ) ,
replyCount : Number ( post . replyCount ) ,
2024-05-09 01:19:53 +02:00
pinned : Boolean ( post . pinned ) ,
reblogged : Boolean ( post . reblogged ) ,
muted : Boolean ( post . muted ) ,
liked : Boolean ( post . liked ) ,
2024-04-11 13:39:07 +02:00
} ) ) ;
} ;
2024-04-17 06:09:21 +02:00
export const resolveNote = async (
2024-04-10 10:37:58 +02:00
uri? : string ,
2024-05-15 02:35:13 +02:00
providedNote? : typeof EntityValidator . $Note ,
2024-04-17 06:09:21 +02:00
) : Promise < Note > = > {
2024-04-10 10:37:58 +02:00
if ( ! uri && ! providedNote ) {
throw new Error ( "No URI or note provided" ) ;
}
2024-04-17 06:09:21 +02:00
const foundStatus = await Note . fromSql (
2024-04-17 08:36:01 +02:00
eq ( Notes . uri , uri ? ? providedNote ? . uri ? ? "" ) ,
2024-04-17 06:09:21 +02:00
) ;
2024-04-10 10:37:58 +02:00
if ( foundStatus ) return foundStatus ;
2024-05-15 02:35:13 +02:00
let note = providedNote ? ? null ;
2024-04-10 10:37:58 +02:00
if ( uri ) {
if ( ! URL . canParse ( uri ) ) {
throw new Error ( ` Invalid URI to parse ${ uri } ` ) ;
}
const response = await fetch ( uri , {
method : "GET" ,
headers : {
Accept : "application/json" ,
} ,
} ) ;
2024-05-15 02:35:13 +02:00
note = ( await response . json ( ) ) as typeof EntityValidator . $Note ;
2024-04-10 10:37:58 +02:00
}
if ( ! note ) {
throw new Error ( "No note was able to be fetched" ) ;
}
if ( note . type !== "Note" ) {
throw new Error ( "Invalid object type" ) ;
}
if ( ! note . author ) {
throw new Error ( "Invalid object author" ) ;
}
2024-04-25 05:40:27 +02:00
const author = await User . resolve ( note . author ) ;
2024-04-10 10:37:58 +02:00
if ( ! author ) {
throw new Error ( "Invalid object author" ) ;
}
2024-04-11 03:40:43 +02:00
const attachments = [ ] ;
for ( const attachment of note . attachments ? ? [ ] ) {
const resolvedAttachment = await attachmentFromLysand ( attachment ) . catch (
( e ) = > {
2024-04-14 13:20:55 +02:00
dualLogger . logError (
LogLevel . ERROR ,
"Federation.StatusResolver" ,
e ,
) ;
2024-04-11 03:40:43 +02:00
return null ;
} ,
) ;
if ( resolvedAttachment ) {
attachments . push ( resolvedAttachment ) ;
}
}
const emojis = [ ] ;
for ( const emoji of note . extensions ? . [ "org.lysand:custom_emojis" ] ? . emojis ? ?
[ ] ) {
const resolvedEmoji = await fetchEmoji ( emoji ) . catch ( ( e ) = > {
2024-04-14 13:20:55 +02:00
dualLogger . logError ( LogLevel . ERROR , "Federation.StatusResolver" , e ) ;
2024-04-11 03:40:43 +02:00
return null ;
} ) ;
if ( resolvedEmoji ) {
emojis . push ( resolvedEmoji ) ;
}
}
2024-04-11 03:31:33 +02:00
2024-04-17 06:09:21 +02:00
const createdNote = await Note . fromData (
2024-04-10 10:37:58 +02:00
author ,
note . content ? ? {
"text/plain" : {
content : "" ,
} ,
} ,
note . visibility as APIStatus [ "visibility" ] ,
note . is_sensitive ? ? false ,
note . subject ? ? "" ,
2024-04-11 03:40:43 +02:00
emojis ,
2024-04-10 10:37:58 +02:00
note . uri ,
await Promise . all (
( note . mentions ? ? [ ] )
2024-04-25 05:40:27 +02:00
. map ( ( mention ) = > User . resolve ( mention ) )
. filter ( ( mention ) = > mention !== null ) as Promise < User > [ ] ,
2024-04-10 10:37:58 +02:00
) ,
2024-04-11 03:31:33 +02:00
attachments . map ( ( a ) = > a . id ) ,
2024-04-17 06:09:21 +02:00
note . replies_to
? ( await resolveNote ( note . replies_to ) ) . getStatus ( ) . id
: undefined ,
note . quotes
? ( await resolveNote ( note . quotes ) ) . getStatus ( ) . id
: undefined ,
2024-04-10 10:37:58 +02:00
) ;
2024-04-11 13:39:07 +02:00
2024-04-17 06:09:21 +02:00
if ( ! createdNote ) {
2024-04-11 13:39:07 +02:00
throw new Error ( "Failed to create status" ) ;
}
2024-04-17 06:09:21 +02:00
return createdNote ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 03:09:14 +02:00
2024-04-10 04:05:02 +02:00
/ * *
* Get people mentioned in the content ( match @username or @username @domain . com mentions )
* @param text The text to parse mentions from .
* @returns An array of users mentioned in the text .
* /
2024-04-25 05:40:27 +02:00
export const parseTextMentions = async ( text : string ) : Promise < User [ ] > = > {
2024-05-13 04:27:40 +02:00
const mentionedPeople = [ . . . text . matchAll ( mentionValidator ) ] ? ? [ ] ;
2024-04-11 13:39:07 +02:00
if ( mentionedPeople . length === 0 ) return [ ] ;
2024-04-14 05:49:32 +02:00
const baseUrlHost = new URL ( config . http . base_url ) . host ;
2024-04-12 03:52:09 +02:00
2024-04-14 05:49:32 +02:00
const isLocal = ( host? : string ) = > host === baseUrlHost || ! host ;
2024-04-12 03:52:09 +02:00
2024-04-14 05:49:32 +02:00
const foundUsers = await db
. select ( {
2024-04-17 08:36:01 +02:00
id : Users.id ,
username : Users.username ,
baseUrl : Instances.baseUrl ,
2024-04-14 05:49:32 +02:00
} )
2024-04-17 08:36:01 +02:00
. from ( Users )
. leftJoin ( Instances , eq ( Users . instanceId , Instances . id ) )
2024-04-14 05:49:32 +02:00
. where (
or (
. . . mentionedPeople . map ( ( person ) = >
and (
2024-04-17 08:36:01 +02:00
eq ( Users . username , person ? . [ 1 ] ? ? "" ) ,
2024-04-14 05:49:32 +02:00
isLocal ( person ? . [ 2 ] )
2024-04-17 08:36:01 +02:00
? isNull ( Users . instanceId )
: eq ( Instances . baseUrl , person ? . [ 2 ] ? ? "" ) ,
2024-04-14 05:49:32 +02:00
) ,
) ,
) ,
) ;
2024-04-12 03:52:09 +02:00
2024-04-14 05:49:32 +02:00
const notFoundRemoteUsers = mentionedPeople . filter (
( person ) = >
! isLocal ( person ? . [ 2 ] ) &&
! foundUsers . find (
( user ) = >
user . username === person ? . [ 1 ] &&
user . baseUrl === person ? . [ 2 ] ,
) ,
) ;
2024-04-12 03:52:09 +02:00
const finalList =
2024-04-14 05:49:32 +02:00
foundUsers . length > 0
2024-04-25 05:40:27 +02:00
? await User . manyFromSql (
inArray (
Users . id ,
foundUsers . map ( ( u ) = > u . id ) ,
) ,
)
2024-04-12 03:52:09 +02:00
: [ ] ;
2024-04-11 00:47:02 +02:00
// Attempt to resolve mentions that were not found
2024-04-14 05:49:32 +02:00
for ( const person of notFoundRemoteUsers ) {
2024-04-11 00:47:02 +02:00
const user = await resolveWebFinger (
2024-04-14 05:49:32 +02:00
person ? . [ 1 ] ? ? "" ,
person ? . [ 2 ] ? ? "" ,
2024-04-11 00:47:02 +02:00
) ;
if ( user ) {
2024-04-11 13:39:07 +02:00
finalList . push ( user ) ;
2024-04-11 00:47:02 +02:00
}
}
2024-04-11 13:39:07 +02:00
return finalList ;
2024-04-10 04:05:02 +02:00
} ;
2024-04-25 05:40:27 +02:00
export const replaceTextMentions = async ( text : string , mentions : User [ ] ) = > {
2024-04-10 11:33:21 +02:00
let finalText = text ;
for ( const mention of mentions ) {
2024-04-25 05:40:27 +02:00
const user = mention . getUser ( ) ;
2024-04-10 11:33:21 +02:00
// Replace @username and @username@domain
2024-04-25 05:40:27 +02:00
if ( user . instance ) {
2024-04-10 11:33:21 +02:00
finalText = finalText . replace (
2024-04-14 05:49:32 +02:00
createRegExp (
2024-04-25 05:40:27 +02:00
exactly ( ` @ ${ user . username } @ ${ user . instance . baseUrl } ` ) ,
2024-04-14 05:49:32 +02:00
[ global ] ,
) ,
2024-04-25 05:40:27 +02:00
` <a class="u-url mention" rel="nofollow noopener noreferrer" target="_blank" href=" ${ mention . getUri ( ) } ">@ ${
user . username
} @ $ { user . instance . baseUrl } < / a > ` ,
2024-04-10 11:33:21 +02:00
) ;
} else {
finalText = finalText . replace (
2024-04-12 03:52:09 +02:00
// Only replace @username if it doesn't have another @ right after
2024-04-14 05:49:32 +02:00
createRegExp (
2024-04-25 05:40:27 +02:00
exactly ( ` @ ${ user . username } ` )
2024-04-14 05:49:32 +02:00
. notBefore ( anyOf ( letter , digit , charIn ( "@" ) ) )
. notAfter ( anyOf ( letter , digit , charIn ( "@" ) ) ) ,
[ global ] ,
) ,
2024-04-25 05:40:27 +02:00
` <a class="u-url mention" rel="nofollow noopener noreferrer" target="_blank" href=" ${ mention . getUri ( ) } ">@ ${
user . username
} < / a > ` ,
2024-04-12 03:52:09 +02:00
) ;
finalText = finalText . replace (
2024-04-14 05:49:32 +02:00
createRegExp (
exactly (
2024-04-25 05:40:27 +02:00
` @ ${ user . username } @ ${
2024-04-14 05:49:32 +02:00
new URL ( config . http . base_url ) . host
} ` ,
) ,
[ global ] ,
) ,
2024-04-25 05:40:27 +02:00
` <a class="u-url mention" rel="nofollow noopener noreferrer" target="_blank" href=" ${ mention . getUri ( ) } ">@ ${
user . username
} < / a > ` ,
2024-04-10 11:33:21 +02:00
) ;
}
}
return finalText ;
} ;
2024-04-14 05:49:32 +02:00
export const contentToHtml = async (
2024-05-15 02:35:13 +02:00
content : typeof EntityValidator . $ContentFormat ,
2024-04-25 05:40:27 +02:00
mentions : User [ ] = [ ] ,
2024-05-12 03:27:28 +02:00
inline = false ,
2024-04-14 05:49:32 +02:00
) : Promise < string > = > {
2024-04-10 04:05:02 +02:00
let htmlContent : string ;
2024-05-12 03:27:28 +02:00
const sanitizer = inline ? sanitizeHtmlInline : sanitizeHtml ;
2024-04-10 04:05:02 +02:00
if ( content [ "text/html" ] ) {
2024-05-12 03:27:28 +02:00
htmlContent = await sanitizer ( content [ "text/html" ] . content ) ;
2024-04-10 04:05:02 +02:00
} else if ( content [ "text/markdown" ] ) {
2024-05-12 03:27:28 +02:00
htmlContent = await sanitizer (
2024-04-22 23:02:09 +02:00
await markdownParse ( content [ "text/markdown" ] . content ) ,
2024-04-10 04:05:02 +02:00
) ;
2024-04-22 23:02:09 +02:00
} else if ( content [ "text/plain" ] ? . content ) {
2024-04-10 04:05:02 +02:00
// Split by newline and add <p> tags
2024-05-12 03:27:28 +02:00
htmlContent = ( await sanitizer ( content [ "text/plain" ] . content ) )
2024-04-10 04:05:02 +02:00
. split ( "\n" )
. map ( ( line ) = > ` <p> ${ line } </p> ` )
. join ( "\n" ) ;
} else {
htmlContent = "" ;
}
2024-04-10 11:33:21 +02:00
// Replace mentions text
htmlContent = await replaceTextMentions ( htmlContent , mentions ? ? [ ] ) ;
2024-04-12 03:52:09 +02:00
// Linkify
htmlContent = linkifyHtml ( htmlContent , {
defaultProtocol : "https" ,
validate : {
email : ( ) = > false ,
} ,
target : "_blank" ,
rel : "nofollow noopener noreferrer" ,
} ) ;
2024-04-14 05:49:32 +02:00
return htmlContent ;
} ;
2024-04-22 23:02:09 +02:00
export const markdownParse = async ( content : string ) = > {
return ( await getMarkdownRenderer ( ) ) . render ( content ) ;
} ;
export const getMarkdownRenderer = async ( ) = > {
const renderer = MarkdownIt ( {
html : true ,
linkify : true ,
} ) ;
renderer . use ( markdownItAnchor , {
permalink : markdownItAnchor.permalink.ariaHidden ( {
symbol : "" ,
placement : "before" ,
} ) ,
} ) ;
renderer . use ( markdownItTocDoneRight , {
containerClass : "toc" ,
level : [ 1 , 2 , 3 , 4 ] ,
listType : "ul" ,
listClass : "toc-list" ,
itemClass : "toc-item" ,
linkClass : "toc-link" ,
} ) ;
renderer . use ( markdownItTaskLists ) ;
renderer . use ( markdownItContainer ) ;
return renderer ;
} ;
2024-04-17 06:09:21 +02:00
export const federateNote = async ( note : Note ) = > {
for ( const user of await note . getUsersToFederateTo ( ) ) {
2024-04-10 07:24:07 +02:00
// TODO: Add queue system
2024-04-10 07:51:00 +02:00
const request = await objectToInboxRequest (
2024-04-17 06:09:21 +02:00
note . toLysand ( ) ,
note . getAuthor ( ) ,
2024-04-10 07:51:00 +02:00
user ,
) ;
2024-04-07 07:30:49 +02:00
2024-04-10 07:24:07 +02:00
// Send request
const response = await fetch ( request ) ;
2024-04-07 07:30:49 +02:00
2024-04-10 07:24:07 +02:00
if ( ! response . ok ) {
2024-04-14 13:20:55 +02:00
dualLogger . log (
LogLevel . DEBUG ,
"Federation.Status" ,
await response . text ( ) ,
) ;
dualLogger . log (
LogLevel . ERROR ,
"Federation.Status" ,
2024-04-25 05:40:27 +02:00
` Failed to federate status ${
note . getStatus ( ) . id
} to $ { user . getUri ( ) } ` ,
2024-04-10 07:24:07 +02:00
) ;
}
2024-04-07 07:30:49 +02:00
}
2024-04-10 07:24:07 +02:00
} ;