diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2019-04-13 01:43:22 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-04-13 01:43:22 +0900 |
| commit | 987168b863c52d0548050ffbac569782bb9a8cef (patch) | |
| tree | c9aa2243dcdcbd044688d201a51c601574bff259 /src/remote | |
| parent | Fix bug (diff) | |
| download | sharkey-987168b863c52d0548050ffbac569782bb9a8cef.tar.gz sharkey-987168b863c52d0548050ffbac569782bb9a8cef.tar.bz2 sharkey-987168b863c52d0548050ffbac569782bb9a8cef.zip | |
strictNullChecks (#4666)
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
Diffstat (limited to 'src/remote')
26 files changed, 192 insertions, 166 deletions
diff --git a/src/remote/activitypub/kernel/accept/follow.ts b/src/remote/activitypub/kernel/accept/follow.ts index 816fcbadbf..f3e517ad9f 100644 --- a/src/remote/activitypub/kernel/accept/follow.ts +++ b/src/remote/activitypub/kernel/accept/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const follower = await Users.findOne({ diff --git a/src/remote/activitypub/kernel/add/index.ts b/src/remote/activitypub/kernel/add/index.ts index d16f0a4a0d..a5b2687416 100644 --- a/src/remote/activitypub/kernel/add/index.ts +++ b/src/remote/activitypub/kernel/add/index.ts @@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => { 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; } diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index 403fc66bed..f9822c5187 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -53,16 +53,16 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: logger.info(`Creating the (Re)Note: ${uri}`); //#region Visibility - const visibility = getVisibility(activity.to, activity.cc, actor); + const visibility = getVisibility(activity.to || [], activity.cc || [], actor); let visibleUsers: User[] = []; if (visibility == 'specified') { - visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); + visibleUsers = await Promise.all((note.to || []).map(uri => resolvePerson(uri))); } //#endergion await post(actor, { - createdAt: new Date(activity.published), + createdAt: activity.published ? new Date(activity.published) : null, renote, visibility, visibleUsers, @@ -75,9 +75,6 @@ type visibility = 'public' | 'home' | 'followers' | 'specified'; function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility { const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'; - to = to || []; - cc = cc || []; - if (to.includes(PUBLIC)) { return 'public'; } else if (cc.includes(PUBLIC)) { diff --git a/src/remote/activitypub/kernel/block/index.ts b/src/remote/activitypub/kernel/block/index.ts index 48e251dd9b..19e33eb7dd 100644 --- a/src/remote/activitypub/kernel/block/index.ts +++ b/src/remote/activitypub/kernel/block/index.ts @@ -9,13 +9,14 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const uri = activity.id || activity; logger.info(`Block: ${uri}`); if (!id.startsWith(config.url + '/')) { - return null; + return; } const blockee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/follow.ts b/src/remote/activitypub/kernel/follow.ts index e6c8833f3a..d37404502f 100644 --- a/src/remote/activitypub/kernel/follow.ts +++ b/src/remote/activitypub/kernel/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const followee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/index.ts b/src/remote/activitypub/kernel/index.ts index 4a57d0675e..d1251817fa 100644 --- a/src/remote/activitypub/kernel/index.ts +++ b/src/remote/activitypub/kernel/index.ts @@ -71,7 +71,7 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { default: apLogger.warn(`unknown activity type: ${(activity as any).type}`); - return null; + return; } }; diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts index 86dd8fb33d..d4fa7bf387 100644 --- a/src/remote/activitypub/kernel/like.ts +++ b/src/remote/activitypub/kernel/like.ts @@ -5,6 +5,7 @@ import { Notes } from '../../../models'; export default async (actor: IRemoteUser, activity: ILike) => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; // Transform: // https://misskey.ex/notes/xxxx to diff --git a/src/remote/activitypub/kernel/reject/follow.ts b/src/remote/activitypub/kernel/reject/follow.ts index b06ae6fb96..91689339ab 100644 --- a/src/remote/activitypub/kernel/reject/follow.ts +++ b/src/remote/activitypub/kernel/reject/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const follower = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/remove/index.ts b/src/remote/activitypub/kernel/remove/index.ts index ae33be59dc..32b8d66471 100644 --- a/src/remote/activitypub/kernel/remove/index.ts +++ b/src/remote/activitypub/kernel/remove/index.ts @@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => { 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; } diff --git a/src/remote/activitypub/kernel/undo/block.ts b/src/remote/activitypub/kernel/undo/block.ts index c916a00737..9c277ed7d2 100644 --- a/src/remote/activitypub/kernel/undo/block.ts +++ b/src/remote/activitypub/kernel/undo/block.ts @@ -9,13 +9,14 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const uri = activity.id || activity; logger.info(`UnBlock: ${uri}`); if (!id.startsWith(config.url + '/')) { - return null; + return; } const blockee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/undo/follow.ts b/src/remote/activitypub/kernel/undo/follow.ts index cc63a740b1..ce84d0c791 100644 --- a/src/remote/activitypub/kernel/undo/follow.ts +++ b/src/remote/activitypub/kernel/undo/follow.ts @@ -7,9 +7,10 @@ import { Users, FollowRequests, Followings } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const followee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/undo/index.ts b/src/remote/activitypub/kernel/undo/index.ts index 6376ab93a8..5f2e58c3bf 100644 --- a/src/remote/activitypub/kernel/undo/index.ts +++ b/src/remote/activitypub/kernel/undo/index.ts @@ -39,6 +39,4 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => { undoLike(actor, object as ILike); break; } - - return null; }; diff --git a/src/remote/activitypub/kernel/undo/like.ts b/src/remote/activitypub/kernel/undo/like.ts index f337a0173e..75879d697a 100644 --- a/src/remote/activitypub/kernel/undo/like.ts +++ b/src/remote/activitypub/kernel/undo/like.ts @@ -8,6 +8,7 @@ import { Notes } from '../../../../models'; */ export default async (actor: IRemoteUser, activity: ILike): Promise<void> => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const noteId = id.split('/').pop(); diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts index c9991dba3b..f8b35ea21c 100644 --- a/src/remote/activitypub/models/image.ts +++ b/src/remote/activitypub/models/image.ts @@ -5,6 +5,7 @@ import fetchMeta from '../../../misc/fetch-meta'; import { apLogger } from '../logger'; import { DriveFile } from '../../../models/entities/drive-file'; import { DriveFiles } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; const logger = apLogger; @@ -14,7 +15,7 @@ const logger = apLogger; export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - return null; + throw new Error('actor has been suspended'); } const image = await new Resolver().resolve(value) as any; @@ -28,17 +29,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive const instance = await fetchMeta(); const cache = instance.cacheRemoteFiles; - let file; - try { - file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache); - } catch (e) { - // 4xxの場合は添付されてなかったことにする - if (e >= 400 && e < 500) { - logger.warn(`Ignored image: ${image.url} - ${e}`); - return null; - } - throw e; - } + let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache); if (file.isLink) { // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 @@ -49,7 +40,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive uri: image.url }); - file = DriveFiles.findOne(file.id); + file = await DriveFiles.findOne(file.id).then(ensure); } } diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index eb4175875d..78f5005953 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -21,6 +21,7 @@ import { IObject, INote } from '../type'; import { Emoji } from '../../../models/entities/emoji'; import { genId } from '../../../misc/gen-id'; import fetchMeta from '../../../misc/fetch-meta'; +import { ensure } from '../../../prelude/ensure'; const logger = apLogger; @@ -29,13 +30,14 @@ const logger = apLogger; * * Misskeyに対象のNoteが登録されていればそれを返します。 */ -export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<Note> { +export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> { const uri = typeof value == 'string' ? value : value.id; + if (uri == null) throw 'missing uri'; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Notes.findOne(id); + return await Notes.findOne(id).then(x => x || null); } //#region このサーバーに既に登録されていたらそれを返す @@ -52,7 +54,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P /** * Noteを作成します。 */ -export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<Note> { +export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<Note | null> { if (resolver == null) resolver = new Resolver(); const object: any = await resolver.resolve(value); @@ -65,7 +67,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false value: value, object: object }); - return null; + throw 'invalid note'; } const note: INote = object; @@ -75,11 +77,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ - const actor = await resolvePerson(note.attributedTo, null, resolver) as IRemoteUser; + const actor = await resolvePerson(note.attributedTo, resolver) as IRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - return null; + throw 'actor has been suspended'; } //#region Visibility @@ -95,9 +97,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false visibility = 'followers'; } else { visibility = 'specified'; - visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, null, resolver))); + visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, resolver))); } -} + } //#endergion const apMentions = await extractMentionedUsers(actor, note.to, note.cc, resolver); @@ -118,7 +120,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false : []; // リプライ - const reply: Note = note.inReplyTo + const reply: Note | undefined | null = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver).catch(e => { // 4xxの場合はリプライしてないことにする if (e.statusCode >= 400 && e.statusCode < 500) { @@ -131,7 +133,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false : null; // 引用 - let quote: Note; + let quote: Note | undefined | null; if (note._misskey_quote && typeof note._misskey_quote == 'string') { quote = await resolveNote(note._misskey_quote).catch(e => { @@ -152,7 +154,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false // vote if (reply && reply.hasPoll) { - const poll = await Polls.findOne({ noteId: reply.id }); + const poll = await Polls.findOne({ noteId: reply.id }).then(ensure); + const tryCreateVote = async (name: string, index: number): Promise<null> => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); @@ -180,7 +183,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } } - const emojis = await extractEmojis(note.tag, actor.host).catch(e => { + const emojis = await extractEmojis(note.tag || [], actor.host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); @@ -196,7 +199,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } return await post(actor, { - createdAt: new Date(note.published), + createdAt: note.published ? new Date(note.published) : null, files, reply, renote: quote, @@ -223,8 +226,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note> { +export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> { const uri = typeof value == 'string' ? value : value.id; + if (uri == null) throw 'missing uri'; // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく @@ -244,75 +248,79 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 return await createNote(uri, resolver).catch(e => { if (e.name === 'duplicated') { - return fetchNote(uri); + return fetchNote(uri).then(note => { + if (note == null) { + throw 'something happened'; + } else { + return note; + } + }); } else { throw e; } }); } -export async function extractEmojis(tags: ITag[], host: string) { +export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> { host = toPuny(host); if (!tags) return []; - const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url); - - return await Promise.all( - eomjiTags.map(async tag => { - const name = tag.name.replace(/^:/, '').replace(/:$/, ''); + const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name); - const exists = await Emojis.findOne({ - host, - name - }); + return await Promise.all(eomjiTags.map(async tag => { + const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); - if (exists) { - if ((tag.updated != null && exists.updatedAt == null) - || (tag.id != null && exists.uri == null) - || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) - ) { - await Emojis.update({ - host, - name, - }, { - uri: tag.id, - url: tag.icon.url, - updatedAt: new Date(tag.updated), - }); + const exists = await Emojis.findOne({ + host, + name + }); - return await Emojis.findOne({ - host, - name - }); - } + if (exists) { + if ((tag.updated != null && exists.updatedAt == null) + || (tag.id != null && exists.uri == null) + || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) + ) { + await Emojis.update({ + host, + name, + }, { + uri: tag.id, + url: tag.icon!.url, + updatedAt: new Date(tag.updated!), + }); - return exists; + return await Emojis.findOne({ + host, + name + }) as Emoji; } - logger.info(`register emoji host=${host}, name=${name}`); + return exists; + } - return await Emojis.save({ - id: genId(), - host, - name, - uri: tag.id, - url: tag.icon.url, - updatedAt: tag.updated ? new Date(tag.updated) : undefined, - aliases: [] - } as Emoji); - }) - ); + logger.info(`register emoji host=${host}, name=${name}`); + + return await Emojis.save({ + id: genId(), + host, + name, + uri: tag.id, + url: tag.icon!.url, + updatedAt: tag.updated ? new Date(tag.updated) : undefined, + aliases: [] + } as Partial<Emoji>); + })); } async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) { const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`]; const uris = difference(unique(concat([to || [], cc || []])), ignoreUris); - const limit = promiseLimit(2); + const limit = promiseLimit<User | null>(2); const users = await Promise.all( - uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise<User>) + uris.map(uri => limit(() => resolvePerson(uri, resolver).catch(() => null)) as Promise<User | null>) ); - return users.filter(x => x != null); + return users.filter(x => x != null) as User[]; } diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index e7021956de..9465cf0cd0 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -26,6 +26,7 @@ import { toPuny } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; import { validActor } from '../../../remote/activitypub/type'; import { getConnection } from 'typeorm'; +import { ensure } from '../../../prelude/ensure'; const logger = apLogger; /** @@ -86,13 +87,13 @@ function validatePerson(x: any, uri: string) { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User> { +export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> { if (typeof uri !== 'string') throw 'uri is not string'; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Users.findOne(id); + return await Users.findOne(id).then(x => x || null); } //#region このサーバーに既に登録されていたらそれを返す @@ -128,7 +129,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us const host = toPuny(new URL(object.id).hostname); - const { fields } = analyzeAttachments(person.attachment); + const { fields } = analyzeAttachments(person.attachment || []); const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()); @@ -161,7 +162,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us await transactionalEntityManager.save(new UserProfile({ userId: user.id, - description: fromHtml(person.summary), + description: person.summary ? fromHtml(person.summary) : null, url: person.url, fields, userHost: host @@ -189,20 +190,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us instanceChart.newUser(i.host); }); - usersChart.update(user, true); + usersChart.update(user!, true); // ハッシュタグ更新 - for (const tag of tags) updateHashtag(user, tag, true, true); - for (const tag of (user.tags || []).filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false); + for (const tag of tags) updateHashtag(user!, tag, true, true); + for (const tag of (user!.tags || []).filter(x => !tags.includes(x))) updateHashtag(user!, tag, true, false); //#region アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all<DriveFile>([ + const [avatar, banner] = (await Promise.all<DriveFile | null>([ person.icon, person.image ].map(img => img == null ? Promise.resolve(null) - : resolveImage(user, img).catch(() => null) + : resolveImage(user!, img).catch(() => null) ))); const avatarId = avatar ? avatar.id : null; @@ -210,9 +211,9 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar) : null; const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null; const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null; - const bannerColor = banner && avatar.properties.avgColor ? banner.properties.avgColor : null; + const bannerColor = banner && banner.properties.avgColor ? banner.properties.avgColor : null; - await Users.update(user.id, { + await Users.update(user!.id, { avatarId, bannerId, avatarUrl, @@ -221,30 +222,30 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us bannerColor }); - user.avatarId = avatarId; - user.bannerId = bannerId; - user.avatarUrl = avatarUrl; - user.bannerUrl = bannerUrl; - user.avatarColor = avatarColor; - user.bannerColor = bannerColor; + user!.avatarId = avatarId; + user!.bannerId = bannerId; + user!.avatarUrl = avatarUrl; + user!.bannerUrl = bannerUrl; + user!.avatarColor = avatarColor; + user!.bannerColor = bannerColor; //#endregion //#region カスタム絵文字取得 - const emojis = await extractEmojis(person.tag, host).catch(e => { + const emojis = await extractEmojis(person.tag || [], host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); - await Users.update(user.id, { + await Users.update(user!.id, { emojis: emojiNames }); //#endregion - await updateFeatured(user.id).catch(err => logger.error(err)); + await updateFeatured(user!.id).catch(err => logger.error(err)); - return user; + return user!; } /** @@ -254,7 +255,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us * @param resolver Resolver * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) */ -export async function updatePerson(uri: string, resolver?: Resolver, hint?: object): Promise<void> { +export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: object): Promise<void> { if (typeof uri !== 'string') throw 'uri is not string'; // URIがこのサーバーを指しているならスキップ @@ -290,7 +291,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje logger.info(`Updating the Person: ${person.id}`); // アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all<DriveFile>([ + const [avatar, banner] = (await Promise.all<DriveFile | null>([ person.icon, person.image ].map(img => @@ -300,14 +301,14 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje ))); // カスタム絵文字取得 - const emojis = await extractEmojis(person.tag, exist.host).catch(e => { + const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); - const { fields, services } = analyzeAttachments(person.attachment); + const { fields, services } = analyzeAttachments(person.attachment || []); const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()); @@ -317,7 +318,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), featured: person.featured, emojis: emojiNames, - description: fromHtml(person.summary), + description: person.summary ? fromHtml(person.summary) : null, name: person.name, url: person.url, endpoints: person.endpoints, @@ -326,7 +327,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje isBot: object.type == 'Service', isCat: (person as any).isCat === true, isLocked: person.manuallyApprovesFollowers, - createdAt: new Date(Date.parse(person.published)) || null, } as Partial<User>; if (avatar) { @@ -379,7 +379,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise<User> { +export async function resolvePerson(uri: string, resolver?: Resolver): Promise<User> { if (typeof uri !== 'string') throw 'uri is not string'; //#region このサーバーに既に登録されていたらそれを返す @@ -439,21 +439,24 @@ export function analyzeAttachments(attachments: ITag[]) { }[] = []; const services: { [x: string]: any } = {}; - if (Array.isArray(attachments)) - for (const attachment of attachments.filter(isPropertyValue)) - if (isPropertyValue(attachment.identifier)) - addService(services, attachment.identifier); - else + if (Array.isArray(attachments)) { + for (const attachment of attachments.filter(isPropertyValue)) { + if (isPropertyValue(attachment.identifier!)) { + addService(services, attachment.identifier!); + } else { fields.push({ - name: attachment.name, - value: fromHtml(attachment.value) + name: attachment.name!, + value: fromHtml(attachment.value!) }); + } + } + } return { fields, services }; } export async function updateFeatured(userId: User['id']) { - const user = await Users.findOne(userId); + const user = await Users.findOne(userId).then(ensure); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; @@ -471,18 +474,18 @@ export async function updateFeatured(userId: User['id']) { if (!Array.isArray(items)) throw new Error(`Collection items is not an array`); // Resolve and regist Notes - const limit = promiseLimit(2); + const limit = promiseLimit<Note | null>(2); const featuredNotes = await Promise.all(items .filter(item => item.type === 'Note') .slice(0, 5) - .map(item => limit(() => resolveNote(item, resolver)) as Promise<Note>)); + .map(item => limit(() => resolveNote(item, resolver)))); for (const note of featuredNotes.filter(note => note != null)) { UserNotePinings.save({ id: genId(), createdAt: new Date(), userId: user.id, - noteId: note.id + noteId: note!.id } as UserNotePining); } } diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index 2ff8e21ab5..708cdc2a66 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -14,10 +14,10 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi throw 'invalid question'; } - const choices = question[multiple ? 'anyOf' : 'oneOf'] - .map((x, i) => x.name); + const choices = question[multiple ? 'anyOf' : 'oneOf']! + .map((x, i) => x.name!); - const votes = question[multiple ? 'anyOf' : 'oneOf'] + const votes = question[multiple ? 'anyOf' : 'oneOf']! .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); return { @@ -60,7 +60,7 @@ export async function updateQuestion(value: any) { for (const choice of poll.choices) { const oldCount = poll.votes[poll.choices.indexOf(choice)]; - const newCount = apChoices.filter(ap => ap.name === choice)[0].replies.totalItems; + const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems; if (oldCount != newCount) { changed = true; diff --git a/src/remote/activitypub/models/tag.ts b/src/remote/activitypub/models/tag.ts index 0a1e6e29f9..8d2008d1d9 100644 --- a/src/remote/activitypub/models/tag.ts +++ b/src/remote/activitypub/models/tag.ts @@ -14,13 +14,13 @@ export type ITag = { identifier?: IIdentifier; }; -export function extractHashtags(tags: ITag[]) { - if (!tags) return []; +export function extractHashtags(tags: ITag[] | null | undefined): string[] { + if (tags == null) return []; const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string'); return hashtags.map(tag => { - const m = tag.name.match(/^#(.+)/); + const m = tag.name ? tag.name.match(/^#(.+)/) : null; return m ? m[1] : null; - }).filter(x => x != null); + }).filter(x => x != null) as string[]; } diff --git a/src/remote/activitypub/renderer/block.ts b/src/remote/activitypub/renderer/block.ts index 946c45a813..c29a9aea82 100644 --- a/src/remote/activitypub/renderer/block.ts +++ b/src/remote/activitypub/renderer/block.ts @@ -1,7 +1,7 @@ import config from '../../../config'; import { ILocalUser, IRemoteUser } from '../../../models/entities/user'; -export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({ +export default (blocker: ILocalUser, blockee: IRemoteUser) => ({ type: 'Block', actor: `${config.url}/users/${blocker.id}`, object: blockee.uri diff --git a/src/remote/activitypub/renderer/follow-user.ts b/src/remote/activitypub/renderer/follow-user.ts index 9446be3c86..6d354803e5 100644 --- a/src/remote/activitypub/renderer/follow-user.ts +++ b/src/remote/activitypub/renderer/follow-user.ts @@ -1,12 +1,13 @@ import config from '../../../config'; import { Users } from '../../../models'; import { User } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; /** * Convert (local|remote)(Follower|Followee)ID to URL * @param id Follower|Followee ID */ export default async function renderFollowUser(id: User['id']): Promise<any> { - const user = await Users.findOne(id); + const user = await Users.findOne(id).then(ensure); return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri; } diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index 58ee4fb52c..c66af2667b 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -10,6 +10,7 @@ import { DriveFiles, Notes, Users, Emojis, Polls } from '../../../models'; import { In } from 'typeorm'; import { Emoji } from '../../../models/entities/emoji'; import { Poll } from '../../../models/entities/poll'; +import { ensure } from '../../../prelude/ensure'; export default async function renderNote(note: Note, dive = true): Promise<any> { const promisedFiles: Promise<DriveFile[]> = note.fileIds.length > 0 @@ -17,15 +18,15 @@ export default async function renderNote(note: Note, dive = true): Promise<any> : Promise.resolve([]); let inReplyTo; - let inReplyToNote: Note; + let inReplyToNote: Note | undefined; if (note.replyId) { inReplyToNote = await Notes.findOne(note.replyId); - if (inReplyToNote !== null) { + if (inReplyToNote != null) { const inReplyToUser = await Users.findOne(inReplyToNote.userId); - if (inReplyToUser !== null) { + if (inReplyToUser != null) { if (inReplyToNote.uri) { inReplyTo = inReplyToNote.uri; } else { @@ -51,9 +52,7 @@ export default async function renderNote(note: Note, dive = true): Promise<any> } } - const user = await Users.findOne({ - id: note.userId - }); + const user = await Users.findOne(note.userId).then(ensure); const attributedTo = `${config.url}/users/${user.id}`; @@ -85,13 +84,13 @@ export default async function renderNote(note: Note, dive = true): Promise<any> const files = await promisedFiles; let text = note.text; - let poll: Poll; + let poll: Poll | undefined; if (note.hasPoll) { poll = await Polls.findOne({ noteId: note.id }); } - let question: string; + let question: string | undefined; if (poll) { if (text == null) text = ''; const url = `${config.url}/notes/${note.id}`; @@ -144,7 +143,7 @@ export default async function renderNote(note: Note, dive = true): Promise<any> name: text, replies: { type: 'Collection', - totalItems: poll.votes[i] + totalItems: poll!.votes[i] } })) } : {}; @@ -179,5 +178,5 @@ export async function getEmojis(names: string[]): Promise<Emoji[]> { })) ); - return emojis.filter(emoji => emoji != null); + return emojis.filter(emoji => emoji != null) as Emoji[]; } diff --git a/src/remote/activitypub/renderer/ordered-collection-page.ts b/src/remote/activitypub/renderer/ordered-collection-page.ts index 83af07870e..2433358646 100644 --- a/src/remote/activitypub/renderer/ordered-collection-page.ts +++ b/src/remote/activitypub/renderer/ordered-collection-page.ts @@ -7,7 +7,7 @@ * @param prev URL of prev page (optional) * @param next URL of next page (optional) */ -export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev: string, next: string) { +export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { const page = { id, partOf, diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index e561e47c68..3fb164ef4e 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -9,14 +9,15 @@ import renderEmoji from './emoji'; import { IIdentifier } from '../models/identifier'; import renderHashtag from './hashtag'; import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; export async function renderPerson(user: ILocalUser) { const id = `${config.url}/users/${user.id}`; const [avatar, banner, profile] = await Promise.all([ - DriveFiles.findOne(user.avatarId), - DriveFiles.findOne(user.bannerId), - UserProfiles.findOne({ userId: user.id }) + user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined), + user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined), + UserProfiles.findOne({ userId: user.id }).then(ensure) ]); const attachment: { @@ -76,9 +77,7 @@ export async function renderPerson(user: ILocalUser) { ...hashtagTags, ]; - const keypair = await UserKeypairs.findOne({ - userId: user.id - }); + const keypair = await UserKeypairs.findOne(user.id).then(ensure); return { type: user.isBot ? 'Service' : 'Person', @@ -94,8 +93,8 @@ export async function renderPerson(user: ILocalUser) { preferredUsername: user.username, name: user.name, summary: toHtml(parse(profile.description)), - icon: user.avatarId && renderImage(avatar), - image: user.bannerId && renderImage(banner), + icon: avatar ? renderImage(avatar) : null, + image: banner ? renderImage(banner) : null, tag, manuallyApprovesFollowers: user.isLocked, publicKey: renderKey(user, keypair), diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index 8aca5e8102..7dc48c15e3 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -12,6 +12,7 @@ import { apLogger } from './logger'; import { UserKeypairs } from '../../models'; import fetchMeta from '../../misc/fetch-meta'; import { toPuny } from '../../misc/convert-host'; +import { ensure } from '../../prelude/ensure'; export const logger = apLogger.createSubLogger('deliver'); @@ -38,7 +39,7 @@ export default async (user: ILocalUser, url: string, object: any) => { const keypair = await UserKeypairs.findOne({ userId: user.id - }); + }).then(ensure); const _ = new Promise((resolve, reject) => { const req = request({ @@ -56,7 +57,7 @@ export default async (user: ILocalUser, url: string, object: any) => { 'Digest': `SHA-256=${hash}` } }, res => { - if (res.statusCode >= 400) { + if (res.statusCode! >= 400) { logger.warn(`${url} --> ${res.statusCode}`); reject(res); } else { @@ -73,7 +74,7 @@ export default async (user: ILocalUser, url: string, object: any) => { }); // Signature: Signature ... => Signature: ... - let sig = req.getHeader('Signature').toString(); + let sig = req.getHeader('Signature')!.toString(); sig = sig.replace(/^Signature /, ''); req.setHeader('Signature', sig); @@ -112,7 +113,7 @@ async function resolveAddr(domain: string) { function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> { return new Promise((res, rej) => { - lookup(domain, options, (error: any, address: string | string[]) => { + lookup(domain, options, (error, address) => { if (error) return rej(error); return res(Array.isArray(address) ? address[0] : address); }); diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index 6a8ce45c91..9b518f5e81 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -10,18 +10,31 @@ import { toPuny } from '../misc/convert-host'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, host: string, option?: any, resync = false): Promise<User> { +export async function resolveUser(username: string, host: string | null, option?: any, resync = false): Promise<User> { const usernameLower = username.toLowerCase(); - host = toPuny(host); if (host == null) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } + host = toPuny(host); + if (config.host == host) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } const user = await Users.findOne({ usernameLower, host }, option); @@ -63,7 +76,13 @@ export async function resolveUser(username: string, host: string, option?: any, await updatePerson(self.href); logger.info(`return resynced remote user: ${acctLower}`); - return await Users.findOne({ uri: self.href }); + return await Users.findOne({ uri: self.href }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } logger.info(`return existing remote user: ${acctLower}`); @@ -76,7 +95,7 @@ async function resolveSelf(acctLower: string) { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`); throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`); }); - const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); + const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); if (!self) { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); throw new Error('self link not found'); diff --git a/src/remote/webfinger.ts b/src/remote/webfinger.ts index 67535e37db..800673943b 100644 --- a/src/remote/webfinger.ts +++ b/src/remote/webfinger.ts @@ -5,7 +5,7 @@ import { query as urlQuery } from '../prelude/url'; type ILink = { href: string; - rel: string; + rel?: string; }; type IWebFinger = { |