2024-04-07 07:30:49 +02:00
import { addUserToMeilisearch } from "@meilisearch" ;
import { type Config , config } from "config-manager" ;
import { htmlToText } from "html-to-text" ;
import type { APIAccount } from "~types/entities/account" ;
import type { APISource } from "~types/entities/source" ;
2024-04-10 01:54:10 +02:00
import type * as Lysand from "lysand-types" ;
2024-04-11 13:39:07 +02:00
import {
fetchEmoji ,
emojiToAPI ,
emojiToLysand ,
type EmojiWithInstance ,
} from "./Emoji" ;
2023-11-11 03:36:06 +01:00
import { addInstanceIfNotExists } from "./Instance" ;
2024-04-09 04:12:54 +02:00
import { createNewRelationship } from "./Relationship" ;
2024-04-10 07:13:13 +02:00
import { getBestContentType , urlToContentFormat } from "@content_types" ;
2024-04-10 07:51:00 +02:00
import { objectToInboxRequest } from "./Federation" ;
2024-04-11 13:39:07 +02:00
import { and , eq , sql , type InferSelectModel } from "drizzle-orm" ;
import {
emojiToUser ,
instance ,
notification ,
relationship ,
user ,
} from "~drizzle/schema" ;
import { db } from "~drizzle/db" ;
export type User = InferSelectModel < typeof user > & {
endpoints? : Partial < {
dislikes : string ;
featured : string ;
likes : string ;
followers : string ;
following : string ;
inbox : string ;
outbox : string ;
} > ;
} ;
2024-03-11 03:04:14 +01:00
2024-04-11 13:39:07 +02:00
export type UserWithRelations = User & {
instance : InferSelectModel < typeof instance > | null ;
emojis : EmojiWithInstance [ ] ;
followerCount : number ;
followingCount : number ;
statusCount : number ;
} ;
2023-10-23 07:39:42 +02:00
2024-04-11 13:39:07 +02:00
export type UserWithRelationsAndRelationships = UserWithRelations & {
relationships : InferSelectModel < typeof relationship > [ ] ;
relationshipSubjects : InferSelectModel < typeof relationship > [ ] ;
} ;
export const userRelations : {
instance : true ;
emojis : {
with : {
emoji : {
with : {
instance : true ;
} ;
} ;
} ;
} ;
} = {
instance : true ,
emojis : {
with : {
emoji : {
with : {
instance : true ,
} ,
} ,
} ,
} ,
} ;
export const userExtras = {
followerCount :
sql ` (SELECT COUNT(*) FROM "Relationship" "relationships" WHERE ("relationships"."ownerId" = "user".id AND "relationships"."following" = true)) ` . as (
"follower_count" ,
) ,
followingCount :
sql ` (SELECT COUNT(*) FROM "Relationship" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = "user".id AND "relationshipSubjects"."following" = true)) ` . as (
"following_count" ,
) ,
statusCount :
sql ` (SELECT COUNT(*) FROM "Status" "statuses" WHERE "statuses"."authorId" = "user".id) ` . as (
"status_count" ,
) ,
} ;
2023-11-04 04:34:31 +01:00
2024-04-11 13:39:07 +02:00
export const userExtrasTemplate = ( name : string ) = > ( {
// @ts-ignore
followerCount : sql ( [
` (SELECT COUNT(*) FROM "Relationship" "relationships" WHERE ("relationships"."ownerId" = " ${ name } ".id AND "relationships"."following" = true)) ` ,
] ) . as ( "follower_count" ) ,
// @ts-ignore
followingCount : sql ( [
` (SELECT COUNT(*) FROM "Relationship" "relationshipSubjects" WHERE ("relationshipSubjects"."subjectId" = " ${ name } ".id AND "relationshipSubjects"."following" = true)) ` ,
] ) . as ( "following_count" ) ,
// @ts-ignore
statusCount : sql ( [
` (SELECT COUNT(*) FROM "Status" "statuses" WHERE "statuses"."authorId" = " ${ name } ".id) ` ,
] ) . as ( "status_count" ) ,
2023-11-27 01:56:16 +01:00
} ) ;
2024-04-11 13:39:07 +02:00
/ * c o n s t a = a w a i t d b . q u e r y . u s e r . f i n d F i r s t ( {
with : {
instance : true ,
emojis : {
with : {
instance : true ,
} ,
} ,
} ,
extras : {
//
followerCount : sql ` SELECT COUNT(*) FROM relationship WHERE owner_id = user.id AND following = true ` ,
} ,
} ) ; * /
export interface AuthData {
user : UserWithRelations | null ;
token : string ;
}
2023-10-08 22:20:42 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Get the user ' s avatar in raw URL format
* @param config The config to use
* @returns The raw URL for the user ' s avatar
* /
2024-04-07 06:16:54 +02:00
export const getAvatarUrl = ( user : User , config : Config ) = > {
2024-04-08 05:55:12 +02:00
if ( ! user . avatar )
return (
config . defaults . avatar ||
` https://api.dicebear.com/8.x/ ${ config . defaults . placeholder_style } /svg?seed= ${ user . username } `
) ;
2024-04-08 06:07:11 +02:00
return user . avatar ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-18 22:29:56 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Get the user ' s header in raw URL format
* @param config The config to use
* @returns The raw URL for the user ' s header
* /
2024-04-07 06:16:54 +02:00
export const getHeaderUrl = ( user : User , config : Config ) = > {
2024-04-07 07:30:49 +02:00
if ( ! user . header ) return config . defaults . header ;
2024-04-08 06:07:11 +02:00
return user . header ;
2023-11-11 03:36:06 +01:00
} ;
export const getFromRequest = async ( req : Request ) : Promise < AuthData > = > {
2024-04-07 07:30:49 +02:00
// Check auth token
const token = req . headers . get ( "Authorization" ) ? . split ( " " ) [ 1 ] || "" ;
2023-11-11 03:36:06 +01:00
2024-04-07 07:30:49 +02:00
return { user : await retrieveUserFromToken ( token ) , token } ;
2023-11-11 03:36:06 +01:00
} ;
2024-04-10 07:51:00 +02:00
export const followRequestUser = async (
2024-04-09 04:12:54 +02:00
follower : User ,
followee : User ,
2024-04-09 04:26:48 +02:00
relationshipId : string ,
2024-04-09 04:12:54 +02:00
reblogs = false ,
notify = false ,
languages : string [ ] = [ ] ,
2024-04-11 13:39:07 +02:00
) : Promise < InferSelectModel < typeof relationship > > = > {
2024-04-10 07:51:00 +02:00
const isRemote = follower . instanceId !== followee . instanceId ;
2024-04-11 13:39:07 +02:00
const updatedRelationship = (
await db
. update ( relationship )
. set ( {
following : isRemote ? false : ! followee . isLocked ,
requested : isRemote ? true : followee . isLocked ,
showingReblogs : reblogs ,
notifying : notify ,
languages : languages ,
} )
. where ( eq ( relationship . id , relationshipId ) )
. returning ( )
) [ 0 ] ;
2024-04-10 09:54:15 +02:00
2024-04-10 07:51:00 +02:00
if ( isRemote ) {
// Federate
// TODO: Make database job
const request = await objectToInboxRequest (
followRequestToLysand ( follower , followee ) ,
follower ,
followee ,
) ;
2024-04-09 04:12:54 +02:00
2024-04-10 07:51:00 +02:00
// Send request
const response = await fetch ( request ) ;
2024-04-09 04:12:54 +02:00
2024-04-10 07:51:00 +02:00
if ( ! response . ok ) {
2024-04-10 08:14:33 +02:00
console . error ( await response . text ( ) ) ;
2024-04-10 09:54:15 +02:00
console . error (
2024-04-10 07:51:00 +02:00
` Failed to federate follow request from ${ follower . id } to ${ followee . uri } ` ,
) ;
2024-04-10 09:54:15 +02:00
2024-04-11 13:39:07 +02:00
return (
await db
. update ( relationship )
. set ( {
following : false ,
requested : false ,
} )
. where ( eq ( relationship . id , relationshipId ) )
. returning ( )
) [ 0 ] ;
2024-04-10 07:51:00 +02:00
}
2024-04-09 04:12:54 +02:00
} else {
2024-04-11 13:39:07 +02:00
await db . insert ( notification ) . values ( {
accountId : followee.id ,
type : followee . isLocked ? "follow_request" : "follow" ,
notifiedId : follower.id ,
} ) ;
2024-04-09 04:12:54 +02:00
}
2024-04-11 13:39:07 +02:00
return updatedRelationship ;
2024-04-09 04:12:54 +02:00
} ;
2024-04-10 10:07:03 +02:00
export const sendFollowAccept = async ( follower : User , followee : User ) = > {
// TODO: Make database job
const request = await objectToInboxRequest (
followAcceptToLysand ( follower , followee ) ,
followee ,
follower ,
) ;
// Send request
const response = await fetch ( request ) ;
if ( ! response . ok ) {
console . error ( await response . text ( ) ) ;
throw new Error (
` Failed to federate follow accept from ${ followee . id } to ${ follower . uri } ` ,
) ;
}
} ;
export const sendFollowReject = async ( follower : User , followee : User ) = > {
// TODO: Make database job
const request = await objectToInboxRequest (
followRejectToLysand ( follower , followee ) ,
followee ,
follower ,
) ;
// Send request
const response = await fetch ( request ) ;
if ( ! response . ok ) {
console . error ( await response . text ( ) ) ;
throw new Error (
` Failed to federate follow reject from ${ followee . id } to ${ follower . uri } ` ,
) ;
}
} ;
2024-04-11 13:39:07 +02:00
export const transformOutputToUserWithRelations = (
user : Omit < User , " endpoints " > & {
followerCount : unknown ;
followingCount : unknown ;
statusCount : unknown ;
emojis : {
a : string ;
b : string ;
emoji? : EmojiWithInstance ;
} [ ] ;
instance : InferSelectModel < typeof instance > | null ;
endpoints : unknown ;
} ,
) : UserWithRelations = > {
return {
. . . user ,
followerCount : Number ( user . followerCount ) ,
followingCount : Number ( user . followingCount ) ,
statusCount : Number ( user . statusCount ) ,
endpoints :
user . endpoints ? ?
( { } as Partial < {
dislikes : string ;
featured : string ;
likes : string ;
followers : string ;
following : string ;
inbox : string ;
outbox : string ;
} > ) ,
emojis : user.emojis.map (
( emoji ) = >
( emoji as unknown as Record < string , object > )
. emoji as EmojiWithInstance ,
) ,
} ;
} ;
export const findManyUsers = async (
query : Parameters < typeof db.query.user.findMany > [ 0 ] ,
) : Promise < UserWithRelations [ ] > = > {
const output = await db . query . user . findMany ( {
. . . query ,
with : {
. . . userRelations ,
. . . query ? . with ,
} ,
extras : {
. . . userExtras ,
. . . query ? . extras ,
} ,
} ) ;
return output . map ( ( user ) = > transformOutputToUserWithRelations ( user ) ) ;
} ;
export const findFirstUser = async (
query : Parameters < typeof db.query.user.findFirst > [ 0 ] ,
) : Promise < UserWithRelations | null > = > {
const output = await db . query . user . findFirst ( {
. . . query ,
with : {
. . . userRelations ,
. . . query ? . with ,
2024-04-07 07:30:49 +02:00
} ,
2024-04-11 13:39:07 +02:00
extras : {
. . . userExtras ,
. . . query ? . extras ,
} ,
} ) ;
if ( ! output ) return null ;
return transformOutputToUserWithRelations ( output ) ;
} ;
export const resolveUser = async (
uri : string ,
) : Promise < UserWithRelations | null > = > {
// Check if user not already in database
const foundUser = await findFirstUser ( {
where : ( user , { eq } ) = > eq ( user . uri , uri ) ,
2024-04-07 07:30:49 +02:00
} ) ;
if ( foundUser ) return foundUser ;
2024-04-10 10:37:58 +02:00
// Check if URI is of a local user
if ( uri . startsWith ( config . http . base_url ) ) {
const uuid = uri . match (
/[0-9A-F]{8}-[0-9A-F]{4}-[7][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i ,
) ;
if ( ! uuid ) {
throw new Error (
` URI ${ uri } is of a local user, but it could not be parsed ` ,
) ;
}
2024-04-11 13:39:07 +02:00
const foundLocalUser = await findFirstUser ( {
where : ( user , { eq } ) = > eq ( user . id , uuid [ 0 ] ) ,
with : userRelations ,
2024-04-10 10:37:58 +02:00
} ) ;
2024-04-11 13:39:07 +02:00
return foundLocalUser || null ;
2024-04-10 10:37:58 +02:00
}
2024-04-10 08:57:29 +02:00
if ( ! URL . canParse ( uri ) ) {
throw new Error ( ` Invalid URI to parse ${ uri } ` ) ;
}
2024-04-07 07:30:49 +02:00
const response = await fetch ( uri , {
method : "GET" ,
headers : {
Accept : "application/json" ,
} ,
} ) ;
2024-04-10 01:54:10 +02:00
const data = ( await response . json ( ) ) as Partial < Lysand.User > ;
2024-04-07 07:30:49 +02:00
if (
! (
data . id &&
data . username &&
data . uri &&
data . created_at &&
2024-04-10 01:54:10 +02:00
data . dislikes &&
2024-04-07 07:30:49 +02:00
data . featured &&
2024-04-10 01:54:10 +02:00
data . likes &&
2024-04-07 07:30:49 +02:00
data . followers &&
data . following &&
data . inbox &&
data . outbox &&
data . public_key
)
) {
throw new Error ( "Invalid user data" ) ;
}
// Parse emojis and add them to database
const userEmojis =
data . extensions ? . [ "org.lysand:custom_emojis" ] ? . emojis ? ? [ ] ;
2024-04-10 07:13:13 +02:00
const instance = await addInstanceIfNotExists ( data . uri ) ;
const emojis = [ ] ;
for ( const emoji of userEmojis ) {
2024-04-11 03:40:43 +02:00
emojis . push ( await fetchEmoji ( emoji ) ) ;
2024-04-10 07:13:13 +02:00
}
2024-04-11 13:39:07 +02:00
const newUser = (
await db
. insert ( user )
. values ( {
username : data.username ,
uri : data.uri ,
createdAt : new Date ( data . created_at ) . toISOString ( ) ,
endpoints : {
dislikes : data.dislikes ,
featured : data.featured ,
likes : data.likes ,
followers : data.followers ,
following : data.following ,
inbox : data.inbox ,
outbox : data.outbox ,
} ,
instanceId : instance.id ,
avatar : data.avatar
? Object . entries ( data . avatar ) [ 0 ] [ 1 ] . content
: "" ,
header : data.header
? Object . entries ( data . header ) [ 0 ] [ 1 ] . content
: "" ,
displayName : data.display_name ? ? "" ,
note : getBestContentType ( data . bio ) . content ,
publicKey : data.public_key.public_key ,
source : {
language : null ,
note : "" ,
privacy : "public" ,
sensitive : false ,
fields : [ ] ,
} ,
} )
. returning ( )
) [ 0 ] ;
// Add emojis to user
await db . insert ( emojiToUser ) . values (
emojis . map ( ( emoji ) = > ( {
a : emoji.id ,
b : newUser.id ,
} ) ) ,
) ;
const finalUser = await findFirstUser ( {
where : ( user , { eq } ) = > eq ( user . id , newUser . id ) ,
with : userRelations ,
2024-04-07 07:30:49 +02:00
} ) ;
2024-04-11 13:39:07 +02:00
if ( ! finalUser ) return null ;
2024-04-07 07:30:49 +02:00
// Add to Meilisearch
2024-04-11 13:39:07 +02:00
await addUserToMeilisearch ( finalUser ) ;
2024-04-07 07:30:49 +02:00
2024-04-11 13:39:07 +02:00
return finalUser ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 05:18:05 +02:00
2024-04-10 09:13:45 +02:00
export const getUserUri = ( user : User ) = > {
return (
user . uri ||
new URL ( ` /users/ ${ user . id } ` , config . http . base_url ) . toString ( )
) ;
} ;
2024-04-10 06:22:57 +02:00
/ * *
* Resolves a WebFinger identifier to a user .
* @param identifier Either a UUID or a username
* /
2024-04-11 13:39:07 +02:00
export const resolveWebFinger = async (
identifier : string ,
host : string ,
) : Promise < UserWithRelations | null > = > {
2024-04-10 06:22:57 +02:00
// Check if user not already in database
2024-04-11 13:39:07 +02:00
const foundUser = await db
. select ( )
. from ( user )
. innerJoin ( instance , eq ( user . instanceId , instance . id ) )
. where ( and ( eq ( user . username , identifier ) , eq ( instance . baseUrl , host ) ) )
. limit ( 1 ) ;
if ( foundUser [ 0 ] )
return (
( await findFirstUser ( {
where : ( user , { eq } ) = > eq ( user . id , foundUser [ 0 ] . User . id ) ,
with : userRelations ,
} ) ) || null
) ;
2024-04-10 06:22:57 +02:00
const hostWithProtocol = host . startsWith ( "http" ) ? host : ` https:// ${ host } ` ;
const response = await fetch (
new URL (
` /.well-known/webfinger? ${ new URLSearchParams ( {
resource : ` acct: ${ identifier } @ ${ host } ` ,
} ) } ` ,
hostWithProtocol ,
) ,
{
method : "GET" ,
headers : {
Accept : "application/json" ,
} ,
} ,
) ;
2024-04-10 08:27:16 +02:00
if ( response . status === 404 ) {
return null ;
}
2024-04-10 06:22:57 +02:00
const data = ( await response . json ( ) ) as {
subject : string ;
links : {
rel : string ;
type : string ;
href : string ;
} [ ] ;
} ;
if ( ! data . subject || ! data . links ) {
throw new Error (
"Invalid WebFinger data (missing subject or links from response)" ,
) ;
}
const relevantLink = data . links . find ( ( link ) = > link . rel === "self" ) ;
if ( ! relevantLink ) {
throw new Error (
"Invalid WebFinger data (missing link with rel: 'self')" ,
) ;
}
return resolveUser ( relevantLink . href ) ;
} ;
2023-11-11 03:36:06 +01:00
/ * *
* Fetches the list of followers associated with the actor and updates the user ' s followers
* /
export const fetchFollowers = ( ) = > {
2024-04-07 07:30:49 +02:00
//
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 05:18:05 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Creates a new LOCAL user .
* @param data The data for the new user .
* @returns The newly created user .
* /
export const createNewLocalUser = async ( data : {
2024-04-07 07:30:49 +02:00
username : string ;
display_name? : string ;
password : string ;
email : string ;
bio? : string ;
avatar? : string ;
header? : string ;
admin? : boolean ;
2024-04-11 15:52:44 +02:00
skipPasswordHash? : boolean ;
2024-04-11 13:39:07 +02:00
} ) : Promise < UserWithRelations | null > = > {
2024-04-07 07:30:49 +02:00
const keys = await generateUserKeys ( ) ;
2024-04-11 13:39:07 +02:00
const newUser = (
await db
. insert ( user )
. values ( {
username : data.username ,
displayName : data.display_name ? ? data . username ,
2024-04-11 15:52:44 +02:00
password : data.skipPasswordHash
? data . password
: await Bun . password . hash ( data . password ) ,
2024-04-11 13:39:07 +02:00
email : data.email ,
note : data.bio ? ? "" ,
avatar : data.avatar ? ? config . defaults . avatar ,
header : data.header ? ? config . defaults . avatar ,
isAdmin : data.admin ? ? false ,
publicKey : keys.public_key ,
privateKey : keys.private_key ,
updatedAt : new Date ( ) . toISOString ( ) ,
source : {
language : null ,
note : "" ,
privacy : "public" ,
sensitive : false ,
fields : [ ] ,
} ,
} )
. returning ( )
) [ 0 ] ;
const finalUser = await findFirstUser ( {
where : ( user , { eq } ) = > eq ( user . id , newUser . id ) ,
with : userRelations ,
2024-04-07 07:30:49 +02:00
} ) ;
2024-04-11 13:39:07 +02:00
if ( ! finalUser ) return null ;
2024-04-07 07:30:49 +02:00
// Add to Meilisearch
2024-04-11 13:39:07 +02:00
await addUserToMeilisearch ( finalUser ) ;
2024-04-07 07:30:49 +02:00
2024-04-11 13:39:07 +02:00
return finalUser ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 03:09:14 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Parses mentions from a list of URIs
* /
2024-04-11 13:39:07 +02:00
export const parseMentionsUris = async (
mentions : string [ ] ,
) : Promise < UserWithRelations [ ] > = > {
return await findManyUsers ( {
where : ( user , { inArray } ) = > inArray ( user . uri , mentions ) ,
with : userRelations ,
2024-04-07 07:30:49 +02:00
} ) ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 03:09:14 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Retrieves a user from a token .
* @param access_token The access token to retrieve the user from .
* @returns The user associated with the given access token .
* /
2024-04-11 13:39:07 +02:00
export const retrieveUserFromToken = async (
access_token : string ,
) : Promise < UserWithRelations | null > = > {
2024-04-07 07:30:49 +02:00
if ( ! access_token ) return null ;
2024-04-11 13:39:07 +02:00
const token = await db . query . token . findFirst ( {
where : ( tokens , { eq } ) = > eq ( tokens . accessToken , access_token ) ,
2024-04-07 07:30:49 +02:00
} ) ;
2024-04-11 13:39:07 +02:00
if ( ! token || ! token . userId ) return null ;
const user = await findFirstUser ( {
where : ( user , { eq } ) = > eq ( user . id , token . userId ? ? "" ) ,
} ) ;
2024-04-07 07:30:49 +02:00
2024-04-11 13:39:07 +02:00
return user ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 05:18:05 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Gets the relationship to another user .
* @param other The other user to get the relationship to .
* @returns The relationship to the other user .
* /
export const getRelationshipToOtherUser = async (
2024-04-07 07:30:49 +02:00
user : UserWithRelations ,
other : User ,
2024-04-11 13:39:07 +02:00
) : Promise < InferSelectModel < typeof relationship > > = > {
const foundRelationship = await db . query . relationship . findFirst ( {
where : ( relationship , { and , eq } ) = >
and (
eq ( relationship . ownerId , user . id ) ,
eq ( relationship . subjectId , other . id ) ,
) ,
2024-04-07 07:30:49 +02:00
} ) ;
2024-04-09 04:12:54 +02:00
2024-04-11 13:39:07 +02:00
if ( ! foundRelationship ) {
2024-04-09 04:12:54 +02:00
// Create new relationship
const newRelationship = await createNewRelationship ( user , other ) ;
return newRelationship ;
}
2024-04-11 13:39:07 +02:00
return foundRelationship ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-22 03:09:14 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Generates keys for the user .
* /
export const generateUserKeys = async ( ) = > {
2024-04-07 07:30:49 +02:00
const keys = await crypto . subtle . generateKey ( "Ed25519" , true , [
"sign" ,
"verify" ,
] ) ;
const privateKey = btoa (
String . fromCharCode . apply ( null , [
. . . new Uint8Array (
// jesus help me what do these letters mean
await crypto . subtle . exportKey ( "pkcs8" , keys . privateKey ) ,
) ,
] ) ,
) ;
const publicKey = btoa (
String . fromCharCode (
. . . new Uint8Array (
// why is exporting a key so hard
await crypto . subtle . exportKey ( "spki" , keys . publicKey ) ,
) ,
) ,
) ;
// Add header, footer and newlines later on
// These keys are base64 encrypted
return {
private_key : privateKey ,
public_key : publicKey ,
} ;
2023-11-11 03:36:06 +01:00
} ;
2023-11-20 03:42:40 +01:00
export const userToAPI = (
2024-04-11 13:39:07 +02:00
userToConvert : UserWithRelations ,
2024-04-07 07:30:49 +02:00
isOwnAccount = false ,
2023-11-20 03:42:40 +01:00
) : APIAccount = > {
2024-04-07 07:30:49 +02:00
return {
2024-04-11 13:39:07 +02:00
id : userToConvert.id ,
username : userToConvert.username ,
display_name : userToConvert.displayName ,
note : userToConvert.note ,
2024-04-09 06:33:59 +02:00
url :
2024-04-11 13:39:07 +02:00
userToConvert . uri ||
new URL (
` /@ ${ userToConvert . username } ` ,
config . http . base_url ,
) . toString ( ) ,
avatar : getAvatarUrl ( userToConvert , config ) ,
header : getHeaderUrl ( userToConvert , config ) ,
locked : userToConvert.isLocked ,
created_at : new Date ( userToConvert . createdAt ) . toISOString ( ) ,
followers_count : userToConvert.followerCount ,
following_count : userToConvert.followingCount ,
statuses_count : userToConvert.statusCount ,
emojis : userToConvert.emojis.map ( ( emoji ) = > emojiToAPI ( emoji ) ) ,
2024-04-07 07:30:49 +02:00
// TODO: Add fields
fields : [ ] ,
2024-04-11 13:39:07 +02:00
bot : userToConvert.isBot ,
2024-04-07 07:30:49 +02:00
source :
2024-04-11 13:39:07 +02:00
isOwnAccount && userToConvert . source
? ( userToConvert . source as APISource )
2024-04-07 07:30:49 +02:00
: undefined ,
// TODO: Add static avatar and header
avatar_static : "" ,
header_static : "" ,
acct :
2024-04-11 13:39:07 +02:00
userToConvert . instance === null
? userToConvert . username
: ` ${ userToConvert . username } @ ${ userToConvert . instance . baseUrl } ` ,
2024-04-07 07:30:49 +02:00
// TODO: Add these fields
limited : false ,
moved : null ,
noindex : false ,
suspended : false ,
discoverable : undefined ,
mute_expires_at : undefined ,
group : false ,
pleroma : {
2024-04-11 13:39:07 +02:00
is_admin : userToConvert.isAdmin ,
is_moderator : userToConvert.isAdmin ,
2024-04-07 07:30:49 +02:00
} ,
} ;
2023-11-11 03:36:06 +01:00
} ;
2023-09-18 07:38:08 +02:00
2023-11-11 03:36:06 +01:00
/ * *
* Should only return local users
* /
2024-04-10 01:54:10 +02:00
export const userToLysand = ( user : UserWithRelations ) : Lysand . User = > {
2024-04-07 07:30:49 +02:00
if ( user . instanceId !== null ) {
throw new Error ( "Cannot convert remote user to Lysand format" ) ;
}
return {
id : user.id ,
type : "User" ,
2024-04-10 09:13:45 +02:00
uri : getUserUri ( user ) ,
2024-04-10 01:54:10 +02:00
bio : {
"text/html" : {
2024-04-07 07:30:49 +02:00
content : user.note ,
} ,
2024-04-10 01:54:10 +02:00
"text/plain" : {
2024-04-07 07:30:49 +02:00
content : htmlToText ( user . note ) ,
} ,
2024-04-10 01:54:10 +02:00
} ,
2024-04-07 07:30:49 +02:00
created_at : new Date ( user . createdAt ) . toISOString ( ) ,
2024-04-10 01:54:10 +02:00
dislikes : new URL (
` /users/ ${ user . id } /dislikes ` ,
2024-04-08 05:28:18 +02:00
config . http . base_url ,
) . toString ( ) ,
featured : new URL (
` /users/ ${ user . id } /featured ` ,
config . http . base_url ,
) . toString ( ) ,
2024-04-10 01:54:10 +02:00
likes : new URL (
` /users/ ${ user . id } /likes ` ,
2024-04-08 05:28:18 +02:00
config . http . base_url ,
) . toString ( ) ,
followers : new URL (
` /users/ ${ user . id } /followers ` ,
config . http . base_url ,
) . toString ( ) ,
following : new URL (
` /users/ ${ user . id } /following ` ,
config . http . base_url ,
) . toString ( ) ,
inbox : new URL (
` /users/ ${ user . id } /inbox ` ,
config . http . base_url ,
) . toString ( ) ,
outbox : new URL (
` /users/ ${ user . id } /outbox ` ,
config . http . base_url ,
) . toString ( ) ,
2024-04-07 07:30:49 +02:00
indexable : false ,
username : user.username ,
2024-04-10 04:05:02 +02:00
avatar : urlToContentFormat ( getAvatarUrl ( user , config ) ) ? ? undefined ,
header : urlToContentFormat ( getHeaderUrl ( user , config ) ) ? ? undefined ,
2024-04-07 07:30:49 +02:00
display_name : user.displayName ,
fields : ( user . source as APISource ) . fields . map ( ( field ) = > ( {
2024-04-10 01:54:10 +02:00
key : {
"text/html" : {
2024-04-07 07:30:49 +02:00
content : field.name ,
} ,
2024-04-10 01:54:10 +02:00
"text/plain" : {
2024-04-07 07:30:49 +02:00
content : htmlToText ( field . name ) ,
} ,
2024-04-10 01:54:10 +02:00
} ,
value : {
"text/html" : {
2024-04-07 07:30:49 +02:00
content : field.value ,
} ,
2024-04-10 01:54:10 +02:00
"text/plain" : {
2024-04-07 07:30:49 +02:00
content : htmlToText ( field . value ) ,
} ,
2024-04-10 01:54:10 +02:00
} ,
2024-04-07 07:30:49 +02:00
} ) ) ,
public_key : {
2024-04-08 05:28:18 +02:00
actor : new URL (
` /users/ ${ user . id } ` ,
config . http . base_url ,
) . toString ( ) ,
2024-04-07 07:30:49 +02:00
public_key : user.publicKey ,
} ,
extensions : {
"org.lysand:custom_emojis" : {
emojis : user.emojis.map ( ( emoji ) = > emojiToLysand ( emoji ) ) ,
} ,
} ,
} ;
2023-11-11 03:36:06 +01:00
} ;
2024-04-10 07:51:00 +02:00
export const followRequestToLysand = (
follower : User ,
followee : User ,
) : Lysand . Follow = > {
if ( follower . instanceId ) {
throw new Error ( "Follower must be a local user" ) ;
}
if ( ! followee . instanceId ) {
throw new Error ( "Followee must be a remote user" ) ;
}
if ( ! followee . uri ) {
throw new Error ( "Followee must have a URI in database" ) ;
}
const id = crypto . randomUUID ( ) ;
return {
type : "Follow" ,
id : id ,
2024-04-10 09:13:45 +02:00
author : getUserUri ( follower ) ,
2024-04-10 07:51:00 +02:00
followee : followee.uri ,
created_at : new Date ( ) . toISOString ( ) ,
uri : new URL ( ` /follows/ ${ id } ` , config . http . base_url ) . toString ( ) ,
} ;
} ;
export const followAcceptToLysand = (
follower : User ,
followee : User ,
) : Lysand . FollowAccept = > {
2024-04-10 09:18:41 +02:00
if ( ! follower . instanceId ) {
throw new Error ( "Follower must be a remote user" ) ;
2024-04-10 07:51:00 +02:00
}
2024-04-10 09:18:41 +02:00
if ( followee . instanceId ) {
throw new Error ( "Followee must be a local user" ) ;
2024-04-10 07:51:00 +02:00
}
2024-04-10 09:18:41 +02:00
if ( ! follower . uri ) {
throw new Error ( "Follower must have a URI in database" ) ;
2024-04-10 07:51:00 +02:00
}
const id = crypto . randomUUID ( ) ;
return {
type : "FollowAccept" ,
id : id ,
2024-04-10 09:18:41 +02:00
author : getUserUri ( followee ) ,
2024-04-10 07:51:00 +02:00
created_at : new Date ( ) . toISOString ( ) ,
2024-04-10 09:18:41 +02:00
follower : follower.uri ,
2024-04-10 07:51:00 +02:00
uri : new URL ( ` /follows/ ${ id } ` , config . http . base_url ) . toString ( ) ,
} ;
} ;
2024-04-10 10:07:03 +02:00
export const followRejectToLysand = (
follower : User ,
followee : User ,
) : Lysand . FollowReject = > {
return {
. . . followAcceptToLysand ( follower , followee ) ,
type : "FollowReject" ,
} ;
} ;