@@ -0,0 +1,29 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import accept from '@/services/following/requests/accept';
|
||||
import { IFollow } from '../../type';
|
||||
import DbResolver from '../../db-resolver';
|
||||
import { relayAccepted } from '@/services/relay';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||
|
||||
const dbResolver = new DbResolver();
|
||||
const follower = await dbResolver.getUserFromApId(activity.actor);
|
||||
|
||||
if (follower == null) {
|
||||
return `skip: follower not found`;
|
||||
}
|
||||
|
||||
if (follower.host != null) {
|
||||
return `skip: follower is not a local user`;
|
||||
}
|
||||
|
||||
// relay
|
||||
const match = activity.id?.match(/follow-relay\/(\w+)/);
|
||||
if (match) {
|
||||
return await relayAccepted(match[1]);
|
||||
}
|
||||
|
||||
await accept(actor, follower);
|
||||
return `ok`;
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import acceptFollow from './follow';
|
||||
import { IAccept, isFollow, getApType } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
|
||||
const uri = activity.id || activity;
|
||||
|
||||
logger.info(`Accept: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
logger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (isFollow(object)) return await acceptFollow(actor, object);
|
||||
|
||||
return `skip: Unknown Accept type: ${getApType(object)}`;
|
||||
};
|
23
packages/backend/src/remote/activitypub/kernel/add/index.ts
Normal file
23
packages/backend/src/remote/activitypub/kernel/add/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { IAdd } from '../../type';
|
||||
import { resolveNote } from '../../models/note';
|
||||
import { addPinned } from '@/services/i/pin';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
||||
if (activity.target == null) {
|
||||
throw new Error('target is null');
|
||||
}
|
||||
|
||||
if (activity.target === actor.featured) {
|
||||
const note = await resolveNote(activity.object);
|
||||
if (note == null) throw new Error('note not found');
|
||||
await addPinned(actor, note.id);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`unknown target: ${activity.target}`);
|
||||
};
|
@@ -0,0 +1,19 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import announceNote from './note';
|
||||
import { IAnnounce, getApId } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
logger.info(`Announce: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
announceNote(resolver, actor, activity, targetUri);
|
||||
};
|
@@ -0,0 +1,67 @@
|
||||
import Resolver from '../../resolver';
|
||||
import post from '@/services/note/create';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { IAnnounce, getApId } from '../../type';
|
||||
import { fetchNote, resolveNote } from '../../models/note';
|
||||
import { apLogger } from '../../logger';
|
||||
import { extractDbHost } from '@/misc/convert-host';
|
||||
import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import { getApLock } from '@/misc/app-lock';
|
||||
import { parseAudience } from '../../audience';
|
||||
import { StatusError } from '@/misc/fetch';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
/**
|
||||
* アナウンスアクティビティを捌きます
|
||||
*/
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
// アナウンサーが凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
return;
|
||||
}
|
||||
|
||||
// アナウンス先をブロックしてたら中断
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(extractDbHost(uri))) return;
|
||||
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
// 既に同じURIを持つものが登録されていないかチェック
|
||||
const exist = await fetchNote(uri);
|
||||
if (exist) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Announce対象をresolve
|
||||
let renote;
|
||||
try {
|
||||
renote = await resolveNote(targetUri);
|
||||
} catch (e) {
|
||||
// 対象が4xxならスキップ
|
||||
if (e instanceof StatusError && e.isClientError) {
|
||||
logger.warn(`Ignored announce target ${targetUri} - ${e.statusCode}`);
|
||||
return;
|
||||
}
|
||||
logger.warn(`Error in announce target ${targetUri} - ${e.statusCode || e}`);
|
||||
throw e;
|
||||
}
|
||||
|
||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||
|
||||
const activityAudience = await parseAudience(actor, activity.to, activity.cc);
|
||||
|
||||
await post(actor, {
|
||||
createdAt: activity.published ? new Date(activity.published) : null,
|
||||
renote,
|
||||
visibility: activityAudience.visibility,
|
||||
visibleUsers: activityAudience.visibleUsers,
|
||||
uri
|
||||
});
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
import { IBlock } from '../../type';
|
||||
import block from '@/services/blocking/create';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import DbResolver from '../../db-resolver';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
|
||||
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
|
||||
|
||||
const dbResolver = new DbResolver();
|
||||
const blockee = await dbResolver.getUserFromApId(activity.object);
|
||||
|
||||
if (blockee == null) {
|
||||
return `skip: blockee not found`;
|
||||
}
|
||||
|
||||
if (blockee.host != null) {
|
||||
return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
|
||||
}
|
||||
|
||||
await block(actor, blockee);
|
||||
return `ok`;
|
||||
};
|
@@ -0,0 +1,43 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import createNote from './note';
|
||||
import { ICreate, getApId, isPost, getApType } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
import { toArray, concat, unique } from '@/prelude/array';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
logger.info(`Create: ${uri}`);
|
||||
|
||||
// copy audiences between activity <=> object.
|
||||
if (typeof activity.object === 'object') {
|
||||
const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
|
||||
const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)]));
|
||||
|
||||
activity.to = to;
|
||||
activity.cc = cc;
|
||||
activity.object.to = to;
|
||||
activity.object.cc = cc;
|
||||
}
|
||||
|
||||
// If there is no attributedTo, use Activity actor.
|
||||
if (typeof activity.object === 'object' && !activity.object.attributedTo) {
|
||||
activity.object.attributedTo = activity.actor;
|
||||
}
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
logger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (isPost(object)) {
|
||||
createNote(resolver, actor, object, false, activity);
|
||||
} else {
|
||||
logger.warn(`Unknown type: ${getApType(object)}`);
|
||||
}
|
||||
};
|
@@ -0,0 +1,44 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { createNote, fetchNote } from '../../models/note';
|
||||
import { getApId, IObject, ICreate } from '../../type';
|
||||
import { getApLock } from '@/misc/app-lock';
|
||||
import { extractDbHost } from '@/misc/convert-host';
|
||||
import { StatusError } from '@/misc/fetch';
|
||||
|
||||
/**
|
||||
* 投稿作成アクティビティを捌きます
|
||||
*/
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
|
||||
const uri = getApId(note);
|
||||
|
||||
if (typeof note === 'object') {
|
||||
if (actor.uri !== note.attributedTo) {
|
||||
return `skip: actor.uri !== note.attributedTo`;
|
||||
}
|
||||
|
||||
if (typeof note.id === 'string') {
|
||||
if (extractDbHost(actor.uri) !== extractDbHost(note.id)) {
|
||||
return `skip: host in actor.uri !== note.id`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
const exist = await fetchNote(note);
|
||||
if (exist) return 'skip: note exists';
|
||||
|
||||
await createNote(note, resolver, silent);
|
||||
return 'ok';
|
||||
} catch (e) {
|
||||
if (e instanceof StatusError && e.isClientError) {
|
||||
return `skip ${e.statusCode}`;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
import { apLogger } from '../../logger';
|
||||
import { createDeleteAccountJob } from '@/queue';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { Users } from '@/models/index';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export async function deleteActor(actor: IRemoteUser, uri: string): Promise<string> {
|
||||
logger.info(`Deleting the Actor: ${uri}`);
|
||||
|
||||
if (actor.uri !== uri) {
|
||||
return `skip: delete actor ${actor.uri} !== ${uri}`;
|
||||
}
|
||||
|
||||
if (actor.isDeleted) {
|
||||
logger.info(`skip: already deleted`);
|
||||
}
|
||||
|
||||
const job = await createDeleteAccountJob(actor);
|
||||
|
||||
await Users.update(actor.id, {
|
||||
isDeleted: true,
|
||||
});
|
||||
|
||||
return `ok: queued ${job.name} ${job.id}`;
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
import deleteNote from './note';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type';
|
||||
import { toSingle } from '@/prelude/array';
|
||||
import { deleteActor } from './actor';
|
||||
|
||||
/**
|
||||
* 削除アクティビティを捌きます
|
||||
*/
|
||||
export default async (actor: IRemoteUser, activity: IDelete): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
||||
// 削除対象objectのtype
|
||||
let formarType: string | undefined;
|
||||
|
||||
if (typeof activity.object === 'string') {
|
||||
// typeが不明だけど、どうせ消えてるのでremote resolveしない
|
||||
formarType = undefined;
|
||||
} else {
|
||||
const object = activity.object as IObject;
|
||||
if (isTombstone(object)) {
|
||||
formarType = toSingle(object.formerType);
|
||||
} else {
|
||||
formarType = toSingle(object.type);
|
||||
}
|
||||
}
|
||||
|
||||
const uri = getApId(activity.object);
|
||||
|
||||
// type不明でもactorとobjectが同じならばそれはPersonに違いない
|
||||
if (!formarType && actor.uri === uri) {
|
||||
formarType = 'Person';
|
||||
}
|
||||
|
||||
// それでもなかったらおそらくNote
|
||||
if (!formarType) {
|
||||
formarType = 'Note';
|
||||
}
|
||||
|
||||
if (validPost.includes(formarType)) {
|
||||
return await deleteNote(actor, uri);
|
||||
} else if (validActor.includes(formarType)) {
|
||||
return await deleteActor(actor, uri);
|
||||
} else {
|
||||
return `Unknown type ${formarType}`;
|
||||
}
|
||||
};
|
@@ -0,0 +1,41 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import deleteNode from '@/services/note/delete';
|
||||
import { apLogger } from '../../logger';
|
||||
import DbResolver from '../../db-resolver';
|
||||
import { getApLock } from '@/misc/app-lock';
|
||||
import { deleteMessage } from '@/services/messages/delete';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export default async function(actor: IRemoteUser, uri: string): Promise<string> {
|
||||
logger.info(`Deleting the Note: ${uri}`);
|
||||
|
||||
const unlock = await getApLock(uri);
|
||||
|
||||
try {
|
||||
const dbResolver = new DbResolver();
|
||||
const note = await dbResolver.getNoteFromApId(uri);
|
||||
|
||||
if (note == null) {
|
||||
const message = await dbResolver.getMessageFromApId(uri);
|
||||
if (message == null) return 'message not found';
|
||||
|
||||
if (message.userId !== actor.id) {
|
||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
||||
}
|
||||
|
||||
await deleteMessage(message);
|
||||
|
||||
return 'ok: message deleted';
|
||||
}
|
||||
|
||||
if (note.userId !== actor.id) {
|
||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
||||
}
|
||||
|
||||
await deleteNode(actor, note);
|
||||
return 'ok: note deleted';
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
30
packages/backend/src/remote/activitypub/kernel/flag/index.ts
Normal file
30
packages/backend/src/remote/activitypub/kernel/flag/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import config from '@/config/index';
|
||||
import { IFlag, getApIds } from '../../type';
|
||||
import { AbuseUserReports, Users } from '@/models/index';
|
||||
import { In } from 'typeorm';
|
||||
import { genId } from '@/misc/gen-id';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFlag): Promise<string> => {
|
||||
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
|
||||
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
|
||||
const uris = getApIds(activity.object);
|
||||
|
||||
const userIds = uris.filter(uri => uri.startsWith(config.url + '/users/')).map(uri => uri.split('/').pop());
|
||||
const users = await Users.find({
|
||||
id: In(userIds)
|
||||
});
|
||||
if (users.length < 1) return `skip`;
|
||||
|
||||
await AbuseUserReports.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
targetUserId: users[0].id,
|
||||
targetUserHost: users[0].host,
|
||||
reporterId: actor.id,
|
||||
reporterHost: actor.host,
|
||||
comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`
|
||||
});
|
||||
|
||||
return `ok`;
|
||||
};
|
20
packages/backend/src/remote/activitypub/kernel/follow.ts
Normal file
20
packages/backend/src/remote/activitypub/kernel/follow.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import follow from '@/services/following/create';
|
||||
import { IFollow } from '../type';
|
||||
import DbResolver from '../db-resolver';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
const dbResolver = new DbResolver();
|
||||
const followee = await dbResolver.getUserFromApId(activity.object);
|
||||
|
||||
if (followee == null) {
|
||||
return `skip: followee not found`;
|
||||
}
|
||||
|
||||
if (followee.host != null) {
|
||||
return `skip: フォローしようとしているユーザーはローカルユーザーではありません`;
|
||||
}
|
||||
|
||||
await follow(actor, followee, activity.id);
|
||||
return `ok`;
|
||||
};
|
71
packages/backend/src/remote/activitypub/kernel/index.ts
Normal file
71
packages/backend/src/remote/activitypub/kernel/index.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag } from '../type';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import create from './create/index';
|
||||
import performDeleteActivity from './delete/index';
|
||||
import performUpdateActivity from './update/index';
|
||||
import { performReadActivity } from './read';
|
||||
import follow from './follow';
|
||||
import undo from './undo/index';
|
||||
import like from './like';
|
||||
import announce from './announce/index';
|
||||
import accept from './accept/index';
|
||||
import reject from './reject/index';
|
||||
import add from './add/index';
|
||||
import remove from './remove/index';
|
||||
import block from './block/index';
|
||||
import flag from './flag/index';
|
||||
import { apLogger } from '../logger';
|
||||
import Resolver from '../resolver';
|
||||
import { toArray } from '@/prelude/array';
|
||||
|
||||
export async function performActivity(actor: IRemoteUser, activity: IObject) {
|
||||
if (isCollectionOrOrderedCollection(activity)) {
|
||||
const resolver = new Resolver();
|
||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
||||
const act = await resolver.resolve(item);
|
||||
try {
|
||||
await performOneActivity(actor, act);
|
||||
} catch (e) {
|
||||
apLogger.error(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await performOneActivity(actor, activity);
|
||||
}
|
||||
}
|
||||
|
||||
async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> {
|
||||
if (actor.isSuspended) return;
|
||||
|
||||
if (isCreate(activity)) {
|
||||
await create(actor, activity);
|
||||
} else if (isDelete(activity)) {
|
||||
await performDeleteActivity(actor, activity);
|
||||
} else if (isUpdate(activity)) {
|
||||
await performUpdateActivity(actor, activity);
|
||||
} else if (isRead(activity)) {
|
||||
await performReadActivity(actor, activity);
|
||||
} else if (isFollow(activity)) {
|
||||
await follow(actor, activity);
|
||||
} else if (isAccept(activity)) {
|
||||
await accept(actor, activity);
|
||||
} else if (isReject(activity)) {
|
||||
await reject(actor, activity);
|
||||
} else if (isAdd(activity)) {
|
||||
await add(actor, activity).catch(err => apLogger.error(err));
|
||||
} else if (isRemove(activity)) {
|
||||
await remove(actor, activity).catch(err => apLogger.error(err));
|
||||
} else if (isAnnounce(activity)) {
|
||||
await announce(actor, activity);
|
||||
} else if (isLike(activity)) {
|
||||
await like(actor, activity);
|
||||
} else if (isUndo(activity)) {
|
||||
await undo(actor, activity);
|
||||
} else if (isBlock(activity)) {
|
||||
await block(actor, activity);
|
||||
} else if (isFlag(activity)) {
|
||||
await flag(actor, activity);
|
||||
} else {
|
||||
apLogger.warn(`unrecognized activity type: ${(activity as any).type}`);
|
||||
}
|
||||
}
|
21
packages/backend/src/remote/activitypub/kernel/like.ts
Normal file
21
packages/backend/src/remote/activitypub/kernel/like.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { ILike, getApId } from '../type';
|
||||
import create from '@/services/note/reaction/create';
|
||||
import { fetchNote, extractEmojis } from '../models/note';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
const note = await fetchNote(targetUri);
|
||||
if (!note) return `skip: target note not found ${targetUri}`;
|
||||
|
||||
await extractEmojis(activity.tag || [], actor.host).catch(() => null);
|
||||
|
||||
return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => {
|
||||
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') {
|
||||
return 'skip: already reacted';
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}).then(() => 'ok');
|
||||
};
|
27
packages/backend/src/remote/activitypub/kernel/read.ts
Normal file
27
packages/backend/src/remote/activitypub/kernel/read.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { IRead, getApId } from '../type';
|
||||
import { isSelfHost, extractDbHost } from '@/misc/convert-host';
|
||||
import { MessagingMessages } from '@/models/index';
|
||||
import { readUserMessagingMessage } from '../../../server/api/common/read-messaging-message';
|
||||
|
||||
export const performReadActivity = async (actor: IRemoteUser, activity: IRead): Promise<string> => {
|
||||
const id = await getApId(activity.object);
|
||||
|
||||
if (!isSelfHost(extractDbHost(id))) {
|
||||
return `skip: Read to foreign host (${id})`;
|
||||
}
|
||||
|
||||
const messageId = id.split('/').pop();
|
||||
|
||||
const message = await MessagingMessages.findOne(messageId);
|
||||
if (message == null) {
|
||||
return `skip: message not found`;
|
||||
}
|
||||
|
||||
if (actor.id != message.recipientId) {
|
||||
return `skip: actor is not a message recipient`;
|
||||
}
|
||||
|
||||
await readUserMessagingMessage(message.recipientId!, message.userId, [message.id]);
|
||||
return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`;
|
||||
};
|
@@ -0,0 +1,29 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import reject from '@/services/following/requests/reject';
|
||||
import { IFollow } from '../../type';
|
||||
import DbResolver from '../../db-resolver';
|
||||
import { relayRejected } from '@/services/relay';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||
|
||||
const dbResolver = new DbResolver();
|
||||
const follower = await dbResolver.getUserFromApId(activity.actor);
|
||||
|
||||
if (follower == null) {
|
||||
return `skip: follower not found`;
|
||||
}
|
||||
|
||||
if (follower.host != null) {
|
||||
return `skip: follower is not a local user`;
|
||||
}
|
||||
|
||||
// relay
|
||||
const match = activity.id?.match(/follow-relay\/(\w+)/);
|
||||
if (match) {
|
||||
return await relayRejected(match[1]);
|
||||
}
|
||||
|
||||
await reject(actor, follower);
|
||||
return `ok`;
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import rejectFollow from './follow';
|
||||
import { IReject, isFollow, getApType } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IReject): Promise<string> => {
|
||||
const uri = activity.id || activity;
|
||||
|
||||
logger.info(`Reject: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
logger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (isFollow(object)) return await rejectFollow(actor, object);
|
||||
|
||||
return `skip: Unknown Reject type: ${getApType(object)}`;
|
||||
};
|
@@ -0,0 +1,23 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { IRemove } from '../../type';
|
||||
import { resolveNote } from '../../models/note';
|
||||
import { removePinned } from '@/services/i/pin';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
||||
if (activity.target == null) {
|
||||
throw new Error('target is null');
|
||||
}
|
||||
|
||||
if (activity.target === actor.featured) {
|
||||
const note = await resolveNote(activity.object);
|
||||
if (note == null) throw new Error('note not found');
|
||||
await removePinned(actor, note.id);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`unknown target: ${activity.target}`);
|
||||
};
|
@@ -0,0 +1,17 @@
|
||||
import { Notes } from '@/models/index';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { IAnnounce, getApId } from '../../type';
|
||||
import deleteNote from '@/services/note/delete';
|
||||
|
||||
export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<string> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
const note = await Notes.findOne({
|
||||
uri
|
||||
});
|
||||
|
||||
if (!note) return 'skip: no such Announce';
|
||||
|
||||
await deleteNote(actor, note);
|
||||
return 'ok: deleted';
|
||||
};
|
20
packages/backend/src/remote/activitypub/kernel/undo/block.ts
Normal file
20
packages/backend/src/remote/activitypub/kernel/undo/block.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { IBlock } from '../../type';
|
||||
import unblock from '@/services/blocking/delete';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import DbResolver from '../../db-resolver';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
|
||||
const dbResolver = new DbResolver();
|
||||
const blockee = await dbResolver.getUserFromApId(activity.object);
|
||||
|
||||
if (blockee == null) {
|
||||
return `skip: blockee not found`;
|
||||
}
|
||||
|
||||
if (blockee.host != null) {
|
||||
return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`;
|
||||
}
|
||||
|
||||
await unblock(actor, blockee);
|
||||
return `ok`;
|
||||
};
|
@@ -0,0 +1,41 @@
|
||||
import unfollow from '@/services/following/delete';
|
||||
import cancelRequest from '@/services/following/requests/cancel';
|
||||
import { IFollow } from '../../type';
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { FollowRequests, Followings } from '@/models/index';
|
||||
import DbResolver from '../../db-resolver';
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
|
||||
const dbResolver = new DbResolver();
|
||||
|
||||
const followee = await dbResolver.getUserFromApId(activity.object);
|
||||
if (followee == null) {
|
||||
return `skip: followee not found`;
|
||||
}
|
||||
|
||||
if (followee.host != null) {
|
||||
return `skip: フォロー解除しようとしているユーザーはローカルユーザーではありません`;
|
||||
}
|
||||
|
||||
const req = await FollowRequests.findOne({
|
||||
followerId: actor.id,
|
||||
followeeId: followee.id
|
||||
});
|
||||
|
||||
const following = await Followings.findOne({
|
||||
followerId: actor.id,
|
||||
followeeId: followee.id
|
||||
});
|
||||
|
||||
if (req) {
|
||||
await cancelRequest(followee, actor);
|
||||
return `ok: follow request canceled`;
|
||||
}
|
||||
|
||||
if (following) {
|
||||
await unfollow(actor, followee);
|
||||
return `ok: unfollowed`;
|
||||
}
|
||||
|
||||
return `skip: リクエストもフォローもされていない`;
|
||||
};
|
34
packages/backend/src/remote/activitypub/kernel/undo/index.ts
Normal file
34
packages/backend/src/remote/activitypub/kernel/undo/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType } from '../../type';
|
||||
import unfollow from './follow';
|
||||
import unblock from './block';
|
||||
import undoLike from './like';
|
||||
import { undoAnnounce } from './announce';
|
||||
import Resolver from '../../resolver';
|
||||
import { apLogger } from '../../logger';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IUndo): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
||||
const uri = activity.id || activity;
|
||||
|
||||
logger.info(`Undo: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
logger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (isFollow(object)) return await unfollow(actor, object);
|
||||
if (isBlock(object)) return await unblock(actor, object);
|
||||
if (isLike(object)) return await undoLike(actor, object);
|
||||
if (isAnnounce(object)) return await undoAnnounce(actor, object);
|
||||
|
||||
return `skip: unknown object type ${getApType(object)}`;
|
||||
};
|
21
packages/backend/src/remote/activitypub/kernel/undo/like.ts
Normal file
21
packages/backend/src/remote/activitypub/kernel/undo/like.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { ILike, getApId } from '../../type';
|
||||
import deleteReaction from '@/services/note/reaction/delete';
|
||||
import { fetchNote } from '../../models/note';
|
||||
|
||||
/**
|
||||
* Process Undo.Like activity
|
||||
*/
|
||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
const note = await fetchNote(targetUri);
|
||||
if (!note) return `skip: target note not found ${targetUri}`;
|
||||
|
||||
await deleteReaction(actor, note).catch(e => {
|
||||
if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') return;
|
||||
throw e;
|
||||
});
|
||||
|
||||
return `ok`;
|
||||
};
|
@@ -0,0 +1,34 @@
|
||||
import { IRemoteUser } from '@/models/entities/user';
|
||||
import { getApType, IUpdate, isActor } from '../../type';
|
||||
import { apLogger } from '../../logger';
|
||||
import { updateQuestion } from '../../models/question';
|
||||
import Resolver from '../../resolver';
|
||||
import { updatePerson } from '../../models/person';
|
||||
|
||||
/**
|
||||
* Updateアクティビティを捌きます
|
||||
*/
|
||||
export default async (actor: IRemoteUser, activity: IUpdate): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
return `skip: invalid actor`;
|
||||
}
|
||||
|
||||
apLogger.debug('Update');
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
apLogger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (isActor(object)) {
|
||||
await updatePerson(actor.uri!, resolver, object);
|
||||
return `ok: Person updated`;
|
||||
} else if (getApType(object) === 'Question') {
|
||||
await updateQuestion(object).catch(e => console.log(e));
|
||||
return `ok: Question updated`;
|
||||
} else {
|
||||
return `skip: Unknown type: ${getApType(object)}`;
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user