fix remote move queue

This commit is contained in:
cutestnekoaqua 2022-12-07 18:16:23 +01:00
parent 2978dad2be
commit 2694baf85d
4 changed files with 63 additions and 34 deletions

View File

@ -60,12 +60,24 @@ const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]
function isLocalUser(user: User): user is ILocalUser; function isLocalUser(user: User): user is ILocalUser;
function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; }; function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
/**
* Returns true if the user is local.
*
* @param user The user to check.
* @returns True if the user is local.
*/
function isLocalUser(user: User | { host: User['host'] }): boolean { function isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null; return user.host == null;
} }
function isRemoteUser(user: User): user is IRemoteUser; function isRemoteUser(user: User): user is IRemoteUser;
function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; }; function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
/**
* Returns true if the user is remote.
*
* @param user The user to check.
* @returns True if the user is remote.
*/
function isRemoteUser(user: User | { host: User['host'] }): boolean { function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user); return !isLocalUser(user);
} }

View File

@ -12,27 +12,44 @@ import { ApiError } from '@/server/api/error.js';
import { meta } from '@/server/api/endpoints/following/create.js'; import { meta } from '@/server/api/endpoints/following/create.js';
import { IObject, IActor } from '../../type.js'; import { IObject, IActor } from '../../type.js';
import type { IMove } from '../../type.js'; import type { IMove } from '../../type.js';
import Resolver from '@/remote/activitypub/resolver.js';
export default async (actor: CacheableRemoteUser, activity: IMove): Promise<string> => { export default async (actor: CacheableRemoteUser, activity: IMove): Promise<string> => {
// ※ There is a block target in activity.object, which should be a local user that exists. // ※ There is a block target in activity.object, which should be a local user that exists.
const dbResolver = new DbResolver(); const dbResolver = new DbResolver();
const resolver = new Resolver();
let new_acc = await dbResolver.getUserFromApId(activity.target); let new_acc = await dbResolver.getUserFromApId(activity.target);
if (!new_acc) new_acc = await getRemoteUser(<string>activity.target); let actor_new;
let actor_old;
if (!new_acc) actor_new = await resolver.resolve(<string>activity.target) as IActor;
let old_acc = await dbResolver.getUserFromApId(activity.actor); let old_acc = actor;
if (!old_acc) new_acc = await getRemoteUser(<string>activity.actor); if (!old_acc) actor_old = await resolver.resolve(<string>activity.actor) as IActor;
if (!new_acc || new_acc.uri === null) { if ((!new_acc || new_acc.uri === null) && (!actor_new || actor_new.id === null)) {
return 'move: new acc not found'; return 'move: new acc not found';
} }
if (!old_acc || old_acc.uri === null) { if ((!old_acc || old_acc.uri === null) && (!actor_old || actor_old.id === null)) {
return 'move: old acc not found'; return 'move: old acc not found';
} }
await updatePerson(new_acc.uri);
await updatePerson(old_acc.uri); let newUri: string | null | undefined
new_acc = await getRemoteUser(new_acc.uri); let oldUri: string | null | undefined
old_acc = await getRemoteUser(old_acc.uri); newUri = new_acc ? new_acc.uri :
actor_new?.url?.toString();
oldUri = old_acc ? old_acc.uri :
actor_old?.url?.toString();
if(newUri === null || newUri === undefined) return 'move: new acc not found #2';
if(oldUri === null || oldUri === undefined) return 'move: old acc not found #2';
await updatePerson(newUri);
await updatePerson(oldUri);
new_acc = await getRemoteUser(newUri);
old_acc = await getRemoteUser(oldUri);
if (old_acc === null || old_acc.uri === null || !new_acc.alsoKnownAs?.includes(old_acc.uri)) return 'move: accounts invalid'; if (old_acc === null || old_acc.uri === null || !new_acc.alsoKnownAs?.includes(old_acc.uri)) return 'move: accounts invalid';
@ -45,18 +62,18 @@ export default async (actor: CacheableRemoteUser, activity: IMove): Promise<stri
const followings = await query const followings = await query
.getMany(); .getMany();
followings.forEach(following => { followings.forEach(async following => {
if (!following.follower?.host) { if (following.follower?.host) {
const follower = following.follower; const follower = following.follower;
deleteFollowing(follower!, old_acc!); await deleteFollowing(follower!, old_acc!);
try { try {
create(follower!, new_acc!); await create(follower!, new_acc!);
} catch (e) { } catch (e) {
if (e instanceof IdentifiableError) { if (e instanceof IdentifiableError) {
if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking); if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') return meta.errors.blocking;
if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked); if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') return meta.errors.blocked;
} }
throw e; return e;
} }
} }
}); });

View File

