1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-22 10:22:39 +01:00

send accept when follow is received

This commit is contained in:
Madeline 2023-09-29 04:59:12 +00:00
parent b6df59fd45
commit b993216651
5 changed files with 141 additions and 35 deletions

View File

@ -1,9 +1,33 @@
import { Message, MessageCreateEvent, emitEvent } from "@spacebar/util";
import { APCreate, AnyAPObject, ObjectIsNote } from "activitypub-types";
import {
ActorType,
FederationActivity,
FederationKey,
Invite,
Member,
Message,
MessageCreateEvent,
emitEvent,
} from "@spacebar/util";
import {
APAccept,
APCreate,
APFollow,
AnyAPObject,
ObjectIsNote,
} from "activitypub-types";
import { Request } from "express";
import { HttpSig } from "../HttpSig";
import { federationQueue } from "../queue";
import { transformNoteToMessage } from "../transforms";
import { APError, hasAPContext, resolveAPObject } from "../utils";
import { APFollowWithInvite } from "../types";
import {
ACTIVITYSTREAMS_CONTEXT,
APError,
fetchFederatedUser,
hasAPContext,
resolveAPObject,
splitQualifiedMention,
} from "../utils";
/**
* Key names are derived from the object type names
@ -37,6 +61,27 @@ const handlers = {
message.save(),
]);
},
Follow: async (activity: APFollow) => {
// dummy: send back Accept regardless
if (typeof activity.object != "string")
throw new APError("not implemented");
const mention = splitQualifiedMention(activity.object);
const keys = await FederationKey.findOneOrFail({
where: { domain: mention.domain, actorId: mention.user },
});
switch (keys.type) {
case ActorType.GUILD:
if (typeof activity.actor != "string")
throw new APError("not implemented");
return addRemoteUserToGuild(activity.actor, keys, activity);
default:
throw new APError("not implemented");
}
},
} as Record<string, (activity: AnyAPObject) => Promise<unknown>>;
export const genericInboxHandler = async (req: Request) => {
@ -78,3 +123,34 @@ export const genericInboxHandler = async (req: Request) => {
console.warn(`Activity of type ${type} not implemented`);
throw new APError(`Activity of type ${type} not implemented`);
};
const addRemoteUserToGuild = async (
actor: string,
guild: FederationKey,
follow: APFollow,
) => {
const invite = (follow as APFollowWithInvite).invite;
if (!invite) throw new APError("Requires invite");
await Invite.findOneOrFail({
where: {
guild_id: guild.actorId,
code: splitQualifiedMention(invite).user,
},
});
const { entity, keys } = await fetchFederatedUser(actor);
await Member.addToGuild(entity.id, guild.actorId);
const accept = await FederationActivity.create({
data: {
type: "Accept",
"@context": ACTIVITYSTREAMS_CONTEXT,
actor: guild.federatedId,
object: follow,
} as APAccept,
}).save();
federationQueue.distribute(accept.toJSON());
};

View File

@ -1,8 +1,8 @@
import { Config, FederationKey } from "@spacebar/util";
import { APActivity } from "activitypub-types";
import { Config, Debug, FederationKey } from "@spacebar/util";
import { APActivity, ActivityIsFollow } from "activitypub-types";
import fetch from "node-fetch";
import { HttpSig } from "./HttpSig";
import { APError, splitQualifiedMention } from "./utils";
import { APError, LOG_NAMES, splitQualifiedMention } from "./utils";
//
type Instance = string;
@ -15,6 +15,8 @@ class FederationQueue {
let { actor } = activity;
const { to, object } = activity;
Debug(LOG_NAMES.remote, `distributing activity ${activity.id}`);
if (!actor)
throw new APError("Activity with no actor cannot be signed.");
if (Array.isArray(actor)) actor = actor[0];
@ -43,7 +45,11 @@ class FederationQueue {
if (!recv) continue;
// this is wrong?
if (typeof recv != "string") continue;
if (typeof recv != "string") {
if (ActivityIsFollow(recv)) {
recv = recv.actor!.toString();
} else continue;
}
if (recv == "https://www.w3.org/ns/activitystreams#Public") {
console.debug(`TODO: Skipping sending activity to #Public`);
@ -58,6 +64,7 @@ class FederationQueue {
// TODO: this is bad
if (!recv.includes("/inbox")) recv = `${recv}/inbox`;
Debug(LOG_NAMES.remote, `sending activity to ${recv}`);
await this.signAndSend(activity, sender, recv);
}
}

View File

@ -340,7 +340,7 @@ export const transformOrganisationToGuild = async (org: APOrganization) => {
const guild = Guild.create({
id: keys.actorId,
name: org.name,
owner_id: owner.user.id,
owner_id: owner.entity.id,
});
await Promise.all([guild.save(), keys.save()]);

View File

@ -0,0 +1,5 @@
import { APFollow } from "activitypub-types";
export type APFollowWithInvite = APFollow & {
invite: string;
};

View File

@ -1,11 +1,13 @@
import { DEFAULT_FETCH_OPTIONS } from "@spacebar/api";
import {
ActorType,
BaseClass,
Config,
Debug,
FederationActivity,
FederationCache,
FederationKey,
Guild,
OrmUtils,
Snowflake,
User,
@ -13,7 +15,6 @@ import {
WebfingerResponse,
} from "@spacebar/util";
import {
APFollow,
APObject,
APPerson,
AnyAPObject,
@ -26,6 +27,7 @@ import fetch from "node-fetch";
import { ProxyAgent } from "proxy-agent";
import TurndownService from "turndown";
import { federationQueue } from "./queue";
import { APFollowWithInvite } from "./types";
export const ACTIVITYSTREAMS_CONTEXT = "https://www.w3.org/ns/activitystreams";
export const LOG_NAMES = {
@ -142,7 +144,9 @@ export const tryResolveWebfinger = async (lookup: string) => {
};
/** Fetch from local db, if not found fetch from remote instance and save */
export const fetchFederatedUser = async (actorId: string) => {
export const fetchFederatedUser = async (
actorId: string,
): Promise<{ keys: FederationKey; entity: BaseClass }> => {
// if we were given webfinger, resolve that first
const mention = splitQualifiedMention(actorId);
const cache = await FederationKey.findOne({
@ -151,7 +155,7 @@ export const fetchFederatedUser = async (actorId: string) => {
if (cache) {
return {
keys: cache,
user: await User.findOneOrFail({ where: { id: cache.actorId } }),
entity: await User.findOneOrFail({ where: { id: cache.actorId } }),
};
}
@ -187,33 +191,46 @@ export const fetchFederatedUser = async (actorId: string) => {
outbox: remoteActor.outbox,
});
const user = User.create({
id: keys.actorId,
username: remoteActor.name,
discriminator: "0",
bio: new TurndownService().turndown(remoteActor.summary), // html -> markdown
email: `${remoteActor.preferredUsername}@${keys.domain}`,
data: {
hash: "#",
valid_tokens_since: new Date(),
},
extended_settings: "{}",
settings: UserSettings.create(),
premium: false,
let entity: BaseClass | undefined = undefined;
if (type == ActorType.USER)
entity = User.create({
id: keys.actorId,
username: remoteActor.name,
discriminator: "0",
bio: new TurndownService().turndown(remoteActor.summary), // html -> markdown
email: `${remoteActor.preferredUsername}@${keys.domain}`,
data: {
hash: "#",
valid_tokens_since: new Date(),
},
extended_settings: "{}",
settings: UserSettings.create(),
premium: false,
premium_since: Config.get().defaults.user.premium
? new Date()
: undefined,
rights: Config.get().register.defaultRights,
premium_type: Config.get().defaults.user.premiumType ?? 0,
verified: Config.get().defaults.user.verified ?? true,
created_at: new Date(),
});
premium_since: Config.get().defaults.user.premium
? new Date()
: undefined,
rights: Config.get().register.defaultRights,
premium_type: Config.get().defaults.user.premiumType ?? 0,
verified: Config.get().defaults.user.verified ?? true,
created_at: new Date(),
});
await Promise.all([keys.save(), user.save()]);
if (type == ActorType.GUILD)
entity = Guild.create({
id: keys.actorId,
name: remoteActor.name,
owner_id: (
await fetchFederatedUser(remoteActor.attributedTo!.toString())
).entity.id,
});
if (!entity) throw new APError("not possible :3");
await Promise.all([keys.save(), entity.save()]);
return {
keys,
user,
entity,
};
};
@ -232,7 +249,8 @@ export const tryFederatedGuildJoin = async (code: string, user_id: string) => {
type: "Follow",
actor: `https://${host}/federation/users/${user_id}`,
object: guild.id,
} as APFollow,
invite: code,
} as APFollowWithInvite,
}).save();
await federationQueue.distribute(follow.toJSON());