Begin more work

This commit is contained in:
Jesse Wierzbinski 2023-09-19 14:16:50 -10:00
parent 1a1bee83a7
commit dacbc5a00b
No known key found for this signature in database
GPG key ID: F9A1E418934E40B0
6 changed files with 198 additions and 14 deletions

View file

@ -1,8 +1,9 @@
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { APActor } from "activitypub-types";
import { getConfig } from "@config";
import { APActor, APOrderedCollectionPage, IconField } from "activitypub-types";
import { getConfig, getHost } from "@config";
import { appendFile } from "fs/promises";
import { errorResponse } from "@response";
import { APIAccount } from "~types/entities/account";
/**
* Stores an ActivityPub actor as raw JSON-LD data
@ -17,7 +18,10 @@ export class RawActor extends BaseEntity {
@Column("jsonb")
data!: APActor;
static async getById(id: string) {
@Column("jsonb")
followers!: string[];
static async getByActorId(id: string) {
return await RawActor.createQueryBuilder("actor")
.where("actor.data->>'id' = :id", {
id,
@ -59,6 +63,98 @@ export class RawActor extends BaseEntity {
return errorResponse("Actor already exists", 409);
}
getInstanceDomain() {
return new URL(this.data.id ?? "").host;
}
async fetchFollowers() {
// Fetch follower list using ActivityPub
// Loop to fetch all followers until there are no more pages
let followers: APOrderedCollectionPage = await fetch(
`${this.data.followers?.toString() ?? ""}?page=1`,
{
headers: {
Accept: "application/activity+json",
},
}
);
let followersList = followers.orderedItems ?? [];
while (followers.type === "OrderedCollectionPage" && followers.next) {
// Fetch next page
// eslint-disable-next-line @typescript-eslint/no-base-to-string
followers = await fetch(followers.next.toString(), {
headers: {
Accept: "application/activity+json",
},
}).then(res => res.json());
// Add new followers to list
followersList = {
...followersList,
...(followers.orderedItems ?? []),
};
}
this.followers = followersList as string[];
}
// eslint-disable-next-line @typescript-eslint/require-await
async toAPIAccount(isOwnAccount = false): Promise<APIAccount> {
const config = getConfig();
return {
id: this.id,
username: this.data.preferredUsername ?? "",
display_name: this.data.name ?? this.data.preferredUsername ?? "",
note: this.data.summary ?? "",
url: `${config.http.base_url}:${config.http.port}/@${
this.data.preferredUsername
}@${this.getInstanceDomain()}`,
// @ts-expect-error It actually works
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
avatar: (this.data.icon as IconField).url ?? config.defaults.avatar,
// @ts-expect-error It actually works
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
header: this.data.image?.url ?? config.defaults.header,
locked: false,
created_at: new Date(this.data.published ?? 0).toISOString(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
followers_count: 0,
following_count: 0,
statuses_count: 0,
emojis: [],
fields: [],
bot: false,
source: isOwnAccount
? {
privacy: "public",
sensitive: false,
language: "en",
note: "",
fields: [],
}
: undefined,
avatar_static: "",
header_static: "",
acct:
this.getInstanceDomain() == getHost()
? `${this.data.preferredUsername}`
: `${
this.data.preferredUsername
}@${this.getInstanceDomain()}`,
limited: false,
moved: null,
noindex: false,
suspended: false,
discoverable: undefined,
mute_expires_at: undefined,
group: false,
role: undefined,
};
}
async isObjectFiltered() {
const config = getConfig();
@ -109,6 +205,6 @@ export class RawActor extends BaseEntity {
}
static async exists(id: string) {
return !!(await RawActor.getById(id));
return !!(await RawActor.getByActorId(id));
}
}

View file

@ -1,7 +1,11 @@
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { APObject } from "activitypub-types";
import { APActor, APObject, DateTime } from "activitypub-types";
import { getConfig } from "@config";
import { appendFile } from "fs/promises";
import { APIStatus } from "~types/entities/status";
import { RawActor } from "./RawActor";
import { APIAccount } from "~types/entities/account";
import { User } from "./User";
/**
* Stores an ActivityPub object as raw JSON-LD data
@ -24,6 +28,50 @@ export class RawObject extends BaseEntity {
.getOne();
}
async isPinned() {
}
async toAPI(): Promise<APIStatus> {
return {
account:
(await (
await RawActor.getByActorId(
(this.data.attributedTo as APActor).id ?? ""
)
)?.toAPIAccount()) ?? (null as unknown as APIAccount),
created_at: new Date(this.data.published as DateTime).toISOString(),
id: this.id,
in_reply_to_id: null,
application: null,
card: null,
content: this.data.content as string,
emojis: [],
favourited: false,
favourites_count: 0,
media_attachments: [],
mentions: [],
in_reply_to_account_id: null,
language: null,
muted: false,
pinned: false,
poll: null,
reblog: null,
reblogged: false,
reblogs_count: 0,
replies_count: 0,
sensitive: false,
spoiler_text: "",
tags: [],
uri: this.data.id as string,
visibility: "public",
url: this.data.id as string,
bookmarked: false,
quote: null,
quote_id: undefined,
};
}
async isObjectFiltered() {
const config = getConfig();

View file

@ -13,6 +13,7 @@ import {
import { APIAccount } from "~types/entities/account";
import { RawActor } from "./RawActor";
import { APActor } from "activitypub-types";
import { RawObject } from "./RawObject";
const config = getConfig();
@ -79,11 +80,20 @@ export class User extends BaseEntity {
@JoinTable()
following!: RawActor[];
@ManyToMany(() => RawActor, actor => actor.id)
@JoinTable()
followers!: RawActor[];
@ManyToMany(() => RawObject, object => object.id)
@JoinTable()
pinned_notes!: RawObject[];
static async getByActorId(id: string) {
return await User.createQueryBuilder("user")
// Objects is a many-to-many relationship
.leftJoinAndSelect("user.actor", "actor")
.leftJoinAndSelect("user.following", "following")
.leftJoinAndSelect("user.followers", "followers")
.where("actor.data @> :data", {
data: JSON.stringify({
id,