2023-10-19 21:53:59 +02:00
|
|
|
import { ConfigType, getConfig } from "@config";
|
2023-09-13 02:29:13 +02:00
|
|
|
import {
|
|
|
|
|
BaseEntity,
|
|
|
|
|
Column,
|
|
|
|
|
CreateDateColumn,
|
|
|
|
|
Entity,
|
2023-09-18 22:29:56 +02:00
|
|
|
JoinTable,
|
|
|
|
|
ManyToMany,
|
|
|
|
|
ManyToOne,
|
2023-09-22 05:18:05 +02:00
|
|
|
OneToMany,
|
2023-09-13 02:29:13 +02:00
|
|
|
PrimaryGeneratedColumn,
|
2023-09-27 01:08:05 +02:00
|
|
|
RemoveOptions,
|
2023-09-13 02:29:13 +02:00
|
|
|
UpdateDateColumn,
|
|
|
|
|
} from "typeorm";
|
2023-09-12 22:48:10 +02:00
|
|
|
import { APIAccount } from "~types/entities/account";
|
2023-09-22 03:09:14 +02:00
|
|
|
import { Token } from "./Token";
|
2023-10-23 03:47:04 +02:00
|
|
|
import { Status, statusRelations } from "./Status";
|
2023-09-22 04:15:12 +02:00
|
|
|
import { APISource } from "~types/entities/source";
|
2023-09-22 05:18:05 +02:00
|
|
|
import { Relationship } from "./Relationship";
|
2023-10-08 22:20:42 +02:00
|
|
|
import { Instance } from "./Instance";
|
2023-11-04 04:34:31 +01:00
|
|
|
import { User as LysandUser } from "~types/lysand/Object";
|
|
|
|
|
import { htmlToText } from "html-to-text";
|
|
|
|
|
import { Emoji } from "./Emoji";
|
2023-09-11 05:54:14 +02:00
|
|
|
|
2023-11-05 00:59:55 +01:00
|
|
|
export const userRelations = [
|
|
|
|
|
"relationships",
|
|
|
|
|
"pinned_notes",
|
|
|
|
|
"instance",
|
|
|
|
|
"emojis",
|
|
|
|
|
];
|
2023-10-23 07:39:42 +02:00
|
|
|
|
2023-09-12 22:48:10 +02:00
|
|
|
/**
|
2023-09-28 20:19:21 +02:00
|
|
|
* Represents a user in the database.
|
2023-09-12 22:48:10 +02:00
|
|
|
* Stores local and remote users
|
|
|
|
|
*/
|
2023-09-11 05:31:08 +02:00
|
|
|
@Entity({
|
|
|
|
|
name: "users",
|
|
|
|
|
})
|
2023-09-12 22:48:10 +02:00
|
|
|
export class User extends BaseEntity {
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The unique identifier for the user.
|
|
|
|
|
*/
|
2023-09-11 05:31:08 +02:00
|
|
|
@PrimaryGeneratedColumn("uuid")
|
|
|
|
|
id!: string;
|
|
|
|
|
|
2023-11-04 04:34:31 +01:00
|
|
|
/**
|
|
|
|
|
* The user URI on the global network
|
|
|
|
|
*/
|
|
|
|
|
@Column("varchar")
|
|
|
|
|
uri!: string;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The username for the user.
|
|
|
|
|
*/
|
2023-09-11 05:54:14 +02:00
|
|
|
@Column("varchar", {
|
|
|
|
|
unique: true,
|
|
|
|
|
})
|
2023-09-11 05:31:08 +02:00
|
|
|
username!: string;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The display name for the user.
|
|
|
|
|
*/
|
2023-09-22 05:18:05 +02:00
|
|
|
@Column("varchar")
|
2023-09-13 07:30:45 +02:00
|
|
|
display_name!: string;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The password for the user.
|
|
|
|
|
*/
|
2023-10-08 22:20:42 +02:00
|
|
|
@Column("varchar", {
|
|
|
|
|
nullable: true,
|
|
|
|
|
})
|
|
|
|
|
password!: string | null;
|
2023-09-11 05:31:08 +02:00
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The email address for the user.
|
|
|
|
|
*/
|
2023-09-11 05:54:14 +02:00
|
|
|
@Column("varchar", {
|
|
|
|
|
unique: true,
|
2023-10-08 22:20:42 +02:00
|
|
|
nullable: true,
|
2023-09-11 05:54:14 +02:00
|
|
|
})
|
2023-10-08 22:20:42 +02:00
|
|
|
email!: string | null;
|
2023-09-11 05:54:14 +02:00
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The note for the user.
|
|
|
|
|
*/
|
2023-09-11 05:54:14 +02:00
|
|
|
@Column("varchar", {
|
|
|
|
|
default: "",
|
|
|
|
|
})
|
2023-09-18 22:29:56 +02:00
|
|
|
note!: string;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* Whether the user is an admin or not.
|
|
|
|
|
*/
|
2023-09-18 22:29:56 +02:00
|
|
|
@Column("boolean", {
|
|
|
|
|
default: false,
|
|
|
|
|
})
|
|
|
|
|
is_admin!: boolean;
|
|
|
|
|
|
2023-11-04 04:34:31 +01:00
|
|
|
@Column("jsonb", {
|
|
|
|
|
nullable: true,
|
|
|
|
|
})
|
|
|
|
|
endpoints!: {
|
|
|
|
|
liked: string;
|
|
|
|
|
disliked: string;
|
|
|
|
|
featured: string;
|
|
|
|
|
followers: string;
|
|
|
|
|
following: string;
|
|
|
|
|
inbox: string;
|
|
|
|
|
outbox: string;
|
|
|
|
|
} | null;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The source for the user.
|
|
|
|
|
*/
|
2023-09-22 04:15:12 +02:00
|
|
|
@Column("jsonb")
|
|
|
|
|
source!: APISource;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
2023-10-19 21:53:59 +02:00
|
|
|
* The avatar for the user (filename, as UUID)
|
2023-09-28 20:19:21 +02:00
|
|
|
*/
|
2023-09-18 22:29:56 +02:00
|
|
|
@Column("varchar")
|
|
|
|
|
avatar!: string;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
2023-10-19 21:53:59 +02:00
|
|
|
* The header for the user (filename, as UUID)
|
2023-09-28 20:19:21 +02:00
|
|
|
*/
|
2023-09-18 22:29:56 +02:00
|
|
|
@Column("varchar")
|
|
|
|
|
header!: string;
|
2023-09-11 05:54:14 +02:00
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The date the user was created.
|
|
|
|
|
*/
|
2023-09-11 05:31:08 +02:00
|
|
|
@CreateDateColumn()
|
|
|
|
|
created_at!: Date;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The date the user was last updated.
|
|
|
|
|
*/
|
2023-09-11 05:31:08 +02:00
|
|
|
@UpdateDateColumn()
|
|
|
|
|
updated_at!: Date;
|
2023-09-11 05:54:14 +02:00
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The public key for the user.
|
|
|
|
|
*/
|
2023-09-18 07:38:08 +02:00
|
|
|
@Column("varchar")
|
|
|
|
|
public_key!: string;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The private key for the user.
|
|
|
|
|
*/
|
2023-10-08 22:20:42 +02:00
|
|
|
@Column("varchar", {
|
|
|
|
|
nullable: true,
|
|
|
|
|
})
|
|
|
|
|
private_key!: string | null;
|
2023-09-18 07:38:08 +02:00
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The relationships for the user.
|
|
|
|
|
*/
|
2023-09-22 05:18:05 +02:00
|
|
|
@OneToMany(() => Relationship, relationship => relationship.owner)
|
|
|
|
|
relationships!: Relationship[];
|
|
|
|
|
|
2023-10-08 22:20:42 +02:00
|
|
|
/**
|
|
|
|
|
* User's instance, null if local user
|
|
|
|
|
*/
|
|
|
|
|
@ManyToOne(() => Instance, {
|
|
|
|
|
nullable: true,
|
|
|
|
|
})
|
|
|
|
|
instance!: Instance | null;
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* The pinned notes for the user.
|
|
|
|
|
*/
|
2023-10-23 07:39:42 +02:00
|
|
|
@ManyToMany(() => Status, status => status.id)
|
2023-09-20 02:16:50 +02:00
|
|
|
@JoinTable()
|
2023-10-23 07:39:42 +02:00
|
|
|
pinned_notes!: Status[];
|
2023-09-20 02:16:50 +02:00
|
|
|
|
2023-11-04 04:34:31 +01:00
|
|
|
/**
|
|
|
|
|
* The emojis for the user.
|
|
|
|
|
*/
|
|
|
|
|
@ManyToMany(() => Emoji, emoji => emoji.id)
|
|
|
|
|
@JoinTable()
|
|
|
|
|
emojis!: Emoji[];
|
|
|
|
|
|
2023-10-19 21:53:59 +02: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
|
|
|
|
|
*/
|
|
|
|
|
getAvatarUrl(config: ConfigType) {
|
|
|
|
|
if (config.media.backend === "local") {
|
|
|
|
|
return `${config.http.base_url}/media/${this.avatar}`;
|
|
|
|
|
} else if (config.media.backend === "s3") {
|
|
|
|
|
return `${config.s3.public_url}/${this.avatar}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the user's header in raw URL format
|
|
|
|
|
* @param config The config to use
|
|
|
|
|
* @returns The raw URL for the user's header
|
|
|
|
|
*/
|
|
|
|
|
getHeaderUrl(config: ConfigType) {
|
|
|
|
|
if (config.media.backend === "local") {
|
|
|
|
|
return `${config.http.base_url}/media/${this.header}`;
|
|
|
|
|
} else if (config.media.backend === "s3") {
|
|
|
|
|
return `${config.s3.public_url}/${this.header}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-16 05:51:29 +02:00
|
|
|
static async getFromRequest(req: Request) {
|
|
|
|
|
// Check auth token
|
|
|
|
|
const token = req.headers.get("Authorization")?.split(" ")[1] || "";
|
|
|
|
|
|
|
|
|
|
return { user: await User.retrieveFromToken(token), token };
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-04 04:34:31 +01:00
|
|
|
static async fetchRemoteUser(uri: string) {
|
2023-11-05 00:59:55 +01:00
|
|
|
// Check if user not already in database
|
|
|
|
|
const foundUser = await User.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
uri,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (foundUser) return foundUser;
|
|
|
|
|
|
2023-11-04 04:34:31 +01:00
|
|
|
const response = await fetch(uri, {
|
|
|
|
|
method: "GET",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
Accept: "application/json",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const data = (await response.json()) as Partial<LysandUser>;
|
|
|
|
|
|
|
|
|
|
const user = new User();
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!(
|
|
|
|
|
data.id &&
|
|
|
|
|
data.username &&
|
|
|
|
|
data.uri &&
|
|
|
|
|
data.created_at &&
|
|
|
|
|
data.disliked &&
|
|
|
|
|
data.featured &&
|
|
|
|
|
data.liked &&
|
|
|
|
|
data.followers &&
|
|
|
|
|
data.following &&
|
|
|
|
|
data.inbox &&
|
|
|
|
|
data.outbox &&
|
|
|
|
|
data.public_key
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error("Invalid user data");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user.id = data.id;
|
|
|
|
|
user.username = data.username;
|
|
|
|
|
user.uri = data.uri;
|
|
|
|
|
user.created_at = new Date(data.created_at);
|
|
|
|
|
user.endpoints = {
|
|
|
|
|
disliked: data.disliked,
|
|
|
|
|
featured: data.featured,
|
|
|
|
|
liked: data.liked,
|
|
|
|
|
followers: data.followers,
|
|
|
|
|
following: data.following,
|
|
|
|
|
inbox: data.inbox,
|
|
|
|
|
outbox: data.outbox,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
user.avatar = (data.avatar && data.avatar[0].content) || "";
|
|
|
|
|
user.header = (data.header && data.header[0].content) || "";
|
|
|
|
|
user.display_name = data.display_name ?? "";
|
|
|
|
|
// TODO: Add bio content types
|
|
|
|
|
user.note = data.bio?.[0].content ?? "";
|
|
|
|
|
|
|
|
|
|
// Parse emojis and add them to database
|
|
|
|
|
const emojis =
|
|
|
|
|
data.extensions?.["org.lysand:custom_emojis"]?.emojis ?? [];
|
|
|
|
|
|
|
|
|
|
for (const emoji of emojis) {
|
|
|
|
|
user.emojis.push(await Emoji.addIfNotExists(emoji));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user.public_key = data.public_key.public_key;
|
|
|
|
|
|
|
|
|
|
const uriData = new URL(data.uri);
|
|
|
|
|
|
|
|
|
|
user.instance = await Instance.addIfNotExists(uriData.origin);
|
|
|
|
|
|
|
|
|
|
await user.save();
|
|
|
|
|
return user;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-08 22:20:42 +02:00
|
|
|
/**
|
|
|
|
|
* Fetches the list of followers associated with the actor and updates the user's followers
|
|
|
|
|
*/
|
|
|
|
|
async fetchFollowers() {
|
2023-10-30 21:23:29 +01:00
|
|
|
//
|
2023-10-08 22:20:42 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* Gets a user by actor ID.
|
|
|
|
|
* @param id The actor ID to search for.
|
|
|
|
|
* @returns The user with the given actor ID.
|
|
|
|
|
*/
|
2023-09-18 22:29:56 +02:00
|
|
|
static async getByActorId(id: string) {
|
|
|
|
|
return await User.createQueryBuilder("user")
|
|
|
|
|
// Objects is a many-to-many relationship
|
|
|
|
|
.leftJoinAndSelect("user.actor", "actor")
|
2023-09-22 05:18:05 +02:00
|
|
|
.leftJoinAndSelect("user.relationships", "relationships")
|
2023-09-18 22:29:56 +02:00
|
|
|
.where("actor.data @> :data", {
|
|
|
|
|
data: JSON.stringify({
|
|
|
|
|
id,
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
.getOne();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
2023-10-08 22:20:42 +02:00
|
|
|
* Creates a new LOCAL user.
|
2023-09-28 20:19:21 +02:00
|
|
|
* @param data The data for the new user.
|
|
|
|
|
* @returns The newly created user.
|
|
|
|
|
*/
|
2023-10-08 22:20:42 +02:00
|
|
|
static async createNewLocal(data: {
|
2023-09-18 22:29:56 +02:00
|
|
|
username: string;
|
|
|
|
|
display_name?: string;
|
|
|
|
|
password: string;
|
|
|
|
|
email: string;
|
|
|
|
|
bio?: string;
|
|
|
|
|
avatar?: string;
|
|
|
|
|
header?: string;
|
|
|
|
|
}) {
|
|
|
|
|
const config = getConfig();
|
|
|
|
|
const user = new User();
|
|
|
|
|
|
|
|
|
|
user.username = data.username;
|
|
|
|
|
user.display_name = data.display_name ?? data.username;
|
|
|
|
|
user.password = await Bun.password.hash(data.password);
|
|
|
|
|
user.email = data.email;
|
|
|
|
|
user.note = data.bio ?? "";
|
|
|
|
|
user.avatar = data.avatar ?? config.defaults.avatar;
|
|
|
|
|
user.header = data.header ?? config.defaults.avatar;
|
2023-11-04 04:34:31 +01:00
|
|
|
user.uri = `${config.http.base_url}/users/${user.id}`;
|
2023-11-05 00:59:55 +01:00
|
|
|
user.emojis = [];
|
2023-09-18 22:29:56 +02:00
|
|
|
|
2023-09-22 05:18:05 +02:00
|
|
|
user.relationships = [];
|
2023-10-08 22:20:42 +02:00
|
|
|
user.instance = null;
|
2023-09-22 05:18:05 +02:00
|
|
|
|
2023-09-22 04:15:12 +02:00
|
|
|
user.source = {
|
|
|
|
|
language: null,
|
|
|
|
|
note: "",
|
|
|
|
|
privacy: "public",
|
|
|
|
|
sensitive: false,
|
|
|
|
|
fields: [],
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-23 07:39:42 +02:00
|
|
|
user.pinned_notes = [];
|
|
|
|
|
|
2023-09-18 22:29:56 +02:00
|
|
|
await user.generateKeys();
|
|
|
|
|
await user.save();
|
2023-10-17 00:03:29 +02:00
|
|
|
|
2023-09-18 22:29:56 +02:00
|
|
|
return user;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-05 00:59:55 +01:00
|
|
|
static async parseMentions(mentions: string[]) {
|
|
|
|
|
return await Promise.all(
|
|
|
|
|
mentions.map(async mention => {
|
|
|
|
|
const user = await User.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
uri: mention,
|
|
|
|
|
},
|
|
|
|
|
relations: userRelations,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (user) return user;
|
|
|
|
|
else return await User.fetchRemoteUser(mention);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-28 20:19:21 +02: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.
|
|
|
|
|
*/
|
2023-09-27 00:33:43 +02:00
|
|
|
static async retrieveFromToken(access_token: string) {
|
|
|
|
|
if (!access_token) return null;
|
|
|
|
|
|
|
|
|
|
const token = await Token.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
access_token,
|
|
|
|
|
},
|
2023-10-23 07:39:42 +02:00
|
|
|
relations: userRelations.map(r => `user.${r}`),
|
2023-09-27 00:33:43 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!token) return null;
|
|
|
|
|
|
|
|
|
|
return token.user;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* Gets the relationship to another user.
|
|
|
|
|
* @param other The other user to get the relationship to.
|
|
|
|
|
* @returns The relationship to the other user.
|
|
|
|
|
*/
|
2023-09-22 05:18:05 +02:00
|
|
|
async getRelationshipToOtherUser(other: User) {
|
|
|
|
|
const relationship = await Relationship.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
owner: {
|
|
|
|
|
id: this.id,
|
|
|
|
|
},
|
|
|
|
|
subject: {
|
|
|
|
|
id: other.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
relations: ["owner", "subject"],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return relationship;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* Removes the user.
|
|
|
|
|
* @param options The options for removing the user.
|
|
|
|
|
* @returns The removed user.
|
|
|
|
|
*/
|
2023-09-27 01:08:05 +02:00
|
|
|
async remove(options?: RemoveOptions | undefined) {
|
2023-09-22 03:09:14 +02:00
|
|
|
// Clean up tokens
|
|
|
|
|
const tokens = await Token.findBy({
|
|
|
|
|
user: {
|
|
|
|
|
id: this.id,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2023-09-27 01:08:05 +02:00
|
|
|
const statuses = await Status.find({
|
|
|
|
|
where: {
|
|
|
|
|
account: {
|
|
|
|
|
id: this.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-10-23 03:47:04 +02:00
|
|
|
relations: statusRelations,
|
2023-09-22 03:09:14 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Delete both
|
|
|
|
|
await Promise.all(tokens.map(async token => await token.remove()));
|
|
|
|
|
|
|
|
|
|
await Promise.all(statuses.map(async status => await status.remove()));
|
2023-09-22 05:18:05 +02:00
|
|
|
|
|
|
|
|
// Get relationships
|
|
|
|
|
const relationships = await this.getRelationships();
|
|
|
|
|
// Delete them all
|
|
|
|
|
await Promise.all(
|
|
|
|
|
relationships.map(async relationship => await relationship.remove())
|
|
|
|
|
);
|
2023-09-27 01:08:05 +02:00
|
|
|
|
|
|
|
|
return await super.remove(options);
|
2023-09-22 05:18:05 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* Gets the relationships for the user.
|
|
|
|
|
* @returns The relationships for the user.
|
|
|
|
|
*/
|
2023-09-22 05:18:05 +02:00
|
|
|
async getRelationships() {
|
|
|
|
|
const relationships = await Relationship.find({
|
|
|
|
|
where: {
|
|
|
|
|
owner: {
|
|
|
|
|
id: this.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
relations: ["subject"],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return relationships;
|
2023-09-22 03:09:14 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-28 20:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* Generates keys for the user.
|
|
|
|
|
*/
|
2023-09-18 07:38:08 +02:00
|
|
|
async generateKeys(): Promise<void> {
|
2023-11-05 00:59:55 +01:00
|
|
|
const keys = (await crypto.subtle.generateKey("Ed25519", true, [
|
|
|
|
|
"sign",
|
|
|
|
|
"verify",
|
|
|
|
|
])) as CryptoKeyPair;
|
2023-09-18 07:38:08 +02:00
|
|
|
|
|
|
|
|
const privateKey = btoa(
|
|
|
|
|
String.fromCharCode.apply(null, [
|
|
|
|
|
...new Uint8Array(
|
2023-09-29 01:58:05 +02:00
|
|
|
// jesus help me what do these letters mean
|
2023-11-05 00:59:55 +01:00
|
|
|
await crypto.subtle.exportKey("pkcs8", keys.privateKey)
|
2023-09-18 07:38:08 +02:00
|
|
|
),
|
|
|
|
|
])
|
|
|
|
|
);
|
|
|
|
|
const publicKey = btoa(
|
|
|
|
|
String.fromCharCode(
|
|
|
|
|
...new Uint8Array(
|
2023-09-29 01:58:05 +02:00
|
|
|
// why is exporting a key so hard
|
2023-11-05 00:59:55 +01:00
|
|
|
await crypto.subtle.exportKey("spki", keys.publicKey)
|
2023-09-18 07:38:08 +02:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Add header, footer and newlines later on
|
2023-11-05 00:59:55 +01:00
|
|
|
// These keys are base64 encrypted
|
2023-09-18 07:38:08 +02:00
|
|
|
this.private_key = privateKey;
|
|
|
|
|
this.public_key = publicKey;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-12 22:48:10 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/require-await
|
2023-10-08 22:20:42 +02:00
|
|
|
async toAPI(isOwnAccount = false): Promise<APIAccount> {
|
|
|
|
|
const follower_count = await Relationship.count({
|
|
|
|
|
where: {
|
|
|
|
|
subject: {
|
|
|
|
|
id: this.id,
|
|
|
|
|
},
|
|
|
|
|
following: true,
|
|
|
|
|
},
|
|
|
|
|
relations: ["subject"],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const following_count = await Relationship.count({
|
|
|
|
|
where: {
|
|
|
|
|
owner: {
|
|
|
|
|
id: this.id,
|
|
|
|
|
},
|
|
|
|
|
following: true,
|
|
|
|
|
},
|
|
|
|
|
relations: ["owner"],
|
|
|
|
|
});
|
|
|
|
|
|
2023-10-23 07:39:42 +02:00
|
|
|
const statusCount = await Status.count({
|
|
|
|
|
where: {
|
|
|
|
|
account: {
|
|
|
|
|
id: this.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
relations: ["account"],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const config = getConfig();
|
|
|
|
|
|
2023-10-08 22:20:42 +02:00
|
|
|
return {
|
|
|
|
|
id: this.id,
|
2023-10-23 07:39:42 +02:00
|
|
|
username: this.username,
|
|
|
|
|
display_name: this.display_name,
|
|
|
|
|
note: this.note,
|
|
|
|
|
url: `${config.http.base_url}/users/${this.username}`,
|
|
|
|
|
avatar: this.getAvatarUrl(config) || config.defaults.avatar,
|
|
|
|
|
header: this.getHeaderUrl(config) || config.defaults.header,
|
|
|
|
|
locked: false,
|
|
|
|
|
created_at: new Date(this.created_at).toISOString(),
|
2023-10-08 22:20:42 +02:00
|
|
|
followers_count: follower_count,
|
|
|
|
|
following_count: following_count,
|
2023-10-23 07:39:42 +02:00
|
|
|
statuses_count: statusCount,
|
2023-11-04 04:34:31 +01:00
|
|
|
emojis: await Promise.all(this.emojis.map(emoji => emoji.toAPI())),
|
2023-10-23 07:39:42 +02:00
|
|
|
fields: [],
|
|
|
|
|
bot: false,
|
|
|
|
|
source: isOwnAccount ? this.source : undefined,
|
|
|
|
|
avatar_static: "",
|
|
|
|
|
header_static: "",
|
|
|
|
|
acct:
|
|
|
|
|
this.instance === null
|
|
|
|
|
? `${this.username}`
|
|
|
|
|
: `${this.username}@${this.instance.base_url}`,
|
|
|
|
|
limited: false,
|
|
|
|
|
moved: null,
|
|
|
|
|
noindex: false,
|
|
|
|
|
suspended: false,
|
|
|
|
|
discoverable: undefined,
|
|
|
|
|
mute_expires_at: undefined,
|
|
|
|
|
group: false,
|
|
|
|
|
role: undefined,
|
2023-10-08 22:20:42 +02:00
|
|
|
};
|
2023-09-11 05:54:14 +02:00
|
|
|
}
|
2023-11-04 04:34:31 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Should only return local users
|
|
|
|
|
*/
|
|
|
|
|
toLysand(): LysandUser {
|
|
|
|
|
if (this.instance !== null) {
|
|
|
|
|
throw new Error("Cannot convert remote user to Lysand format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: this.id,
|
|
|
|
|
type: "User",
|
|
|
|
|
uri: this.uri,
|
|
|
|
|
bio: [
|
|
|
|
|
{
|
|
|
|
|
content: this.note,
|
|
|
|
|
content_type: "text/html",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
content: htmlToText(this.note),
|
|
|
|
|
content_type: "text/plain",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
created_at: new Date(this.created_at).toISOString(),
|
|
|
|
|
disliked: `${this.uri}/disliked`,
|
|
|
|
|
featured: `${this.uri}/featured`,
|
|
|
|
|
liked: `${this.uri}/liked`,
|
|
|
|
|
followers: `${this.uri}/followers`,
|
|
|
|
|
following: `${this.uri}/following`,
|
|
|
|
|
inbox: `${this.uri}/inbox`,
|
|
|
|
|
outbox: `${this.uri}/outbox`,
|
|
|
|
|
indexable: false,
|
|
|
|
|
username: this.username,
|
|
|
|
|
avatar: [
|
|
|
|
|
{
|
|
|
|
|
content: this.getAvatarUrl(getConfig()) || "",
|
|
|
|
|
content_type: `image/${this.avatar.split(".")[1]}`,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
header: [
|
|
|
|
|
{
|
|
|
|
|
content: this.getHeaderUrl(getConfig()) || "",
|
|
|
|
|
content_type: `image/${this.header.split(".")[1]}`,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
display_name: this.display_name,
|
|
|
|
|
fields: this.source.fields.map(field => ({
|
|
|
|
|
key: [
|
|
|
|
|
{
|
|
|
|
|
content: field.name,
|
|
|
|
|
content_type: "text/html",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
content: htmlToText(field.name),
|
|
|
|
|
content_type: "text/plain",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
value: [
|
|
|
|
|
{
|
|
|
|
|
content: field.value,
|
|
|
|
|
content_type: "text/html",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
content: htmlToText(field.value),
|
|
|
|
|
content_type: "text/plain",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})),
|
|
|
|
|
public_key: {
|
|
|
|
|
actor: `${getConfig().http.base_url}/users/${this.id}`,
|
|
|
|
|
public_key: this.public_key,
|
|
|
|
|
},
|
|
|
|
|
extensions: {
|
|
|
|
|
"org.lysand:custom_emojis": {
|
|
|
|
|
emojis: this.emojis.map(emoji => emoji.toLysand()),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
2023-09-13 02:29:13 +02:00
|
|
|
}
|