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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
214 changed files with 939 additions and 785 deletions

View file

@ -120,7 +120,7 @@ gulp.task('copy:client', () =>
]) ])
.pipe(isProduction ? (imagemin as any)() : gutil.noop()) .pipe(isProduction ? (imagemin as any)() : gutil.noop())
.pipe(rename(path => { .pipe(rename(path => {
path.dirname = path.dirname.replace('assets', '.'); path.dirname = path.dirname!.replace('assets', '.');
})) }))
.pipe(gulp.dest('./built/client/assets/')) .pipe(gulp.dest('./built/client/assets/'))
); );

View file

@ -106,7 +106,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bootstrap-vue": "2.0.0-rc.13", "bootstrap-vue": "2.0.0-rc.13",
"bull": "3.7.0", "bull": "3.7.0",
"cafy": "15.1.0", "cafy": "15.1.1",
"chai": "4.2.0", "chai": "4.2.0",
"chai-http": "4.2.1", "chai-http": "4.2.1",
"chalk": "2.4.2", "chalk": "2.4.2",

View file

@ -44,7 +44,7 @@ function greet() {
export async function masterMain() { export async function masterMain() {
greet(); greet();
let config: Config; let config!: Config;
try { try {
// initialize app // initialize app

View file

@ -15,6 +15,6 @@ export async function workerMain() {
if (cluster.isWorker) { if (cluster.isWorker) {
// Send a 'ready' message to parent process // Send a 'ready' message to parent process
process.send('ready'); process.send!('ready');
} }
} }

View file

@ -29,7 +29,7 @@ export default function load() {
config.url = url.origin; config.url = url.origin;
config.port = config.port || parseInt(process.env.PORT, 10); config.port = config.port || parseInt(process.env.PORT || '', 10);
mixin.host = url.host; mixin.host = url.host;
mixin.hostname = url.hostname; mixin.hostname = url.hostname;

View file

@ -19,7 +19,7 @@ initDb().then(() => {
all, local all, local
}; };
process.send(stats); process.send!(stats);
} }
tick(); tick();

View file

@ -76,7 +76,7 @@ class MyCustomLogger implements Logger {
} }
export function initDb(justBorrow = false, sync = false, log = false) { export function initDb(justBorrow = false, sync = false, log = false) {
const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV); const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV || '');
try { try {
const conn = getConnection(); const conn = getConnection();
@ -93,7 +93,7 @@ export function initDb(justBorrow = false, sync = false, log = false) {
synchronize: process.env.NODE_ENV === 'test' || sync, synchronize: process.env.NODE_ENV === 'test' || sync,
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow, dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
logging: enableLogging, logging: enableLogging,
logger: enableLogging ? new MyCustomLogger() : null, logger: enableLogging ? new MyCustomLogger() : undefined,
entities: [ entities: [
Meta, Meta,
Instance, Instance,

View file

@ -37,7 +37,7 @@ export type Undo = {
/** /**
* *
*/ */
turn: Color; turn: Color | null;
}; };
/** /**
@ -47,12 +47,12 @@ export default class Reversi {
public map: MapPixel[]; public map: MapPixel[];
public mapWidth: number; public mapWidth: number;
public mapHeight: number; public mapHeight: number;
public board: Color[]; public board: (Color | null | undefined)[];
public turn: Color = BLACK; public turn: Color | null = BLACK;
public opts: Options; public opts: Options;
public prevPos = -1; public prevPos = -1;
public prevColor: Color = null; public prevColor: Color | null = null;
private logs: Undo[] = []; private logs: Undo[] = [];
@ -145,12 +145,12 @@ export default class Reversi {
// ターン計算 // ターン計算
this.turn = this.turn =
this.canPutSomewhere(!this.prevColor) ? !this.prevColor : this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
this.canPutSomewhere(this.prevColor) ? this.prevColor : this.canPutSomewhere(this.prevColor!) ? this.prevColor :
null; null;
} }
public undo() { public undo() {
const undo = this.logs.pop(); const undo = this.logs.pop()!;
this.prevColor = undo.color; this.prevColor = undo.color;
this.prevPos = undo.pos; this.prevPos = undo.pos;
this.board[undo.pos] = null; this.board[undo.pos] = null;
@ -254,10 +254,10 @@ export default class Reversi {
/** /**
* (null = ) * (null = )
*/ */
public get winner(): Color { public get winner(): Color | null {
return this.isEnded ? return this.isEnded ?
this.blackCount == this.whiteCount ? null : this.blackCount == this.whiteCount ? null :
this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
undefined; undefined as never;
} }
} }

View file

@ -3,8 +3,6 @@ import { URL } from 'url';
import { urlRegex } from './prelude'; import { urlRegex } from './prelude';
export function fromHtml(html: string): string { export function fromHtml(html: string): string {
if (html == null) return null;
const dom = parseFragment(html) as DefaultTreeDocumentFragment; const dom = parseFragment(html) as DefaultTreeDocumentFragment;
let text = ''; let text = '';

View file

@ -2,7 +2,7 @@ import { mfmLanguage } from './language';
import { MfmForest } from './prelude'; import { MfmForest } from './prelude';
import { normalize } from './normalize'; import { normalize } from './normalize';
export function parse(source: string): MfmForest { export function parse(source: string | null): MfmForest | null {
if (source == null || source == '') { if (source == null || source == '') {
return null; return null;
} }
@ -10,7 +10,7 @@ export function parse(source: string): MfmForest {
return normalize(mfmLanguage.root.tryParse(source)); return normalize(mfmLanguage.root.tryParse(source));
} }
export function parsePlain(source: string): MfmForest { export function parsePlain(source: string | null): MfmForest | null {
if (source == null || source == '') { if (source == null || source == '') {
return null; return null;
} }

View file

@ -4,7 +4,7 @@ import { intersperse } from '../prelude/array';
import { MfmForest, MfmTree } from './prelude'; import { MfmForest, MfmTree } from './prelude';
import { IMentionedRemoteUsers } from '../models/entities/note'; import { IMentionedRemoteUsers } from '../models/entities/note';
export function toHtml(tokens: MfmForest, mentionedRemoteUsers: IMentionedRemoteUsers = []) { export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
if (tokens == null) { if (tokens == null) {
return null; return null;
} }

View file

@ -1,6 +1,6 @@
type Acct = { type Acct = {
username: string; username: string;
host: string; host: string | null;
}; };
export default Acct; export default Acct;

View file

@ -2,7 +2,7 @@ import config from '../config';
import { toASCII } from 'punycode'; import { toASCII } from 'punycode';
import { URL } from 'url'; import { URL } from 'url';
export function getFullApAccount(username: string, host: string) { export function getFullApAccount(username: string, host: string | null) {
return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`; return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
} }
@ -17,6 +17,5 @@ export function extractDbHost(uri: string) {
} }
export function toPuny(host: string) { export function toPuny(host: string) {
if (host == null) return null;
return toASCII(host.toLowerCase()); return toASCII(host.toLowerCase());
} }

View file

@ -3,7 +3,7 @@ import fileType from 'file-type';
import checkSvg from '../misc/check-svg'; import checkSvg from '../misc/check-svg';
export async function detectMine(path: string) { export async function detectMine(path: string) {
return new Promise<[string, string]>((res, rej) => { return new Promise<[string, string | null]>((res, rej) => {
const readable = fs.createReadStream(path); const readable = fs.createReadStream(path);
readable readable
.on('error', rej) .on('error', rej)

View file

@ -1,5 +1,4 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as URL from 'url';
import * as request from 'request'; import * as request from 'request';
import config from '../config'; import config from '../config';
import chalk from 'chalk'; import chalk from 'chalk';
@ -26,7 +25,7 @@ export async function downloadUrl(url: string, path: string) {
rej(error); rej(error);
}); });
const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url; const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
const req = request({ const req = request({
url: requestUrl, url: requestUrl,

View file

@ -9,7 +9,6 @@ export default async function(): Promise<Meta> {
} else { } else {
return Metas.save({ return Metas.save({
id: genId(), id: genId(),
hiddenTags: []
} as Meta); } as Meta);
} }
} }

View file

@ -1,8 +1,9 @@
import fetchMeta from './fetch-meta'; import fetchMeta from './fetch-meta';
import { ILocalUser } from '../models/entities/user'; import { ILocalUser } from '../models/entities/user';
import { Users } from '../models'; import { Users } from '../models';
import { ensure } from '../prelude/ensure';
export async function fetchProxyAccount(): Promise<ILocalUser> { export async function fetchProxyAccount(): Promise<ILocalUser> {
const meta = await fetchMeta(); const meta = await fetchMeta();
return await Users.findOne({ username: meta.proxyAccount, host: null }) as ILocalUser; return await Users.findOne({ username: meta.proxyAccount!, host: null }).then(ensure) as ILocalUser;
} }

View file

@ -7,7 +7,7 @@ export class IdentifiableError extends Error {
constructor(id: string, message?: string) { constructor(id: string, message?: string) {
super(message); super(message);
this.message = message; this.message = message || '';
this.id = id; this.id = id;
} }
} }

View file

@ -3,7 +3,7 @@ export function nyaize(text: string): string {
// ja-JP // ja-JP
.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ') .replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
// ko-KR // ko-KR
.replace(/[나-낳]/g, (match: string) => String.fromCharCode( .replace(/[나-낳]/g, match => String.fromCharCode(
match.codePointAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0) match.codePointAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0)
)); ));
} }

View file

@ -19,8 +19,8 @@ type MyType<T extends Schema> = {
export type SchemaType<p extends Schema> = export type SchemaType<p extends Schema> =
p['type'] extends 'number' ? number : p['type'] extends 'number' ? number :
p['type'] extends 'string' ? string : p['type'] extends 'string' ? string :
p['type'] extends 'array' ? MyType<p['items']>[] : p['type'] extends 'array' ? MyType<NonNullable<p['items']>>[] :
p['type'] extends 'object' ? ObjType<p['properties']> : p['type'] extends 'object' ? ObjType<NonNullable<p['properties']>> :
any; any;
export function convertOpenApiSchema(schema: Schema) { export function convertOpenApiSchema(schema: Schema) {

View file

@ -67,5 +67,5 @@ export type IPoll = {
choices: string[]; choices: string[];
votes?: number[]; votes?: number[];
multiple: boolean; multiple: boolean;
expiresAt: Date; expiresAt: Date | null;
}; };

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { AbuseUserReport } from '../entities/abuse-user-report'; import { AbuseUserReport } from '../entities/abuse-user-report';
import { ensure } from '../../prelude/ensure';
@EntityRepository(AbuseUserReport) @EntityRepository(AbuseUserReport)
export class AbuseUserReportRepository extends Repository<AbuseUserReport> { export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
@ -14,7 +15,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
public async pack( public async pack(
src: AbuseUserReport['id'] | AbuseUserReport, src: AbuseUserReport['id'] | AbuseUserReport,
) { ) {
const report = typeof src === 'object' ? src : await this.findOne(src); const report = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: report.id, id: report.id,

View file

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { App } from '../entities/app'; import { App } from '../entities/app';
import { AccessTokens } from '..'; import { AccessTokens } from '..';
import { ensure } from '../../prelude/ensure';
@EntityRepository(App) @EntityRepository(App)
export class AppRepository extends Repository<App> { export class AppRepository extends Repository<App> {
@ -19,7 +20,7 @@ export class AppRepository extends Repository<App> {
includeProfileImageIds: false includeProfileImageIds: false
}, options); }, options);
const app = typeof src === 'object' ? src : await this.findOne(src); const app = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return { return {
id: app.id, id: app.id,

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { Apps } from '..'; import { Apps } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { AuthSession } from '../entities/auth-session'; import { AuthSession } from '../entities/auth-session';
import { ensure } from '../../prelude/ensure';
@EntityRepository(AuthSession) @EntityRepository(AuthSession)
export class AuthSessionRepository extends Repository<AuthSession> { export class AuthSessionRepository extends Repository<AuthSession> {
@ -9,7 +10,7 @@ export class AuthSessionRepository extends Repository<AuthSession> {
src: AuthSession['id'] | AuthSession, src: AuthSession['id'] | AuthSession,
me?: any me?: any
) { ) {
const session = typeof src === 'object' ? src : await this.findOne(src); const session = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: session.id, id: session.id,

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { Blocking } from '../entities/blocking'; import { Blocking } from '../entities/blocking';
import { ensure } from '../../prelude/ensure';
@EntityRepository(Blocking) @EntityRepository(Blocking)
export class BlockingRepository extends Repository<Blocking> { export class BlockingRepository extends Repository<Blocking> {
@ -16,7 +17,7 @@ export class BlockingRepository extends Repository<Blocking> {
src: Blocking['id'] | Blocking, src: Blocking['id'] | Blocking,
me?: any me?: any
) { ) {
const blocking = typeof src === 'object' ? src : await this.findOne(src); const blocking = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: blocking.id, id: blocking.id,

View file

@ -4,6 +4,7 @@ import { Users, DriveFolders } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { User } from '../entities/user'; import { User } from '../entities/user';
import { toPuny } from '../../misc/convert-host'; import { toPuny } from '../../misc/convert-host';
import { ensure } from '../../prelude/ensure';
@EntityRepository(DriveFile) @EntityRepository(DriveFile)
export class DriveFileRepository extends Repository<DriveFile> { export class DriveFileRepository extends Repository<DriveFile> {
@ -91,7 +92,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
self: false self: false
}, options); }, options);
const file = typeof src === 'object' ? src : await this.findOne(src); const file = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: file.id, id: file.id,
@ -108,7 +109,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
detail: true detail: true
}) : null, }) : null,
user: opts.withUser ? Users.pack(file.userId) : null user: opts.withUser ? Users.pack(file.userId!) : null
}); });
} }
} }

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { DriveFolders, DriveFiles } from '..'; import { DriveFolders, DriveFiles } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { DriveFolder } from '../entities/drive-folder'; import { DriveFolder } from '../entities/drive-folder';
import { ensure } from '../../prelude/ensure';
@EntityRepository(DriveFolder) @EntityRepository(DriveFolder)
export class DriveFolderRepository extends Repository<DriveFolder> { export class DriveFolderRepository extends Repository<DriveFolder> {
@ -22,7 +23,7 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
detail: false detail: false
}, options); }, options);
const folder = typeof src === 'object' ? src : await this.findOne(src); const folder = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: folder.id, id: folder.id,

View file

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { FollowRequest } from '../entities/follow-request'; import { FollowRequest } from '../entities/follow-request';
import { Users } from '..'; import { Users } from '..';
import { ensure } from '../../prelude/ensure';
@EntityRepository(FollowRequest) @EntityRepository(FollowRequest)
export class FollowRequestRepository extends Repository<FollowRequest> { export class FollowRequestRepository extends Repository<FollowRequest> {
@ -8,7 +9,7 @@ export class FollowRequestRepository extends Repository<FollowRequest> {
src: FollowRequest['id'] | FollowRequest, src: FollowRequest['id'] | FollowRequest,
me?: any me?: any
) { ) {
const request = typeof src === 'object' ? src : await this.findOne(src); const request = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return { return {
id: request.id, id: request.id,

View file

@ -2,9 +2,50 @@ import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { Following } from '../entities/following'; import { Following } from '../entities/following';
import { ensure } from '../../prelude/ensure';
type LocalFollowerFollowing = Following & {
followerHost: null;
followerInbox: null;
followerSharedInbox: null;
};
type RemoteFollowerFollowing = Following & {
followerHost: string;
followerInbox: string;
followerSharedInbox: string;
};
type LocalFolloweeFollowing = Following & {
followeeHost: null;
followeeInbox: null;
followeeSharedInbox: null;
};
type RemoteFolloweeFollowing = Following & {
followeeHost: string;
followeeInbox: string;
followeeSharedInbox: string;
};
@EntityRepository(Following) @EntityRepository(Following)
export class FollowingRepository extends Repository<Following> { export class FollowingRepository extends Repository<Following> {
public isLocalFollower(following: Following): following is LocalFollowerFollowing {
return following.followerHost == null;
}
public isRemoteFollower(following: Following): following is RemoteFollowerFollowing {
return following.followerHost != null;
}
public isLocalFollowee(following: Following): following is LocalFolloweeFollowing {
return following.followeeHost == null;
}
public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing {
return following.followeeHost != null;
}
public packMany( public packMany(
followings: any[], followings: any[],
me?: any, me?: any,
@ -24,7 +65,7 @@ export class FollowingRepository extends Repository<Following> {
populateFollower?: boolean; populateFollower?: boolean;
} }
) { ) {
const following = typeof src === 'object' ? src : await this.findOne(src); const following = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
if (opts == null) opts = {}; if (opts == null) opts = {};

View file

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { Users } from '../../..'; import { Users } from '../../..';
import { ReversiGame } from '../../../entities/games/reversi/game'; import { ReversiGame } from '../../../entities/games/reversi/game';
import { ensure } from '../../../../prelude/ensure';
@EntityRepository(ReversiGame) @EntityRepository(ReversiGame)
export class ReversiGameRepository extends Repository<ReversiGame> { export class ReversiGameRepository extends Repository<ReversiGame> {
@ -15,7 +16,7 @@ export class ReversiGameRepository extends Repository<ReversiGame> {
detail: true detail: true
}, options); }, options);
const game = typeof src === 'object' ? src : await this.findOne(src); const game = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
const meId = me ? typeof me === 'string' ? me : me.id : null; const meId = me ? typeof me === 'string' ? me : me.id : null;
return { return {

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { ReversiMatching } from '../../../entities/games/reversi/matching'; import { ReversiMatching } from '../../../entities/games/reversi/matching';
import { Users } from '../../..'; import { Users } from '../../..';
import { ensure } from '../../../../prelude/ensure';
@EntityRepository(ReversiMatching) @EntityRepository(ReversiMatching)
export class ReversiMatchingRepository extends Repository<ReversiMatching> { export class ReversiMatchingRepository extends Repository<ReversiMatching> {
@ -9,7 +10,7 @@ export class ReversiMatchingRepository extends Repository<ReversiMatching> {
src: ReversiMatching['id'] | ReversiMatching, src: ReversiMatching['id'] | ReversiMatching,
me: any me: any
) { ) {
const matching = typeof src === 'object' ? src : await this.findOne(src); const matching = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: matching.id, id: matching.id,

View file

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { MessagingMessage } from '../entities/messaging-message'; import { MessagingMessage } from '../entities/messaging-message';
import { Users, DriveFiles } from '..'; import { Users, DriveFiles } from '..';
import { ensure } from '../../prelude/ensure';
@EntityRepository(MessagingMessage) @EntityRepository(MessagingMessage)
export class MessagingMessageRepository extends Repository<MessagingMessage> { export class MessagingMessageRepository extends Repository<MessagingMessage> {
@ -19,7 +20,7 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
populateRecipient: true populateRecipient: true
}; };
const message = typeof src === 'object' ? src : await this.findOne(src); const message = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return { return {
id: message.id, id: message.id,

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..'; import { Users } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { Muting } from '../entities/muting'; import { Muting } from '../entities/muting';
import { ensure } from '../../prelude/ensure';
@EntityRepository(Muting) @EntityRepository(Muting)
export class MutingRepository extends Repository<Muting> { export class MutingRepository extends Repository<Muting> {
@ -16,7 +17,7 @@ export class MutingRepository extends Repository<Muting> {
src: Muting['id'] | Muting, src: Muting['id'] | Muting,
me?: any me?: any
) { ) {
const muting = typeof src === 'object' ? src : await this.findOne(src); const muting = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: muting.id, id: muting.id,

View file

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { NoteFavorite } from '../entities/note-favorite'; import { NoteFavorite } from '../entities/note-favorite';
import { Notes } from '..'; import { Notes } from '..';
import { ensure } from '../../prelude/ensure';
@EntityRepository(NoteFavorite) @EntityRepository(NoteFavorite)
export class NoteFavoriteRepository extends Repository<NoteFavorite> { export class NoteFavoriteRepository extends Repository<NoteFavorite> {
@ -15,7 +16,7 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
src: NoteFavorite['id'] | NoteFavorite, src: NoteFavorite['id'] | NoteFavorite,
me?: any me?: any
) { ) {
const favorite = typeof src === 'object' ? src : await this.findOne(src); const favorite = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return { return {
id: favorite.id, id: favorite.id,

View file

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { NoteReaction } from '../entities/note-reaction'; import { NoteReaction } from '../entities/note-reaction';
import { Users } from '..'; import { Users } from '..';
import { ensure } from '../../prelude/ensure';
@EntityRepository(NoteReaction) @EntityRepository(NoteReaction)
export class NoteReactionRepository extends Repository<NoteReaction> { export class NoteReactionRepository extends Repository<NoteReaction> {
@ -8,7 +9,7 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
src: NoteReaction['id'] | NoteReaction, src: NoteReaction['id'] | NoteReaction,
me?: any me?: any
) { ) {
const reaction = typeof src === 'object' ? src : await this.findOne(src); const reaction = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return { return {
id: reaction.id, id: reaction.id,

View file

@ -5,6 +5,7 @@ import { unique, concat } from '../../prelude/array';
import { nyaize } from '../../misc/nyaize'; import { nyaize } from '../../misc/nyaize';
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..'; import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { ensure } from '../../prelude/ensure';
@EntityRepository(Note) @EntityRepository(Note)
export class NoteRepository extends Repository<Note> { export class NoteRepository extends Repository<Note> {
@ -12,7 +13,7 @@ export class NoteRepository extends Repository<Note> {
return x.trim().length <= 100; return x.trim().length <= 100;
} }
private async hideNote(packedNote: any, meId: User['id']) { private async hideNote(packedNote: any, meId: User['id'] | null) {
let hide = false; let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示 // visibility が specified かつ自分が指定されていなかったら非表示
@ -75,7 +76,7 @@ export class NoteRepository extends Repository<Note> {
public packMany( public packMany(
notes: (Note['id'] | Note)[], notes: (Note['id'] | Note)[],
me?: User['id'] | User, me?: User['id'] | User | null | undefined,
options?: { options?: {
detail?: boolean; detail?: boolean;
skipHide?: boolean; skipHide?: boolean;
@ -86,7 +87,7 @@ export class NoteRepository extends Repository<Note> {
public async pack( public async pack(
src: Note['id'] | Note, src: Note['id'] | Note,
me?: User['id'] | User, me?: User['id'] | User | null | undefined,
options?: { options?: {
detail?: boolean; detail?: boolean;
skipHide?: boolean; skipHide?: boolean;
@ -98,11 +99,11 @@ export class NoteRepository extends Repository<Note> {
}, options); }, options);
const meId = me ? typeof me === 'string' ? me : me.id : null; const meId = me ? typeof me === 'string' ? me : me.id : null;
const note = typeof src === 'object' ? src : await this.findOne(src); const note = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
const host = note.userHost; const host = note.userHost;
async function populatePoll() { async function populatePoll() {
const poll = await Polls.findOne({ noteId: note.id }); const poll = await Polls.findOne({ noteId: note.id }).then(ensure);
const choices = poll.choices.map(c => ({ const choices = poll.choices.map(c => ({
text: c, text: c,
votes: poll.votes[poll.choices.indexOf(c)], votes: poll.votes[poll.choices.indexOf(c)],
@ -111,7 +112,7 @@ export class NoteRepository extends Repository<Note> {
if (poll.multiple) { if (poll.multiple) {
const votes = await PollVotes.find({ const votes = await PollVotes.find({
userId: meId, userId: meId!,
noteId: note.id noteId: note.id
}); });
@ -121,7 +122,7 @@ export class NoteRepository extends Repository<Note> {
} }
} else { } else {
const vote = await PollVotes.findOne({ const vote = await PollVotes.findOne({
userId: meId, userId: meId!,
noteId: note.id noteId: note.id
}); });
@ -139,7 +140,7 @@ export class NoteRepository extends Repository<Note> {
async function populateMyReaction() { async function populateMyReaction() {
const reaction = await NoteReactions.findOne({ const reaction = await NoteReactions.findOne({
userId: meId, userId: meId!,
noteId: note.id, noteId: note.id,
}); });

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
import { Users, Notes } from '..'; import { Users, Notes } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { Notification } from '../entities/notification'; import { Notification } from '../entities/notification';
import { ensure } from '../../prelude/ensure';
@EntityRepository(Notification) @EntityRepository(Notification)
export class NotificationRepository extends Repository<Notification> { export class NotificationRepository extends Repository<Notification> {
@ -14,7 +15,7 @@ export class NotificationRepository extends Repository<Notification> {
public async pack( public async pack(
src: Notification['id'] | Notification, src: Notification['id'] | Notification,
) { ) {
const notification = typeof src === 'object' ? src : await this.findOne(src); const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({ return await rap({
id: notification.id, id: notification.id,
@ -23,23 +24,23 @@ export class NotificationRepository extends Repository<Notification> {
userId: notification.notifierId, userId: notification.notifierId,
user: Users.pack(notification.notifier || notification.notifierId), user: Users.pack(notification.notifier || notification.notifierId),
...(notification.type === 'mention' ? { ...(notification.type === 'mention' ? {
note: Notes.pack(notification.note || notification.noteId), note: Notes.pack(notification.note || notification.noteId!),
} : {}), } : {}),
...(notification.type === 'reply' ? { ...(notification.type === 'reply' ? {
note: Notes.pack(notification.note || notification.noteId), note: Notes.pack(notification.note || notification.noteId!),
} : {}), } : {}),
...(notification.type === 'renote' ? { ...(notification.type === 'renote' ? {
note: Notes.pack(notification.note || notification.noteId), note: Notes.pack(notification.note || notification.noteId!),
} : {}), } : {}),
...(notification.type === 'quote' ? { ...(notification.type === 'quote' ? {
note: Notes.pack(notification.note || notification.noteId), note: Notes.pack(notification.note || notification.noteId!),
} : {}), } : {}),
...(notification.type === 'reaction' ? { ...(notification.type === 'reaction' ? {
note: Notes.pack(notification.note || notification.noteId), note: Notes.pack(notification.note || notification.noteId!),
reaction: notification.reaction reaction: notification.reaction
} : {}), } : {}),
...(notification.type === 'pollVote' ? { ...(notification.type === 'pollVote' ? {
note: Notes.pack(notification.note || notification.noteId), note: Notes.pack(notification.note || notification.noteId!),
choice: notification.choice choice: notification.choice
} : {}) } : {})
}); });

View file

@ -1,12 +1,13 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { UserList } from '../entities/user-list'; import { UserList } from '../entities/user-list';
import { ensure } from '../../prelude/ensure';
@EntityRepository(UserList) @EntityRepository(UserList)
export class UserListRepository extends Repository<UserList> { export class UserListRepository extends Repository<UserList> {
public async pack( public async pack(
src: any, src: UserList['id'] | UserList,
) { ) {
const userList = typeof src === 'object' ? src : await this.findOne(src); const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return { return {
id: userList.id, id: userList.id,

View file

@ -2,6 +2,7 @@ import { EntityRepository, Repository, In } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user'; import { User, ILocalUser, IRemoteUser } from '../entities/user';
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..'; import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
import rap from '@prezzemolo/rap'; import rap from '@prezzemolo/rap';
import { ensure } from '../../prelude/ensure';
@EntityRepository(User) @EntityRepository(User)
export class UserRepository extends Repository<User> { export class UserRepository extends Repository<User> {
@ -51,7 +52,7 @@ export class UserRepository extends Repository<User> {
public packMany( public packMany(
users: (User['id'] | User)[], users: (User['id'] | User)[],
me?: User['id'] | User, me?: User['id'] | User | null | undefined,
options?: { options?: {
detail?: boolean, detail?: boolean,
includeSecrets?: boolean, includeSecrets?: boolean,
@ -63,7 +64,7 @@ export class UserRepository extends Repository<User> {
public async pack( public async pack(
src: User['id'] | User, src: User['id'] | User,
me?: User['id'] | User, me?: User['id'] | User | null | undefined,
options?: { options?: {
detail?: boolean, detail?: boolean,
includeSecrets?: boolean, includeSecrets?: boolean,
@ -75,12 +76,12 @@ export class UserRepository extends Repository<User> {
includeSecrets: false includeSecrets: false
}, options); }, options);
const user = typeof src === 'object' ? src : await this.findOne(src); const user = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
const meId = me ? typeof me === 'string' ? me : me.id : null; const meId = me ? typeof me === 'string' ? me : me.id : null;
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }) : null; const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }).then(ensure) : null;
return await rap({ return await rap({
id: user.id, id: user.id,
@ -117,12 +118,12 @@ export class UserRepository extends Repository<User> {
} : {}), } : {}),
...(opts.detail ? { ...(opts.detail ? {
url: profile.url, url: profile!.url,
createdAt: user.createdAt, createdAt: user.createdAt,
updatedAt: user.updatedAt, updatedAt: user.updatedAt,
description: profile.description, description: profile!.description,
location: profile.location, location: profile!.location,
birthday: profile.birthday, birthday: profile!.birthday,
followersCount: user.followersCount, followersCount: user.followersCount,
followingCount: user.followingCount, followingCount: user.followingCount,
notesCount: user.notesCount, notesCount: user.notesCount,
@ -135,9 +136,9 @@ export class UserRepository extends Repository<User> {
...(opts.detail && meId === user.id ? { ...(opts.detail && meId === user.id ? {
avatarId: user.avatarId, avatarId: user.avatarId,
bannerId: user.bannerId, bannerId: user.bannerId,
autoWatch: profile.autoWatch, autoWatch: profile!.autoWatch,
alwaysMarkNsfw: profile.alwaysMarkNsfw, alwaysMarkNsfw: profile!.alwaysMarkNsfw,
carefulBot: profile.carefulBot, carefulBot: profile!.carefulBot,
hasUnreadMessagingMessage: MessagingMessages.count({ hasUnreadMessagingMessage: MessagingMessages.count({
where: { where: {
recipientId: user.id, recipientId: user.id,
@ -158,9 +159,9 @@ export class UserRepository extends Repository<User> {
} : {}), } : {}),
...(opts.includeSecrets ? { ...(opts.includeSecrets ? {
clientData: profile.clientData, clientData: profile!.clientData,
email: profile.email, email: profile!.email,
emailVerified: profile.emailVerified, emailVerified: profile!.emailVerified,
} : {}), } : {}),
...(relation ? { ...(relation ? {

7
src/prelude/ensure.ts Normal file
View file

@ -0,0 +1,7 @@
export function ensure<T>(x: T): NonNullable<T> {
if (x == null) {
throw 'ぬるぽ';
} else {
return x!;
}
}

View file

@ -12,7 +12,7 @@ import { queueLogger } from './logger';
import { DriveFile } from '../models/entities/drive-file'; import { DriveFile } from '../models/entities/drive-file';
function initializeQueue(name: string) { function initializeQueue(name: string) {
return new Queue(name, config.redis != null ? { return new Queue(name, {
redis: { redis: {
port: config.redis.port, port: config.redis.port,
host: config.redis.host, host: config.redis.host,
@ -20,7 +20,7 @@ function initializeQueue(name: string) {
db: config.redis.db || 0, db: config.redis.db || 0,
}, },
prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue' prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue'
} : null); });
} }
export const deliverQueue = initializeQueue('deliver'); export const deliverQueue = initializeQueue('deliver');

View file

@ -10,9 +10,11 @@ const logger = queueLogger.createSubLogger('delete-drive-files');
export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> { export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> {
logger.info(`Deleting drive files of ${job.data.user.id} ...`); logger.info(`Deleting drive files of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
let deletedCount = 0; let deletedCount = 0;
let ended = false; let ended = false;

View file

@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-blocking');
export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting blocking of ${job.data.user.id} ...`); logger.info(`Exporting blocking of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
// Create temp file // Create temp file
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
@ -56,6 +58,10 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
for (const block of blockings) { for (const block of blockings) {
const u = await Users.findOne({ id: block.blockeeId }); const u = await Users.findOne({ id: block.blockeeId });
if (u == null) {
exportedCount++; continue;
}
const content = getFullApAccount(u.username, u.host); const content = getFullApAccount(u.username, u.host);
await new Promise((res, rej) => { await new Promise((res, rej) => {
stream.write(content + '\n', err => { stream.write(content + '\n', err => {

View file

@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-following');
export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting following of ${job.data.user.id} ...`); logger.info(`Exporting following of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
// Create temp file // Create temp file
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
@ -56,6 +58,10 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
for (const following of followings) { for (const following of followings) {
const u = await Users.findOne({ id: following.followeeId }); const u = await Users.findOne({ id: following.followeeId });
if (u == null) {
exportedCount++; continue;
}
const content = getFullApAccount(u.username, u.host); const content = getFullApAccount(u.username, u.host);
await new Promise((res, rej) => { await new Promise((res, rej) => {
stream.write(content + '\n', err => { stream.write(content + '\n', err => {

View file

@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-mute');
export async function exportMute(job: Bull.Job, done: any): Promise<void> { export async function exportMute(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting mute of ${job.data.user.id} ...`); logger.info(`Exporting mute of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
// Create temp file // Create temp file
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
@ -56,6 +58,10 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
for (const mute of mutes) { for (const mute of mutes) {
const u = await Users.findOne({ id: mute.muteeId }); const u = await Users.findOne({ id: mute.muteeId });
if (u == null) {
exportedCount++; continue;
}
const content = getFullApAccount(u.username, u.host); const content = getFullApAccount(u.username, u.host);
await new Promise((res, rej) => { await new Promise((res, rej) => {
stream.write(content + '\n', err => { stream.write(content + '\n', err => {

View file

@ -9,15 +9,18 @@ import { Users, Notes, Polls } from '../../../models';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
import { Note } from '../../../models/entities/note'; import { Note } from '../../../models/entities/note';
import { Poll } from '../../../models/entities/poll'; import { Poll } from '../../../models/entities/poll';
import { ensure } from '../../../prelude/ensure';
const logger = queueLogger.createSubLogger('export-notes'); const logger = queueLogger.createSubLogger('export-notes');
export async function exportNotes(job: Bull.Job, done: any): Promise<void> { export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting notes of ${job.data.user.id} ...`); logger.info(`Exporting notes of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
// Create temp file // Create temp file
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
@ -67,9 +70,9 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
cursor = notes[notes.length - 1].id; cursor = notes[notes.length - 1].id;
for (const note of notes) { for (const note of notes) {
let poll: Poll; let poll: Poll | undefined;
if (note.hasPoll) { if (note.hasPoll) {
poll = await Polls.findOne({ noteId: note.id }); poll = await Polls.findOne({ noteId: note.id }).then(ensure);
} }
const content = JSON.stringify(serialize(note, poll)); const content = JSON.stringify(serialize(note, poll));
await new Promise((res, rej) => { await new Promise((res, rej) => {
@ -114,7 +117,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
done(); done();
} }
function serialize(note: Note, poll: Poll): any { function serialize(note: Note, poll: Poll | null = null): any {
return { return {
id: note.id, id: note.id,
text: note.text, text: note.text,

View file

@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-user-lists');
export async function exportUserLists(job: Bull.Job, done: any): Promise<void> { export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
logger.info(`Exporting user lists of ${job.data.user.id} ...`); logger.info(`Exporting user lists of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
const lists = await UserLists.find({ const lists = await UserLists.find({
userId: user.id userId: user.id

View file

@ -13,13 +13,19 @@ const logger = queueLogger.createSubLogger('import-following');
export async function importFollowing(job: Bull.Job, done: any): Promise<void> { export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
logger.info(`Importing following of ${job.data.user.id} ...`); logger.info(`Importing following of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
const file = await DriveFiles.findOne({ const file = await DriveFiles.findOne({
id: job.data.fileId id: job.data.fileId
}); });
if (file == null) {
done();
return;
}
const csv = await downloadTextFile(file.url); const csv = await downloadTextFile(file.url);
@ -31,11 +37,11 @@ export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
try { try {
const { username, host } = parseAcct(line.trim()); const { username, host } = parseAcct(line.trim());
let target = isSelfHost(host) ? await Users.findOne({ let target = isSelfHost(host!) ? await Users.findOne({
host: null, host: null,
usernameLower: username.toLowerCase() usernameLower: username.toLowerCase()
}) : await Users.findOne({ }) : await Users.findOne({
host: toPuny(host), host: toPuny(host!),
usernameLower: username.toLowerCase() usernameLower: username.toLowerCase()
}); });

View file

@ -14,13 +14,19 @@ const logger = queueLogger.createSubLogger('import-user-lists');
export async function importUserLists(job: Bull.Job, done: any): Promise<void> { export async function importUserLists(job: Bull.Job, done: any): Promise<void> {
logger.info(`Importing user lists of ${job.data.user.id} ...`); logger.info(`Importing user lists of ${job.data.user.id} ...`);
const user = await Users.findOne({ const user = await Users.findOne(job.data.user.id);
id: job.data.user.id if (user == null) {
}); done();
return;
}
const file = await DriveFiles.findOne({ const file = await DriveFiles.findOne({
id: job.data.fileId id: job.data.fileId
}); });
if (file == null) {
done();
return;
}
const csv = await downloadTextFile(file.url); const csv = await downloadTextFile(file.url);
@ -43,22 +49,20 @@ export async function importUserLists(job: Bull.Job, done: any): Promise<void> {
}); });
} }
let target = isSelfHost(host) ? await Users.findOne({ let target = isSelfHost(host!) ? await Users.findOne({
host: null, host: null,
usernameLower: username.toLowerCase() usernameLower: username.toLowerCase()
}) : await Users.findOne({ }) : await Users.findOne({
host: toPuny(host), host: toPuny(host!),
usernameLower: username.toLowerCase() usernameLower: username.toLowerCase()
}); });
if (host == null && target == null) continue;
if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue;
if (target == null) { if (target == null) {
target = await resolveUser(username, host); target = await resolveUser(username, host);
} }
if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue;
pushUserToUserList(target, list); pushUserToUserList(target, list);
} }

View file

@ -7,7 +7,7 @@ import { instanceChart } from '../../services/chart';
const logger = new Logger('deliver'); const logger = new Logger('deliver');
let latest: string = null; let latest: string | null = null;
export default async (job: Bull.Job) => { export default async (job: Bull.Job) => {
const { host } = new URL(job.data.to); const { host } = new URL(job.data.to);

View file

@ -14,6 +14,7 @@ import { UserPublickey } from '../../models/entities/user-publickey';
import fetchMeta from '../../misc/fetch-meta'; import fetchMeta from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host'; import { toPuny } from '../../misc/convert-host';
import { validActor } from '../../remote/activitypub/type'; import { validActor } from '../../remote/activitypub/type';
import { ensure } from '../../prelude/ensure';
const logger = new Logger('inbox'); const logger = new Logger('inbox');
@ -35,7 +36,7 @@ export default async (job: Bull.Job): Promise<void> => {
if (keyIdLower.startsWith('acct:')) { if (keyIdLower.startsWith('acct:')) {
const acct = parseAcct(keyIdLower.slice('acct:'.length)); const acct = parseAcct(keyIdLower.slice('acct:'.length));
const host = toPuny(acct.host); const host = acct.host ? toPuny(acct.host) : null;
const username = toPuny(acct.username); const username = toPuny(acct.username);
if (host === null) { if (host === null) {
@ -64,9 +65,7 @@ export default async (job: Bull.Job): Promise<void> => {
host: host host: host
}) as IRemoteUser; }) as IRemoteUser;
key = await UserPublickeys.findOne({ key = await UserPublickeys.findOne(user.id).then(ensure);
userId: user.id
});
} else { } else {
// アクティビティ内のホストの検証 // アクティビティ内のホストの検証
const host = toPuny(new URL(signature.keyId).hostname); const host = toPuny(new URL(signature.keyId).hostname);
@ -87,7 +86,7 @@ export default async (job: Bull.Job): Promise<void> => {
key = await UserPublickeys.findOne({ key = await UserPublickeys.findOne({
keyId: signature.keyId keyId: signature.keyId
}); }).then(ensure);
user = await Users.findOne(key.userId) as IRemoteUser; user = await Users.findOne(key.userId) as IRemoteUser;
} }

View file

@ -6,9 +6,10 @@ import { Users } from '../../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
if (id == null) throw 'missing id';
if (!id.startsWith(config.url + '/')) { if (!id.startsWith(config.url + '/')) {
return null; return;
} }
const follower = await Users.findOne({ const follower = await Users.findOne({

View file

@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
if (activity.target === actor.featured) { if (activity.target === actor.featured) {
const note = await resolveNote(activity.object); const note = await resolveNote(activity.object);
if (note == null) throw new Error('note not found');
await addPinned(actor, note.id); await addPinned(actor, note.id);
return; return;
} }

View file

@ -53,16 +53,16 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
logger.info(`Creating the (Re)Note: ${uri}`); logger.info(`Creating the (Re)Note: ${uri}`);
//#region Visibility //#region Visibility
const visibility = getVisibility(activity.to, activity.cc, actor); const visibility = getVisibility(activity.to || [], activity.cc || [], actor);
let visibleUsers: User[] = []; let visibleUsers: User[] = [];
if (visibility == 'specified') { if (visibility == 'specified') {
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); visibleUsers = await Promise.all((note.to || []).map(uri => resolvePerson(uri)));
} }
//#endergion //#endergion
await post(actor, { await post(actor, {
createdAt: new Date(activity.published), createdAt: activity.published ? new Date(activity.published) : null,
renote, renote,
visibility, visibility,
visibleUsers, visibleUsers,
@ -75,9 +75,6 @@ type visibility = 'public' | 'home' | 'followers' | 'specified';
function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility { function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility {
const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'; const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
to = to || [];
cc = cc || [];
if (to.includes(PUBLIC)) { if (to.includes(PUBLIC)) {
return 'public'; return 'public';
} else if (cc.includes(PUBLIC)) { } else if (cc.includes(PUBLIC)) {

View file

@ -9,13 +9,14 @@ const logger = apLogger;
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (id == null) throw 'missing id';
const uri = activity.id || activity; const uri = activity.id || activity;
logger.info(`Block: ${uri}`); logger.info(`Block: ${uri}`);
if (!id.startsWith(config.url + '/')) { if (!id.startsWith(config.url + '/')) {
return null; return;
} }
const blockee = await Users.findOne(id.split('/').pop()); const blockee = await Users.findOne(id.split('/').pop());

View file

@ -6,9 +6,10 @@ import { Users } from '../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (id == null) throw 'missing id';
if (!id.startsWith(config.url + '/')) { if (!id.startsWith(config.url + '/')) {
return null; return;
} }
const followee = await Users.findOne(id.split('/').pop()); const followee = await Users.findOne(id.split('/').pop());

View file

@ -71,7 +71,7 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
default: default:
apLogger.warn(`unknown activity type: ${(activity as any).type}`); apLogger.warn(`unknown activity type: ${(activity as any).type}`);
return null; return;
} }
}; };

View file

@ -5,6 +5,7 @@ import { Notes } from '../../../models';
export default async (actor: IRemoteUser, activity: ILike) => { export default async (actor: IRemoteUser, activity: ILike) => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (id == null) throw 'missing id';
// Transform: // Transform:
// https://misskey.ex/notes/xxxx to // https://misskey.ex/notes/xxxx to

View file

@ -6,9 +6,10 @@ import { Users } from '../../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
if (id == null) throw 'missing id';
if (!id.startsWith(config.url + '/')) { if (!id.startsWith(config.url + '/')) {
return null; return;
} }
const follower = await Users.findOne(id.split('/').pop()); const follower = await Users.findOne(id.split('/').pop());

View file

@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
if (activity.target === actor.featured) { if (activity.target === actor.featured) {
const note = await resolveNote(activity.object); const note = await resolveNote(activity.object);
if (note == null) throw new Error('note not found');
await removePinned(actor, note.id); await removePinned(actor, note.id);
return; return;
} }

View file

@ -9,13 +9,14 @@ const logger = apLogger;
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (id == null) throw 'missing id';
const uri = activity.id || activity; const uri = activity.id || activity;
logger.info(`UnBlock: ${uri}`); logger.info(`UnBlock: ${uri}`);
if (!id.startsWith(config.url + '/')) { if (!id.startsWith(config.url + '/')) {
return null; return;
} }
const blockee = await Users.findOne(id.split('/').pop()); const blockee = await Users.findOne(id.split('/').pop());

View file

@ -7,9 +7,10 @@ import { Users, FollowRequests, Followings } from '../../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => { export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (id == null) throw 'missing id';
if (!id.startsWith(config.url + '/')) { if (!id.startsWith(config.url + '/')) {
return null; return;
} }
const followee = await Users.findOne(id.split('/').pop()); const followee = await Users.findOne(id.split('/').pop());

View file

@ -39,6 +39,4 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
undoLike(actor, object as ILike); undoLike(actor, object as ILike);
break; break;
} }
return null;
}; };

View file

@ -8,6 +8,7 @@ import { Notes } from '../../../../models';
*/ */
export default async (actor: IRemoteUser, activity: ILike): Promise<void> => { export default async (actor: IRemoteUser, activity: ILike): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id; const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
if (id == null) throw 'missing id';
const noteId = id.split('/').pop(); const noteId = id.split('/').pop();

View file

@ -5,6 +5,7 @@ import fetchMeta from '../../../misc/fetch-meta';
import { apLogger } from '../logger'; import { apLogger } from '../logger';
import { DriveFile } from '../../../models/entities/drive-file'; import { DriveFile } from '../../../models/entities/drive-file';
import { DriveFiles } from '../../../models'; import { DriveFiles } from '../../../models';
import { ensure } from '../../../prelude/ensure';
const logger = apLogger; const logger = apLogger;
@ -14,7 +15,7 @@ const logger = apLogger;
export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> { export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
// 投稿者が凍結されていたらスキップ // 投稿者が凍結されていたらスキップ
if (actor.isSuspended) { if (actor.isSuspended) {
return null; throw new Error('actor has been suspended');
} }
const image = await new Resolver().resolve(value) as any; 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 instance = await fetchMeta();
const cache = instance.cacheRemoteFiles; const cache = instance.cacheRemoteFiles;
let file; let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache);
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;
}
if (file.isLink) { if (file.isLink) {
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
@ -49,7 +40,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
uri: image.url uri: image.url
}); });
file = DriveFiles.findOne(file.id); file = await DriveFiles.findOne(file.id).then(ensure);
} }
} }

View file

@ -21,6 +21,7 @@ import { IObject, INote } from '../type';
import { Emoji } from '../../../models/entities/emoji'; import { Emoji } from '../../../models/entities/emoji';
import { genId } from '../../../misc/gen-id'; import { genId } from '../../../misc/gen-id';
import fetchMeta from '../../../misc/fetch-meta'; import fetchMeta from '../../../misc/fetch-meta';
import { ensure } from '../../../prelude/ensure';
const logger = apLogger; const logger = apLogger;
@ -29,13 +30,14 @@ const logger = apLogger;
* *
* Misskeyに対象のNoteが登録されていればそれを返します * 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; const uri = typeof value == 'string' ? value : value.id;
if (uri == null) throw 'missing uri';
// URIがこのサーバーを指しているならデータベースからフェッチ // URIがこのサーバーを指しているならデータベースからフェッチ
if (uri.startsWith(config.url + '/')) { if (uri.startsWith(config.url + '/')) {
const id = uri.split('/').pop(); const id = uri.split('/').pop();
return await Notes.findOne(id); return await Notes.findOne(id).then(x => x || null);
} }
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す
@ -52,7 +54,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P
/** /**
* Noteを作成します * 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(); if (resolver == null) resolver = new Resolver();
const object: any = await resolver.resolve(value); const object: any = await resolver.resolve(value);
@ -65,7 +67,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
value: value, value: value,
object: object object: object
}); });
return null; throw 'invalid note';
} }
const note: INote = object; 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}`); 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) { if (actor.isSuspended) {
return null; throw 'actor has been suspended';
} }
//#region Visibility //#region Visibility
@ -95,9 +97,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
visibility = 'followers'; visibility = 'followers';
} else { } else {
visibility = 'specified'; 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 //#endergion
const apMentions = await extractMentionedUsers(actor, note.to, note.cc, resolver); 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 => { ? await resolveNote(note.inReplyTo, resolver).catch(e => {
// 4xxの場合はリプライしてないことにする // 4xxの場合はリプライしてないことにする
if (e.statusCode >= 400 && e.statusCode < 500) { if (e.statusCode >= 400 && e.statusCode < 500) {
@ -131,7 +133,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
: null; : null;
// 引用 // 引用
let quote: Note; let quote: Note | undefined | null;
if (note._misskey_quote && typeof note._misskey_quote == 'string') { if (note._misskey_quote && typeof note._misskey_quote == 'string') {
quote = await resolveNote(note._misskey_quote).catch(e => { quote = await resolveNote(note._misskey_quote).catch(e => {
@ -152,7 +154,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
// vote // vote
if (reply && reply.hasPoll) { 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> => { const tryCreateVote = async (name: string, index: number): Promise<null> => {
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { 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}`); 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}`); logger.info(`extractEmojis: ${e}`);
return [] as Emoji[]; return [] as Emoji[];
}); });
@ -196,7 +199,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
} }
return await post(actor, { return await post(actor, {
createdAt: new Date(note.published), createdAt: note.published ? new Date(note.published) : null,
files, files,
reply, reply,
renote: quote, renote: quote,
@ -223,8 +226,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
* Misskeyに対象のNoteが登録されていればそれを返し * Misskeyに対象のNoteが登録されていればそれを返し
* Misskeyに登録しそれを返します * 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; const uri = typeof value == 'string' ? value : value.id;
if (uri == null) throw 'missing uri';
// ブロックしてたら中断 // ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
@ -244,75 +248,79 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
return await createNote(uri, resolver).catch(e => { return await createNote(uri, resolver).catch(e => {
if (e.name === 'duplicated') { if (e.name === 'duplicated') {
return fetchNote(uri); return fetchNote(uri).then(note => {
if (note == null) {
throw 'something happened';
} else {
return note;
}
});
} else { } else {
throw e; throw e;
} }
}); });
} }
export async function extractEmojis(tags: ITag[], host: string) { export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {
host = toPuny(host); host = toPuny(host);
if (!tags) return []; 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( return await Promise.all(eomjiTags.map(async tag => {
eomjiTags.map(async tag => { const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
const name = tag.name.replace(/^:/, '').replace(/:$/, '');
const exists = await Emojis.findOne({ const exists = await Emojis.findOne({
host, host,
name name
}); });
if (exists) { if (exists) {
if ((tag.updated != null && exists.updatedAt == null) if ((tag.updated != null && exists.updatedAt == null)
|| (tag.id != null && exists.uri == null) || (tag.id != null && exists.uri == null)
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
) { ) {
await Emojis.update({ await Emojis.update({
host, host,
name, name,
}, { }, {
uri: tag.id, uri: tag.id,
url: tag.icon.url, url: tag.icon!.url,
updatedAt: new Date(tag.updated), updatedAt: new Date(tag.updated!),
}); });
return await Emojis.findOne({ return await Emojis.findOne({
host, host,
name name
}); }) as Emoji;
}
return exists;
} }
logger.info(`register emoji host=${host}, name=${name}`); return exists;
}
return await Emojis.save({ logger.info(`register emoji host=${host}, name=${name}`);
id: genId(),
host, return await Emojis.save({
name, id: genId(),
uri: tag.id, host,
url: tag.icon.url, name,
updatedAt: tag.updated ? new Date(tag.updated) : undefined, uri: tag.id,
aliases: [] url: tag.icon!.url,
} as Emoji); updatedAt: tag.updated ? new Date(tag.updated) : undefined,
}) aliases: []
); } as Partial<Emoji>);
}));
} }
async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) { async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) {
const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`]; const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`];
const uris = difference(unique(concat([to || [], cc || []])), ignoreUris); const uris = difference(unique(concat([to || [], cc || []])), ignoreUris);
const limit = promiseLimit(2); const limit = promiseLimit<User | null>(2);
const users = await Promise.all( 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[];
} }

View file

@ -26,6 +26,7 @@ import { toPuny } from '../../../misc/convert-host';
import { UserProfile } from '../../../models/entities/user-profile'; import { UserProfile } from '../../../models/entities/user-profile';
import { validActor } from '../../../remote/activitypub/type'; import { validActor } from '../../../remote/activitypub/type';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { ensure } from '../../../prelude/ensure';
const logger = apLogger; const logger = apLogger;
/** /**
@ -86,13 +87,13 @@ function validatePerson(x: any, uri: string) {
* *
* Misskeyに対象のPersonが登録されていればそれを返します * 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'; if (typeof uri !== 'string') throw 'uri is not string';
// URIがこのサーバーを指しているならデータベースからフェッチ // URIがこのサーバーを指しているならデータベースからフェッチ
if (uri.startsWith(config.url + '/')) { if (uri.startsWith(config.url + '/')) {
const id = uri.split('/').pop(); const id = uri.split('/').pop();
return await Users.findOne(id); return await Users.findOne(id).then(x => x || null);
} }
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す
@ -128,7 +129,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
const host = toPuny(new URL(object.id).hostname); 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()); 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({ await transactionalEntityManager.save(new UserProfile({
userId: user.id, userId: user.id,
description: fromHtml(person.summary), description: person.summary ? fromHtml(person.summary) : null,
url: person.url, url: person.url,
fields, fields,
userHost: host userHost: host
@ -189,20 +190,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
instanceChart.newUser(i.host); 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 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 (user!.tags || []).filter(x => !tags.includes(x))) updateHashtag(user!, tag, true, false);
//#region アイコンとヘッダー画像をフェッチ //#region アイコンとヘッダー画像をフェッチ
const [avatar, banner] = (await Promise.all<DriveFile>([ const [avatar, banner] = (await Promise.all<DriveFile | null>([
person.icon, person.icon,
person.image person.image
].map(img => ].map(img =>
img == null img == null
? Promise.resolve(null) ? Promise.resolve(null)
: resolveImage(user, img).catch(() => null) : resolveImage(user!, img).catch(() => null)
))); )));
const avatarId = avatar ? avatar.id : 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 avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar) : null;
const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null; const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null;
const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : 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, avatarId,
bannerId, bannerId,
avatarUrl, avatarUrl,
@ -221,30 +222,30 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
bannerColor bannerColor
}); });
user.avatarId = avatarId; user!.avatarId = avatarId;
user.bannerId = bannerId; user!.bannerId = bannerId;
user.avatarUrl = avatarUrl; user!.avatarUrl = avatarUrl;
user.bannerUrl = bannerUrl; user!.bannerUrl = bannerUrl;
user.avatarColor = avatarColor; user!.avatarColor = avatarColor;
user.bannerColor = bannerColor; user!.bannerColor = bannerColor;
//#endregion //#endregion
//#region カスタム絵文字取得 //#region カスタム絵文字取得
const emojis = await extractEmojis(person.tag, host).catch(e => { const emojis = await extractEmojis(person.tag || [], host).catch(e => {
logger.info(`extractEmojis: ${e}`); logger.info(`extractEmojis: ${e}`);
return [] as Emoji[]; return [] as Emoji[];
}); });
const emojiNames = emojis.map(emoji => emoji.name); const emojiNames = emojis.map(emoji => emoji.name);
await Users.update(user.id, { await Users.update(user!.id, {
emojis: emojiNames emojis: emojiNames
}); });
//#endregion //#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 resolver Resolver
* @param hint Hint of Person object (Personの場合Remote resolveをせずに更新に利用します) * @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'; if (typeof uri !== 'string') throw 'uri is not string';
// URIがこのサーバーを指しているならスキップ // URIがこのサーバーを指しているならスキップ
@ -290,7 +291,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
logger.info(`Updating the Person: ${person.id}`); 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.icon,
person.image person.image
].map(img => ].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}`); logger.info(`extractEmojis: ${e}`);
return [] as Emoji[]; return [] as Emoji[];
}); });
const emojiNames = emojis.map(emoji => emoji.name); 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()); 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), sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
featured: person.featured, featured: person.featured,
emojis: emojiNames, emojis: emojiNames,
description: fromHtml(person.summary), description: person.summary ? fromHtml(person.summary) : null,
name: person.name, name: person.name,
url: person.url, url: person.url,
endpoints: person.endpoints, endpoints: person.endpoints,
@ -326,7 +327,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
isBot: object.type == 'Service', isBot: object.type == 'Service',
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
isLocked: person.manuallyApprovesFollowers, isLocked: person.manuallyApprovesFollowers,
createdAt: new Date(Date.parse(person.published)) || null,
} as Partial<User>; } as Partial<User>;
if (avatar) { if (avatar) {
@ -379,7 +379,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
* Misskeyに対象のPersonが登録されていればそれを返し * Misskeyに対象のPersonが登録されていればそれを返し
* Misskeyに登録しそれを返します * 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'; if (typeof uri !== 'string') throw 'uri is not string';
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す
@ -439,21 +439,24 @@ export function analyzeAttachments(attachments: ITag[]) {
}[] = []; }[] = [];
const services: { [x: string]: any } = {}; const services: { [x: string]: any } = {};
if (Array.isArray(attachments)) if (Array.isArray(attachments)) {
for (const attachment of attachments.filter(isPropertyValue)) for (const attachment of attachments.filter(isPropertyValue)) {
if (isPropertyValue(attachment.identifier)) if (isPropertyValue(attachment.identifier!)) {
addService(services, attachment.identifier); addService(services, attachment.identifier!);
else } else {
fields.push({ fields.push({
name: attachment.name, name: attachment.name!,
value: fromHtml(attachment.value) value: fromHtml(attachment.value!)
}); });
}
}
}
return { fields, services }; return { fields, services };
} }
export async function updateFeatured(userId: User['id']) { 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 (!Users.isRemoteUser(user)) return;
if (!user.featured) 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`); if (!Array.isArray(items)) throw new Error(`Collection items is not an array`);
// Resolve and regist Notes // Resolve and regist Notes
const limit = promiseLimit(2); const limit = promiseLimit<Note | null>(2);
const featuredNotes = await Promise.all(items const featuredNotes = await Promise.all(items
.filter(item => item.type === 'Note') .filter(item => item.type === 'Note')
.slice(0, 5) .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)) { for (const note of featuredNotes.filter(note => note != null)) {
UserNotePinings.save({ UserNotePinings.save({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
userId: user.id, userId: user.id,
noteId: note.id noteId: note!.id
} as UserNotePining); } as UserNotePining);
} }
} }

View file

@ -14,10 +14,10 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi
throw 'invalid question'; throw 'invalid question';
} }
const choices = question[multiple ? 'anyOf' : 'oneOf'] const choices = question[multiple ? 'anyOf' : 'oneOf']!
.map((x, i) => x.name); .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); .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0);
return { return {
@ -60,7 +60,7 @@ export async function updateQuestion(value: any) {
for (const choice of poll.choices) { for (const choice of poll.choices) {
const oldCount = poll.votes[poll.choices.indexOf(choice)]; 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) { if (oldCount != newCount) {
changed = true; changed = true;

View file

@ -14,13 +14,13 @@ export type ITag = {
identifier?: IIdentifier; identifier?: IIdentifier;
}; };
export function extractHashtags(tags: ITag[]) { export function extractHashtags(tags: ITag[] | null | undefined): string[] {
if (!tags) return []; if (tags == null) return [];
const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string'); const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string');
return hashtags.map(tag => { return hashtags.map(tag => {
const m = tag.name.match(/^#(.+)/); const m = tag.name ? tag.name.match(/^#(.+)/) : null;
return m ? m[1] : null; return m ? m[1] : null;
}).filter(x => x != null); }).filter(x => x != null) as string[];
} }

View file

@ -1,7 +1,7 @@
import config from '../../../config'; import config from '../../../config';
import { ILocalUser, IRemoteUser } from '../../../models/entities/user'; import { ILocalUser, IRemoteUser } from '../../../models/entities/user';
export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({ export default (blocker: ILocalUser, blockee: IRemoteUser) => ({
type: 'Block', type: 'Block',
actor: `${config.url}/users/${blocker.id}`, actor: `${config.url}/users/${blocker.id}`,
object: blockee.uri object: blockee.uri

View file

@ -1,12 +1,13 @@
import config from '../../../config'; import config from '../../../config';
import { Users } from '../../../models'; import { Users } from '../../../models';
import { User } from '../../../models/entities/user'; import { User } from '../../../models/entities/user';
import { ensure } from '../../../prelude/ensure';
/** /**
* Convert (local|remote)(Follower|Followee)ID to URL * Convert (local|remote)(Follower|Followee)ID to URL
* @param id Follower|Followee ID * @param id Follower|Followee ID
*/ */
export default async function renderFollowUser(id: User['id']): Promise<any> { 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; return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri;
} }

View file

@ -10,6 +10,7 @@ import { DriveFiles, Notes, Users, Emojis, Polls } from '../../../models';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { Emoji } from '../../../models/entities/emoji'; import { Emoji } from '../../../models/entities/emoji';
import { Poll } from '../../../models/entities/poll'; import { Poll } from '../../../models/entities/poll';
import { ensure } from '../../../prelude/ensure';
export default async function renderNote(note: Note, dive = true): Promise<any> { export default async function renderNote(note: Note, dive = true): Promise<any> {
const promisedFiles: Promise<DriveFile[]> = note.fileIds.length > 0 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([]); : Promise.resolve([]);
let inReplyTo; let inReplyTo;
let inReplyToNote: Note; let inReplyToNote: Note | undefined;
if (note.replyId) { if (note.replyId) {
inReplyToNote = await Notes.findOne(note.replyId); inReplyToNote = await Notes.findOne(note.replyId);
if (inReplyToNote !== null) { if (inReplyToNote != null) {
const inReplyToUser = await Users.findOne(inReplyToNote.userId); const inReplyToUser = await Users.findOne(inReplyToNote.userId);
if (inReplyToUser !== null) { if (inReplyToUser != null) {
if (inReplyToNote.uri) { if (inReplyToNote.uri) {
inReplyTo = inReplyToNote.uri; inReplyTo = inReplyToNote.uri;
} else { } else {
@ -51,9 +52,7 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
} }
} }
const user = await Users.findOne({ const user = await Users.findOne(note.userId).then(ensure);
id: note.userId
});
const attributedTo = `${config.url}/users/${user.id}`; 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; const files = await promisedFiles;
let text = note.text; let text = note.text;
let poll: Poll; let poll: Poll | undefined;
if (note.hasPoll) { if (note.hasPoll) {
poll = await Polls.findOne({ noteId: note.id }); poll = await Polls.findOne({ noteId: note.id });
} }
let question: string; let question: string | undefined;
if (poll) { if (poll) {
if (text == null) text = ''; if (text == null) text = '';
const url = `${config.url}/notes/${note.id}`; const url = `${config.url}/notes/${note.id}`;
@ -144,7 +143,7 @@ export default async function renderNote(note: Note, dive = true): Promise<any>
name: text, name: text,
replies: { replies: {
type: 'Collection', 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[];
} }

View file

@ -7,7 +7,7 @@
* @param prev URL of prev page (optional) * @param prev URL of prev page (optional)
* @param next URL of next 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 = { const page = {
id, id,
partOf, partOf,

View file

@ -9,14 +9,15 @@ import renderEmoji from './emoji';
import { IIdentifier } from '../models/identifier'; import { IIdentifier } from '../models/identifier';
import renderHashtag from './hashtag'; import renderHashtag from './hashtag';
import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models'; import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models';
import { ensure } from '../../../prelude/ensure';
export async function renderPerson(user: ILocalUser) { export async function renderPerson(user: ILocalUser) {
const id = `${config.url}/users/${user.id}`; const id = `${config.url}/users/${user.id}`;
const [avatar, banner, profile] = await Promise.all([ const [avatar, banner, profile] = await Promise.all([
DriveFiles.findOne(user.avatarId), user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined),
DriveFiles.findOne(user.bannerId), user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined),
UserProfiles.findOne({ userId: user.id }) UserProfiles.findOne({ userId: user.id }).then(ensure)
]); ]);
const attachment: { const attachment: {
@ -76,9 +77,7 @@ export async function renderPerson(user: ILocalUser) {
...hashtagTags, ...hashtagTags,
]; ];
const keypair = await UserKeypairs.findOne({ const keypair = await UserKeypairs.findOne(user.id).then(ensure);
userId: user.id
});
return { return {
type: user.isBot ? 'Service' : 'Person', type: user.isBot ? 'Service' : 'Person',
@ -94,8 +93,8 @@ export async function renderPerson(user: ILocalUser) {
preferredUsername: user.username, preferredUsername: user.username,
name: user.name, name: user.name,
summary: toHtml(parse(profile.description)), summary: toHtml(parse(profile.description)),
icon: user.avatarId && renderImage(avatar), icon: avatar ? renderImage(avatar) : null,
image: user.bannerId && renderImage(banner), image: banner ? renderImage(banner) : null,
tag, tag,
manuallyApprovesFollowers: user.isLocked, manuallyApprovesFollowers: user.isLocked,
publicKey: renderKey(user, keypair), publicKey: renderKey(user, keypair),

View file

@ -12,6 +12,7 @@ import { apLogger } from './logger';
import { UserKeypairs } from '../../models'; import { UserKeypairs } from '../../models';
import fetchMeta from '../../misc/fetch-meta'; import fetchMeta from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host'; import { toPuny } from '../../misc/convert-host';
import { ensure } from '../../prelude/ensure';
export const logger = apLogger.createSubLogger('deliver'); export const logger = apLogger.createSubLogger('deliver');
@ -38,7 +39,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
const keypair = await UserKeypairs.findOne({ const keypair = await UserKeypairs.findOne({
userId: user.id userId: user.id
}); }).then(ensure);
const _ = new Promise((resolve, reject) => { const _ = new Promise((resolve, reject) => {
const req = request({ const req = request({
@ -56,7 +57,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
'Digest': `SHA-256=${hash}` 'Digest': `SHA-256=${hash}`
} }
}, res => { }, res => {
if (res.statusCode >= 400) { if (res.statusCode! >= 400) {
logger.warn(`${url} --> ${res.statusCode}`); logger.warn(`${url} --> ${res.statusCode}`);
reject(res); reject(res);
} else { } else {
@ -73,7 +74,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
}); });
// Signature: Signature ... => Signature: ... // Signature: Signature ... => Signature: ...
let sig = req.getHeader('Signature').toString(); let sig = req.getHeader('Signature')!.toString();
sig = sig.replace(/^Signature /, ''); sig = sig.replace(/^Signature /, '');
req.setHeader('Signature', sig); req.setHeader('Signature', sig);
@ -112,7 +113,7 @@ async function resolveAddr(domain: string) {
function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> { function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> {
return new Promise((res, rej) => { return new Promise((res, rej) => {
lookup(domain, options, (error: any, address: string | string[]) => { lookup(domain, options, (error, address) => {
if (error) return rej(error); if (error) return rej(error);
return res(Array.isArray(address) ? address[0] : address); return res(Array.isArray(address) ? address[0] : address);
}); });

View file

@ -10,18 +10,31 @@ import { toPuny } from '../misc/convert-host';
const logger = remoteLogger.createSubLogger('resolve-user'); 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(); const usernameLower = username.toLowerCase();
host = toPuny(host);
if (host == null) { if (host == null) {
logger.info(`return local user: ${usernameLower}`); 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) { if (config.host == host) {
logger.info(`return local user: ${usernameLower}`); 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); 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); await updatePerson(self.href);
logger.info(`return resynced remote user: ${acctLower}`); 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}`); 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 }`); 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 }`); 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) { if (!self) {
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`);
throw new Error('self link not found'); throw new Error('self link not found');

View file

@ -5,7 +5,7 @@ import { query as urlQuery } from '../prelude/url';
type ILink = { type ILink = {
href: string; href: string;
rel: string; rel?: string;
}; };
type IWebFinger = { type IWebFinger = {

View file

@ -17,6 +17,7 @@ import { isSelfHost } from '../misc/convert-host';
import { Notes, Users, Emojis, UserKeypairs, Polls } from '../models'; import { Notes, Users, Emojis, UserKeypairs, Polls } from '../models';
import { ILocalUser, User } from '../models/entities/user'; import { ILocalUser, User } from '../models/entities/user';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { ensure } from '../prelude/ensure';
// Init router // Init router
const router = new Router(); const router = new Router();
@ -123,8 +124,8 @@ router.get('/questions/:question', async (ctx, next) => {
return; return;
} }
const user = await Users.findOne(pollNote.userId); const user = await Users.findOne(pollNote.userId).then(ensure);
const poll = await Polls.findOne({ noteId: pollNote.id }); const poll = await Polls.findOne({ noteId: pollNote.id }).then(ensure);
ctx.body = renderActivity(await renderQuestion(user as ILocalUser, pollNote, poll)); ctx.body = renderActivity(await renderQuestion(user as ILocalUser, pollNote, poll));
setResponseType(ctx); setResponseType(ctx);
@ -156,9 +157,7 @@ router.get('/users/:user/publickey', async ctx => {
return; return;
} }
const keypair = await UserKeypairs.findOne({ const keypair = await UserKeypairs.findOne(user.id).then(ensure);
userId: user.id
});
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user)) {
ctx.body = renderActivity(renderKey(user, keypair)); ctx.body = renderActivity(renderKey(user, keypair));
@ -189,7 +188,7 @@ router.get('/users/:user', async (ctx, next) => {
const user = await Users.findOne({ const user = await Users.findOne({
id: userId, id: userId,
host: null host: null
}); }).then(ensure);
await userInfo(ctx, user); await userInfo(ctx, user);
}); });
@ -200,7 +199,7 @@ router.get('/@:user', async (ctx, next) => {
const user = await Users.findOne({ const user = await Users.findOne({
usernameLower: ctx.params.user.toLowerCase(), usernameLower: ctx.params.user.toLowerCase(),
host: null host: null
}); }).then(ensure);
await userInfo(ctx, user); await userInfo(ctx, user);
}); });

View file

@ -5,6 +5,7 @@ import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-c
import { setResponseType } from '../activitypub'; import { setResponseType } from '../activitypub';
import renderNote from '../../remote/activitypub/renderer/note'; import renderNote from '../../remote/activitypub/renderer/note';
import { Users, Notes, UserNotePinings } from '../../models'; import { Users, Notes, UserNotePinings } from '../../models';
import { ensure } from '../../prelude/ensure';
export default async (ctx: Router.IRouterContext) => { export default async (ctx: Router.IRouterContext) => {
const userId = ctx.params.user; const userId = ctx.params.user;
@ -22,13 +23,14 @@ export default async (ctx: Router.IRouterContext) => {
const pinings = await UserNotePinings.find({ userId: user.id }); const pinings = await UserNotePinings.find({ userId: user.id });
const pinnedNotes = await Promise.all(pinings.map(pining => Notes.findOne(pining.noteId))); const pinnedNotes = await Promise.all(pinings.map(pining =>
Notes.findOne(pining.noteId).then(ensure)));
const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note)));
const rendered = renderOrderedCollection( const rendered = renderOrderedCollection(
`${config.url}/users/${userId}/collections/featured`, `${config.url}/users/${userId}/collections/featured`,
renderedNotes.length, null, null, renderedNotes renderedNotes.length, undefined, undefined, renderedNotes
); );
ctx.body = renderActivity(rendered); ctx.body = renderActivity(rendered);

View file

@ -69,18 +69,18 @@ export default async (ctx: Router.IRouterContext) => {
cursor cursor
})}`, })}`,
user.followersCount, renderedFollowers, partOf, user.followersCount, renderedFollowers, partOf,
null, undefined,
inStock ? `${partOf}?${url.query({ inStock ? `${partOf}?${url.query({
page: 'true', page: 'true',
cursor: followings[followings.length - 1].id cursor: followings[followings.length - 1].id
})}` : null })}` : undefined
); );
ctx.body = renderActivity(rendered); ctx.body = renderActivity(rendered);
setResponseType(ctx); setResponseType(ctx);
} else { } else {
// index page // index page
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null); const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
ctx.body = renderActivity(rendered); ctx.body = renderActivity(rendered);
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
setResponseType(ctx); setResponseType(ctx);

View file

@ -70,18 +70,18 @@ export default async (ctx: Router.IRouterContext) => {
cursor cursor
})}`, })}`,
user.followingCount, renderedFollowees, partOf, user.followingCount, renderedFollowees, partOf,
null, undefined,
inStock ? `${partOf}?${url.query({ inStock ? `${partOf}?${url.query({
page: 'true', page: 'true',
cursor: followings[followings.length - 1].id cursor: followings[followings.length - 1].id
})}` : null })}` : undefined
); );
ctx.body = renderActivity(rendered); ctx.body = renderActivity(rendered);
setResponseType(ctx); setResponseType(ctx);
} else { } else {
// index page // index page
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null); const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
ctx.body = renderActivity(rendered); ctx.body = renderActivity(rendered);
ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
setResponseType(ctx); setResponseType(ctx);

View file

@ -15,6 +15,7 @@ import { Users, Notes } from '../../models';
import { makePaginationQuery } from '../api/common/make-pagination-query'; import { makePaginationQuery } from '../api/common/make-pagination-query';
import { Brackets } from 'typeorm'; import { Brackets } from 'typeorm';
import { Note } from '../../models/entities/note'; import { Note } from '../../models/entities/note';
import { ensure } from '../../prelude/ensure';
export default async (ctx: Router.IRouterContext) => { export default async (ctx: Router.IRouterContext) => {
const userId = ctx.params.user; const userId = ctx.params.user;
@ -73,11 +74,11 @@ export default async (ctx: Router.IRouterContext) => {
notes.length ? `${partOf}?${url.query({ notes.length ? `${partOf}?${url.query({
page: 'true', page: 'true',
since_id: notes[0].id since_id: notes[0].id
})}` : null, })}` : undefined,
notes.length ? `${partOf}?${url.query({ notes.length ? `${partOf}?${url.query({
page: 'true', page: 'true',
until_id: notes[notes.length - 1].id until_id: notes[notes.length - 1].id
})}` : null })}` : undefined
); );
ctx.body = renderActivity(rendered); ctx.body = renderActivity(rendered);
@ -99,9 +100,9 @@ export default async (ctx: Router.IRouterContext) => {
* Pack Create<Note> or Announce Activity * Pack Create<Note> or Announce Activity
* @param note Note * @param note Note
*/ */
export async function packActivity(note: Note): Promise<object> { export async function packActivity(note: Note): Promise<any> {
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) {
const renote = await Notes.findOne(note.renoteId); const renote = await Notes.findOne(note.renoteId).then(ensure);
return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note);
} }

View file

@ -15,11 +15,11 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res)
ctx.status = x; ctx.status = x;
ctx.body = { ctx.body = {
error: { error: {
message: y.message, message: y!.message,
code: y.code, code: y!.code,
id: y.id, id: y!.id,
kind: y.kind, kind: y!.kind,
...(y.info ? { info: y.info } : {}) ...(y!.info ? { info: y!.info } : {})
} }
}; };
} else { } else {
@ -31,9 +31,9 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res)
// Authentication // Authentication
authenticate(body['i']).then(([user, app]) => { authenticate(body['i']).then(([user, app]) => {
// API invoking // API invoking
call(endpoint.name, user, app, body, (ctx.req as any).file).then(res => { call(endpoint.name, user, app, body, (ctx.req as any).file).then((res: any) => {
reply(res); reply(res);
}).catch(e => { }).catch((e: ApiError) => {
reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e); reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e);
}); });
}).catch(() => { }).catch(() => {

View file

@ -3,7 +3,7 @@ import { User } from '../../models/entities/user';
import { App } from '../../models/entities/app'; import { App } from '../../models/entities/app';
import { Users, AccessTokens, Apps } from '../../models'; import { Users, AccessTokens, Apps } from '../../models';
export default async (token: string): Promise<[User, App]> => { export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => {
if (token == null) { if (token == null) {
return [null, null]; return [null, null];
} }

View file

@ -12,7 +12,7 @@ const accessDenied = {
id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e' id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e'
}; };
export default async (endpoint: string, user: User, app: App, data: any, file?: any) => { export default async (endpoint: string, user: User | null | undefined, app: App | null | undefined, data: any, file?: any) => {
const isSecure = user != null && app == null; const isSecure = user != null && app == null;
const ep = endpoints.find(e => e.name === endpoint); const ep = endpoints.find(e => e.name === endpoint);
@ -39,15 +39,15 @@ export default async (endpoint: string, user: User, app: App, data: any, file?:
}); });
} }
if (ep.meta.requireCredential && user.isSuspended) { if (ep.meta.requireCredential && user!.isSuspended) {
throw new ApiError(accessDenied, { reason: 'Your account has been suspended.' }); throw new ApiError(accessDenied, { reason: 'Your account has been suspended.' });
} }
if (ep.meta.requireAdmin && !user.isAdmin) { if (ep.meta.requireAdmin && !user!.isAdmin) {
throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
} }
if (ep.meta.requireModerator && !user.isAdmin && !user.isModerator) { if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) {
throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); throw new ApiError(accessDenied, { reason: 'You are not a moderator.' });
} }
@ -61,7 +61,7 @@ export default async (endpoint: string, user: User, app: App, data: any, file?:
if (ep.meta.requireCredential && ep.meta.limit) { if (ep.meta.requireCredential && ep.meta.limit) {
// Rate limit // Rate limit
await limiter(ep, user).catch(e => { await limiter(ep, user!).catch(e => {
throw new ApiError({ throw new ApiError({
message: 'Rate limit exceeded. Please try again later.', message: 'Rate limit exceeded. Please try again later.',
code: 'RATE_LIMIT_EXCEEDED', code: 'RATE_LIMIT_EXCEEDED',

View file

@ -1,6 +1,6 @@
import { SelectQueryBuilder } from 'typeorm'; import { SelectQueryBuilder } from 'typeorm';
export function makePaginationQuery<T>(q: SelectQueryBuilder<T>, sinceId: string, untilId: string, sinceDate?: number, untilDate?: number) { export function makePaginationQuery<T>(q: SelectQueryBuilder<T>, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number) {
if (sinceId && untilId) { if (sinceId && untilId) {
q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId });
q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId });

View file

@ -5,9 +5,9 @@ import { ApiError } from './error';
import { App } from '../../models/entities/app'; import { App } from '../../models/entities/app';
type Params<T extends IEndpointMeta> = { type Params<T extends IEndpointMeta> = {
[P in keyof T['params']]: T['params'][P]['transform'] extends Function [P in keyof T['params']]: NonNullable<T['params']>[P]['transform'] extends Function
? ReturnType<T['params'][P]['transform']> ? ReturnType<NonNullable<T['params']>[P]['transform']>
: ReturnType<T['params'][P]['validator']['get']>[0]; : ReturnType<NonNullable<T['params']>[P]['validator']['get']>[0];
}; };
export type Response = Record<string, any> | void; export type Response = Record<string, any> | void;
@ -34,11 +34,11 @@ export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T
}; };
} }
function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, ApiError] { function getParams<T extends IEndpointMeta>(defs: T, params: any): [Params<T>, ApiError | null] {
if (defs.params == null) return [params, null]; if (defs.params == null) return [params, null];
const x: any = {}; const x: any = {};
let err: ApiError = null; let err: ApiError | null = null;
Object.entries(defs.params).some(([k, def]) => { Object.entries(defs.params).some(([k, def]) => {
const [v, e] = def.validator.get(params[k]); const [v, e] = def.validator.get(params[k]);
if (e) { if (e) {

View file

@ -29,7 +29,7 @@ export const meta = {
export default define(meta, async (ps) => { export default define(meta, async (ps) => {
const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId);
const reports = await query.take(ps.limit).getMany(); const reports = await query.take(ps.limit!).getMany();
return await AbuseUserReports.packMany(reports); return await AbuseUserReports.packMany(reports);
}); });

View file

@ -56,8 +56,8 @@ export default define(meta, async (ps, me) => {
const files = await DriveFiles.find({ const files = await DriveFiles.find({
where: q, where: q,
take: ps.limit, take: ps.limit!,
order: sort[ps.sort] || sort[fallback], order: sort[ps.sort!] || sort[fallback],
skip: ps.offset skip: ps.offset
}); });

View file

@ -23,7 +23,7 @@ export const meta = {
export default define(meta, async (ps) => { export default define(meta, async (ps) => {
const emojis = await Emojis.find({ const emojis = await Emojis.find({
host: toPuny(ps.host) host: ps.host ? toPuny(ps.host) : null
}); });
return emojis.map(e => ({ return emojis.map(e => ({

View file

@ -2,6 +2,7 @@ import $ from 'cafy';
import define from '../../../define'; import define from '../../../define';
import deleteFollowing from '../../../../../services/following/delete'; import deleteFollowing from '../../../../../services/following/delete';
import { Followings, Users } from '../../../../../models'; import { Followings, Users } from '../../../../../models';
import { ensure } from '../../../../../prelude/ensure';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -22,13 +23,11 @@ export default define(meta, async (ps, me) => {
}); });
const pairs = await Promise.all(followings.map(f => Promise.all([ const pairs = await Promise.all(followings.map(f => Promise.all([
Users.findOne(f.followerId), Users.findOne(f.followerId).then(ensure),
Users.findOne(f.followeeId) Users.findOne(f.followeeId).then(ensure)
]))); ])));
for (const pair of pairs) { for (const pair of pairs) {
deleteFollowing(pair[0], pair[1]); deleteFollowing(pair[0], pair[1]);
} }
return;
}); });

View file

@ -65,7 +65,7 @@ export default define(meta, async (ps) => {
} }
} }
const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit).getMany(); const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit!).getMany();
return logs; return logs;
}); });

View file

@ -28,9 +28,9 @@ export default define(meta, async (ps) => {
const queue = const queue =
ps.domain === 'deliver' ? deliverQueue : ps.domain === 'deliver' ? deliverQueue :
ps.domain === 'inbox' ? inboxQueue : ps.domain === 'inbox' ? inboxQueue :
null; null as never;
const jobs = await queue.getJobs([ps.state], 0, ps.limit); const jobs = await queue.getJobs([ps.state], 0, ps.limit!);
return jobs.map(job => ({ return jobs.map(job => ({
id: job.id, id: job.id,

View file

@ -82,7 +82,7 @@ export default define(meta, async (ps, me) => {
default: query.orderBy('user.id', 'ASC'); break; default: query.orderBy('user.id', 'ASC'); break;
} }
query.take(ps.limit); query.take(ps.limit!);
query.skip(ps.offset); query.skip(ps.offset);
const users = await query.getMany(); const users = await query.getMany();

View file

@ -28,5 +28,5 @@ export const meta = {
export default define(meta, async (ps) => { export default define(meta, async (ps) => {
const user = await getRemoteUser(ps.userId); const user = await getRemoteUser(ps.userId);
await updatePerson(user.uri); await updatePerson(user.uri!);
}); });

View file

@ -123,14 +123,14 @@ async function fetchAny(uri: string) {
const note = await createNote(object.id); const note = await createNote(object.id);
return { return {
type: 'Note', type: 'Note',
object: await Notes.pack(note, null, { detail: true }) object: await Notes.pack(note!, null, { detail: true })
}; };
} }
return null; return null;
} }
async function mergePack(user: User, note: Note) { async function mergePack(user: User | null | undefined, note: Note | null | undefined) {
if (user != null) { if (user != null) {
return { return {
type: 'User', type: 'User',

View file

@ -5,6 +5,7 @@ import define from '../../define';
import { ApiError } from '../../error'; import { ApiError } from '../../error';
import { AuthSessions, AccessTokens, Apps } from '../../../../models'; import { AuthSessions, AccessTokens, Apps } from '../../../../models';
import { genId } from '../../../../misc/gen-id'; import { genId } from '../../../../misc/gen-id';
import { ensure } from '../../../../prelude/ensure';
export const meta = { export const meta = {
tags: ['auth'], tags: ['auth'],
@ -48,7 +49,7 @@ export default define(meta, async (ps, user) => {
if (exist == null) { if (exist == null) {
// Lookup app // Lookup app
const app = await Apps.findOne(session.appId); const app = await Apps.findOne(session.appId).then(ensure);
// Generate Hash // Generate Hash
const sha256 = crypto.createHash('sha256'); const sha256 = crypto.createHash('sha256');

View file

@ -2,6 +2,7 @@ import $ from 'cafy';
import define from '../../../define'; import define from '../../../define';
import { ApiError } from '../../../error'; import { ApiError } from '../../../error';
import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models'; import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models';
import { ensure } from '../../../../../prelude/ensure';
export const meta = { export const meta = {
tags: ['auth'], tags: ['auth'],
@ -90,7 +91,7 @@ export default define(meta, async (ps) => {
const accessToken = await AccessTokens.findOne({ const accessToken = await AccessTokens.findOne({
appId: app.id, appId: app.id,
userId: session.userId userId: session.userId
}); }).then(ensure);
// Delete session // Delete session
AuthSessions.delete(session.id); AuthSessions.delete(session.id);

View file

@ -44,7 +44,7 @@ export default define(meta, async (ps, me) => {
.andWhere(`blocking.blockerId = :meId`, { meId: me.id }); .andWhere(`blocking.blockerId = :meId`, { meId: me.id });
const blockings = await query const blockings = await query
.take(ps.limit) .take(ps.limit!)
.getMany(); .getMany();
return await Blockings.packMany(blockings, me); return await Blockings.packMany(blockings, me);

View file

@ -33,5 +33,5 @@ export const meta = {
}; };
export default define(meta, async (ps) => { export default define(meta, async (ps) => {
return await activeUsersChart.getChart(ps.span as any, ps.limit); return await activeUsersChart.getChart(ps.span as any, ps.limit!);
}); });

Some files were not shown because too many files have changed in this diff Show more