strictNullChecks (#4666)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip
This commit is contained in:
syuilo
2019-04-13 01:43:22 +09:00
committed by GitHub
parent 4ee40c3345
commit 987168b863
214 changed files with 939 additions and 785 deletions

View File

@@ -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);
const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name);
return await Promise.all(
eomjiTags.map(async tag => {
const name = tag.name.replace(/^:/, '').replace(/:$/, '');
return await Promise.all(eomjiTags.map(async tag => {
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
const exists = await Emojis.findOne({
host,
name
});
const exists = 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),
});
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 await Emojis.findOne({
host,
name
});
}
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[];
}