@ -279,21 +279,21 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
} }
/** /**
* Personの情報を更新します * Update Person data from remote.
* Misskeyに対象のPersonが登録されていなければ無視します * If the target Person is not registered in Calckey, it is ignored.
* @param uri URI of Person * @param uri URI of Person
* @param resolver Resolver * @param resolver Resolver
* @param hint Hint of Person object (Personの場合Remote resolveをせずに更新に利用します) * @param hint Hint of Person object (If this value is a valid Person, it is used for updating without Remote resolve)
*/ */
export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> { export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
// URIがこのサーバーを指しているならスキップ // Skip if the URI points to this server
if (uri.startsWith(config.url + '/')) { if (uri.startsWith(config.url + '/')) {
return; return;
} }
//#region このサーバーに既に登録されているか //#region Already registered on this server?
const exist = await Users.findOneBy({ uri }) as IRemoteUser; const exist = await Users.findOneBy({ uri }) as IRemoteUser;
if (exist == null) { if (exist == null) {
@ -309,7 +309,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
logger.info(`Updating the Person: ${person.id}`); logger.info(`Updating the Person: ${person.id}`);
// アバターとヘッダー画像をフェッチ // Fetch avatar and header image
const [avatar, banner] = await Promise.all([ const [avatar, banner] = await Promise.all([
person.icon, person.icon,
person.image, person.image,
@ -319,7 +319,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
: resolveImage(exist, img).catch(() => null), : resolveImage(exist, img).catch(() => null),
)); ));
// カスタム絵文字取得 // Custom pictogram acquisition
const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => { const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => {
logger.info(`extractEmojis: ${e}`); logger.info(`extractEmojis: ${e}`);
return [] as Emoji[]; return [] as Emoji[];
@ -378,10 +378,10 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
publishInternalEvent('remoteUserUpdated', { id: exist.id }); publishInternalEvent('remoteUserUpdated', { id: exist.id });
// ハッシュタグ更新 // Hashtag Update
updateUsertags(exist, tags); updateUsertags(exist, tags);
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする // If the user in question is a follower, followers will also be updated.
await Followings.update({ await Followings.update({
followerId: exist.id, followerId: exist.id,
}, { }, {
@ -392,15 +392,15 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
} }
/** /**
* Personを解決します * Resolve Person.
* *
* Misskeyに対象のPersonが登録されていればそれを返し * If the target person is registered in Calckey, it returns it;
* Misskeyに登録しそれを返します * otherwise, it fetches it from the remote server, registers it in Calckey, and returns it.
*/ */
export async function resolvePerson(uri: string, resolver?: Resolver): Promise<CacheableUser> { export async function resolvePerson(uri: string, resolver?: Resolver): Promise<CacheableUser> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
//#region このサーバーに既に登録されていたらそれを返す //#region If already registered on this server, return it.
const exist = await fetchPerson(uri); const exist = await fetchPerson(uri);
if (exist) { if (exist) {
@ -408,7 +408,7 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<C
} }
//#endregion //#endregion
// リモートサーバーからフェッチしてきて登録 // Fetched from remote server and registered
if (resolver == null) resolver = new Resolver(); if (resolver == null) resolver = new Resolver();
return await createPerson(uri, resolver); return await createPerson(uri, resolver);
} }
@ -486,14 +486,14 @@ export async function updateFeatured(userId: User['id'], resolver?: Resolver) {
// Resolve and regist Notes // Resolve and regist Notes
const limit = promiseLimit<Note | null>(2); const limit = promiseLimit<Note | null>(2);
const featuredNotes = await Promise.all(items const featuredNotes = await Promise.all(items
.filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも .filter(item => getApType(item) === 'Note') // TODO: Maybe it doesn't have to be a Note.
.slice(0, 5) .slice(0, 5)
.map(item => limit(() => resolveNote(item, resolver)))); .map(item => limit(() => resolveNote(item, resolver))));
await db.transaction(async transactionalEntityManager => { await db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); await transactionalEntityManager.delete(UserNotePining, { userId: user.id });
// とりあえずidを別の時間で生成して順番を維持 // For now, generate the id at a different time and maintain the order.
let td = 0; let td = 0;
for (const note of featuredNotes.filter(note => note != null)) { for (const note of featuredNotes.filter(note => note != null)) {
td -= 1000; td -= 1000;

View File

@ -61,7 +61,7 @@ export default async (ctx: Router.RouterContext) => {
followerId: user.id, followerId: user.id,
} as FindOptionsWhere<Following>; } as FindOptionsWhere<Following>;
// カーソルが指定されている場合 // If a cursor is specified
if (cursor) { if (cursor) {
query.id = LessThan(cursor); query.id = LessThan(cursor);
} }
@ -73,7 +73,7 @@ export default async (ctx: Router.RouterContext) => {
order: { id: -1 }, order: { id: -1 },
}); });
// 「次のページ」があるかどうか // Whether there is a "next page" or not
const inStock = followings.length === limit + 1; const inStock = followings.length === limit + 1;
if (inStock) followings.pop(); if (inStock) followings.pop();