strictNullChecks (#4666)
* wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip
This commit is contained in:
parent
4ee40c3345
commit
987168b863
214 changed files with 939 additions and 785 deletions
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -39,6 +39,4 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
|
|||
undoLike(actor, object as ILike);
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -5,7 +5,7 @@ import { query as urlQuery } from '../prelude/url';
|
|||
|
||||
type ILink = {
|
||||
href: string;
|
||||
rel: string;
|
||||
rel?: string;
|
||||
};
|
||||
|
||||
type IWebFinger = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue