Merge branch 'misskey-dev:develop' into sonic

This commit is contained in:
Kainoa Kanter 2022-04-03 17:01:19 -07:00 committed by GitHub
commit 79b9e6d4ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 428 additions and 206 deletions

View file

@ -14,12 +14,12 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- uses: actions/setup-node@v1 - uses: actions/setup-node@v3
with: with:
node-version: 16.x node-version: 16.x
- uses: actions/cache@v2 cache: 'yarn'
with: cache-dependency-path: |
path: '**/node_modules' packages/backend/yarn.lock
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} packages/client/yarn.lock
- run: yarn install - run: yarn install
- run: yarn lint - run: yarn lint

View file

@ -33,9 +33,13 @@ jobs:
with: with:
submodules: true submodules: true
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache-dependency-path: |
packages/backend/yarn.lock
packages/client/yarn.lock
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
- name: Check yarn.lock - name: Check yarn.lock
@ -80,13 +84,13 @@ jobs:
#- uses: browser-actions/setup-firefox@latest #- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }} # if: ${{ matrix.browser == 'firefox' }}
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- uses: actions/cache@v2 cache: 'yarn'
with: cache-dependency-path: |
path: '**/node_modules' packages/backend/yarn.lock
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} packages/client/yarn.lock
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
- name: Check yarn.lock - name: Check yarn.lock

View file

@ -10,10 +10,22 @@
You should also include the user name that made the change. You should also include the user name that made the change.
--> -->
## 12.109.1 (2022/04/02) ## 12.x.x (unreleased)
### Known issues ### Improvements
- two-factor authentication is not working - Improve webhook @syuilo
### Bugfixes
-
## 12.109.2 (2022/04/03)
### Bugfixes
- API: admin/update-meta was not working @syuilo
- Client: テーマを切り替えたり読み込んだりするとmeta[name="theme-color"]のcontentがundefinedになる問題を修正 @tamaina
## 12.109.1 (2022/04/02)
### Bugfixes ### Bugfixes
- API: Renoteが行えない問題を修正 - API: Renoteが行えない問題を修正

View file

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "12.109.1", "version": "12.109.2",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -209,7 +209,11 @@ export const db = new DataSource({
}); });
export async function initDb() { export async function initDb() {
if (db.isInitialized) {
// nop
} else {
await db.connect(); await db.connect();
}
} }
export async function resetDb() { export async function resetDb() {

View file

@ -42,7 +42,8 @@ async function getCaptchaResponse(url: string, secret: string, response: string)
headers: { headers: {
'User-Agent': config.userAgent, 'User-Agent': config.userAgent,
}, },
timeout: 10 * 1000, // TODO
//timeout: 10 * 1000,
agent: getAgentByUrl, agent: getAgentByUrl,
}).catch(e => { }).catch(e => {
throw `${e.message || e}`; throw `${e.message || e}`;

View file

@ -120,9 +120,9 @@ export const httpsAgent = config.proxy
*/ */
export function getAgentByUrl(url: URL, bypassProxy = false) { export function getAgentByUrl(url: URL, bypassProxy = false) {
if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) { if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) {
return url.protocol == 'http:' ? _http : _https; return url.protocol === 'http:' ? _http : _https;
} else { } else {
return url.protocol == 'http:' ? httpAgent : httpsAgent; return url.protocol === 'http:' ? httpAgent : httpsAgent;
} }
} }

View file

@ -23,7 +23,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
} }
// ファイルが添付されているとき // ファイルが添付されているとき
if ((note.files || []).length != 0) { if ((note.files || []).length !== 0) {
summary += ` (📎${note.files!.length})`; summary += ` (📎${note.files!.length})`;
} }

View file

@ -27,6 +27,7 @@ export const packedEmojiSchema = {
host: { host: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
description: 'The local host is represented with `null`.',
}, },
url: { url: {
type: 'string', type: 'string',

View file

@ -21,6 +21,7 @@ export const packedUserLiteSchema = {
type: 'string', type: 'string',
nullable: true, optional: false, nullable: true, optional: false,
example: 'misskey.example.com', example: 'misskey.example.com',
description: 'The local host is represented with `null`.',
}, },
avatarUrl: { avatarUrl: {
type: 'string', type: 'string',

View file

@ -1,4 +1,5 @@
import httpSignature from 'http-signature'; import httpSignature from 'http-signature';
import { v4 as uuid } from 'uuid';
import config from '@/config/index.js'; import config from '@/config/index.js';
import { envOption } from '../env.js'; import { envOption } from '../env.js';
@ -16,7 +17,7 @@ import { getJobInfo } from './get-job-info.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js'; import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js';
import { ThinUser } from './types.js'; import { ThinUser } from './types.js';
import { IActivity } from '@/remote/activitypub/type.js'; import { IActivity } from '@/remote/activitypub/type.js';
import { Webhook } from '@/models/entities/webhook.js'; import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js';
function renderError(e: Error): any { function renderError(e: Error): any {
return { return {
@ -262,12 +263,16 @@ export function createCleanRemoteFilesJob() {
}); });
} }
export function webhookDeliver(webhook: Webhook, content: unknown) { export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) {
const data = { const data = {
type,
content, content,
webhookId: webhook.id, webhookId: webhook.id,
userId: webhook.userId,
to: webhook.url, to: webhook.url,
secret: webhook.secret, secret: webhook.secret,
createdAt: Date.now(),
eventId: uuid(),
}; };
return webhookDeliverQueue.add(data, { return webhookDeliverQueue.add(data, {

View file

@ -8,13 +8,9 @@ import config from '@/config/index.js';
const logger = new Logger('webhook'); const logger = new Logger('webhook');
let latest: string | null = null;
export default async (job: Bull.Job<WebhookDeliverJobData>) => { export default async (job: Bull.Job<WebhookDeliverJobData>) => {
try { try {
if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) { logger.debug(`delivering ${job.data.webhookId}`);
logger.debug(`delivering ${latest}`);
}
const res = await getResponse({ const res = await getResponse({
url: job.data.to, url: job.data.to,
@ -25,7 +21,14 @@ export default async (job: Bull.Job<WebhookDeliverJobData>) => {
'X-Misskey-Hook-Id': job.data.webhookId, 'X-Misskey-Hook-Id': job.data.webhookId,
'X-Misskey-Hook-Secret': job.data.secret, 'X-Misskey-Hook-Secret': job.data.secret,
}, },
body: JSON.stringify(job.data.content), body: JSON.stringify({
hookId: job.data.webhookId,
userId: job.data.userId,
eventId: job.data.eventId,
createdAt: job.data.createdAt,
type: job.data.type,
body: job.data.content,
}),
}); });
Webhooks.update({ id: job.data.webhookId }, { Webhooks.update({ id: job.data.webhookId }, {

View file

@ -48,10 +48,14 @@ export type EndedPollNotificationJobData = {
}; };
export type WebhookDeliverJobData = { export type WebhookDeliverJobData = {
type: string;
content: unknown; content: unknown;
webhookId: Webhook['id']; webhookId: Webhook['id'];
userId: User['id'];
to: string; to: string;
secret: string; secret: string;
createdAt: number;
eventId: string;
}; };
export type ThinUser = { export type ThinUser = {

View file

@ -95,7 +95,7 @@ function genSigningString(request: Request, includeHeaders: string[]) {
function lcObjectKey(src: Record<string, string>) { function lcObjectKey(src: Record<string, string>) {
const dst: Record<string, string> = {}; const dst: Record<string, string> = {};
for (const key of Object.keys(src).filter(x => x != '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key]; for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
return dst; return dst;
} }

View file

@ -109,15 +109,15 @@ export default class DeliverManager {
} }
} }
this.recipes.filter((recipe): recipe is IDirectRecipe => { this.recipes.filter((recipe): recipe is IDirectRecipe =>
// followers recipes have already been processed // followers recipes have already been processed
isDirect(recipe) isDirect(recipe)
// check that shared inbox has not been added yet // check that shared inbox has not been added yet
&& !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox))
// check that they actually have an inbox // check that they actually have an inbox
&& recipe.to.inbox && recipe.to.inbox != null,
}) )
.forEach(recipe => inboxes.add(recipe.to.inbox)); .forEach(recipe => inboxes.add(recipe.to.inbox!));
// deliver // deliver
for (const inbox of inboxes) { for (const inbox of inboxes) {

View file

@ -18,7 +18,7 @@ export const performReadActivity = async (actor: CacheableRemoteUser, activity:
return `skip: message not found`; return `skip: message not found`;
} }
if (actor.id != message.recipientId) { if (actor.id !== message.recipientId) {
return `skip: actor is not a message recipient`; return `skip: actor is not a message recipient`;
} }

View file

@ -1,6 +1,6 @@
import unfollow from '@/services/following/delete.js'; import unfollow from '@/services/following/delete.js';
import cancelRequest from '@/services/following/requests/cancel.js'; import cancelRequest from '@/services/following/requests/cancel.js';
import {IAccept} from '../../type.js'; import { IAccept } from '../../type.js';
import { CacheableRemoteUser } from '@/models/entities/user.js'; import { CacheableRemoteUser } from '@/models/entities/user.js';
import { Followings } from '@/models/index.js'; import { Followings } from '@/models/index.js';
import DbResolver from '../../db-resolver.js'; import DbResolver from '../../db-resolver.js';

View file

@ -113,7 +113,8 @@ export class LdSignature {
headers: { headers: {
Accept: 'application/ld+json, application/json', Accept: 'application/ld+json, application/json',
}, },
timeout: this.loderTimeout, // TODO
//timeout: this.loderTimeout,
agent: u => u.protocol === 'http:' ? httpAgent : httpsAgent, agent: u => u.protocol === 'http:' ? httpAgent : httpsAgent,
}).then(res => { }).then(res => {
if (!res.ok) { if (!res.ok) {

View file

@ -69,7 +69,7 @@ export async function updateQuestion(value: any) {
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;
poll.votes[poll.choices.indexOf(choice)] = newCount; poll.votes[poll.choices.indexOf(choice)] = newCount;
} }

View file

@ -15,7 +15,7 @@ type IWebFinger = {
export default async function(query: string): Promise<IWebFinger> { export default async function(query: string): Promise<IWebFinger> {
const url = genUrl(query); const url = genUrl(query);
return await getJson(url, 'application/jrd+json, application/json'); return await getJson(url, 'application/jrd+json, application/json') as IWebFinger;
} }
function genUrl(query: string) { function genUrl(query: string) {

View file

@ -121,14 +121,14 @@ export function verifyLogin({
signature: Buffer, signature: Buffer,
challenge: string challenge: string
}) { }) {
if (clientData.type != 'webauthn.get') { if (clientData.type !== 'webauthn.get') {
throw new Error('type is not webauthn.get'); throw new Error('type is not webauthn.get');
} }
if (hash(clientData.challenge).toString('hex') != challenge) { if (hash(clientData.challenge).toString('hex') !== challenge) {
throw new Error('challenge mismatch'); throw new Error('challenge mismatch');
} }
if (clientData.origin != config.scheme + '://' + config.host) { if (clientData.origin !== config.scheme + '://' + config.host) {
throw new Error('origin mismatch'); throw new Error('origin mismatch');
} }
@ -148,11 +148,11 @@ export const procedures = {
verify({ publicKey }: {publicKey: Map<number, Buffer>}) { verify({ publicKey }: {publicKey: Map<number, Buffer>}) {
const negTwo = publicKey.get(-2); const negTwo = publicKey.get(-2);
if (!negTwo || negTwo.length != 32) { if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given'); throw new Error('invalid or no -2 key given');
} }
const negThree = publicKey.get(-3); const negThree = publicKey.get(-3);
if (!negThree || negThree.length != 32) { if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given'); throw new Error('invalid or no -3 key given');
} }
@ -183,7 +183,7 @@ export const procedures = {
rpIdHash: Buffer, rpIdHash: Buffer,
credentialId: Buffer, credentialId: Buffer,
}) { }) {
if (attStmt.alg != -7) { if (attStmt.alg !== -7) {
throw new Error('alg mismatch'); throw new Error('alg mismatch');
} }
@ -196,11 +196,11 @@ export const procedures = {
const negTwo = publicKey.get(-2); const negTwo = publicKey.get(-2);
if (!negTwo || negTwo.length != 32) { if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given'); throw new Error('invalid or no -2 key given');
} }
const negThree = publicKey.get(-3); const negThree = publicKey.get(-3);
if (!negThree || negThree.length != 32) { if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given'); throw new Error('invalid or no -3 key given');
} }
@ -263,7 +263,7 @@ export const procedures = {
.map((key: any) => PEMString(key)) .map((key: any) => PEMString(key))
.concat([GSR2]); .concat([GSR2]);
if (getCertSubject(certificateChain[0]).CN != 'attest.android.com') { if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') {
throw new Error('invalid common name'); throw new Error('invalid common name');
} }
@ -283,11 +283,11 @@ export const procedures = {
const negTwo = publicKey.get(-2); const negTwo = publicKey.get(-2);
if (!negTwo || negTwo.length != 32) { if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given'); throw new Error('invalid or no -2 key given');
} }
const negThree = publicKey.get(-3); const negThree = publicKey.get(-3);
if (!negThree || negThree.length != 32) { if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given'); throw new Error('invalid or no -3 key given');
} }
@ -332,11 +332,11 @@ export const procedures = {
const negTwo = publicKey.get(-2); const negTwo = publicKey.get(-2);
if (!negTwo || negTwo.length != 32) { if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given'); throw new Error('invalid or no -2 key given');
} }
const negThree = publicKey.get(-3); const negThree = publicKey.get(-3);
if (!negThree || negThree.length != 32) { if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given'); throw new Error('invalid or no -3 key given');
} }
@ -353,7 +353,7 @@ export const procedures = {
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
throw new Error('ECDAA-Verify is not supported'); throw new Error('ECDAA-Verify is not supported');
} else { } else {
if (attStmt.alg != -7) throw new Error('alg mismatch'); if (attStmt.alg !== -7) throw new Error('alg mismatch');
throw new Error('self attestation is not supported'); throw new Error('self attestation is not supported');
} }
@ -377,7 +377,7 @@ export const procedures = {
credentialId: Buffer credentialId: Buffer
}) { }) {
const x5c: Buffer[] = attStmt.x5c; const x5c: Buffer[] = attStmt.x5c;
if (x5c.length != 1) { if (x5c.length !== 1) {
throw new Error('x5c length does not match expectation'); throw new Error('x5c length does not match expectation');
} }
@ -387,11 +387,11 @@ export const procedures = {
const negTwo: Buffer = publicKey.get(-2); const negTwo: Buffer = publicKey.get(-2);
if (!negTwo || negTwo.length != 32) { if (!negTwo || negTwo.length !== 32) {
throw new Error('invalid or no -2 key given'); throw new Error('invalid or no -2 key given');
} }
const negThree: Buffer = publicKey.get(-3); const negThree: Buffer = publicKey.get(-3);
if (!negThree || negThree.length != 32) { if (!negThree || negThree.length !== 32) {
throw new Error('invalid or no -3 key given'); throw new Error('invalid or no -3 key given');
} }

View file

@ -27,7 +27,12 @@ export const paramDef = {
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) }, type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
hostname: { type: 'string', nullable: true, default: null }, hostname: {
type: 'string',
nullable: true,
default: null,
description: 'The local host is represented with `null`.',
},
}, },
required: [], required: [],
} as const; } as const;

View file

@ -40,6 +40,7 @@ export const meta = {
userHost: { userHost: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
description: 'The local host is represented with `null`.',
}, },
md5: { md5: {
type: 'string', type: 'string',
@ -151,11 +152,20 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
anyOf: [
{
properties: { properties: {
fileId: { type: 'string', format: 'misskey:id' }, fileId: { type: 'string', format: 'misskey:id' },
},
required: ['fileId'],
},
{
properties: {
url: { type: 'string' }, url: { type: 'string' },
}, },
required: [], required: ['url'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -40,6 +40,7 @@ export const meta = {
host: { host: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
description: 'The local host is represented with `null`.',
}, },
url: { url: {
type: 'string', type: 'string',
@ -54,7 +55,12 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
query: { type: 'string', nullable: true, default: null }, query: { type: 'string', nullable: true, default: null },
host: { type: 'string', nullable: true, default: null }, host: {
type: 'string',
nullable: true,
default: null,
description: 'Use `null` to represent the local host.',
},
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },

View file

@ -38,8 +38,9 @@ export const meta = {
optional: false, nullable: true, optional: false, nullable: true,
}, },
host: { host: {
type: 'string', type: 'null',
optional: false, nullable: true, optional: false,
description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.',
}, },
url: { url: {
type: 'string', type: 'string',

View file

@ -17,7 +17,11 @@ export const paramDef = {
ids: { type: 'array', items: { ids: { type: 'array', items: {
type: 'string', format: 'misskey:id', type: 'string', format: 'misskey:id',
} }, } },
category: { type: 'string', nullable: true }, category: {
type: 'string',
nullable: true,
description: 'Use `null` to reset the category.',
},
}, },
required: ['ids'], required: ['ids'],
} as const; } as const;

View file

@ -23,7 +23,11 @@ export const paramDef = {
properties: { properties: {
id: { type: 'string', format: 'misskey:id' }, id: { type: 'string', format: 'misskey:id' },
name: { type: 'string' }, name: { type: 'string' },
category: { type: 'string', nullable: true }, category: {
type: 'string',
nullable: true,
description: 'Use `null` to reset the category.',
},
aliases: { type: 'array', items: { aliases: { type: 'array', items: {
type: 'string', type: 'string',
} }, } },

View file

@ -26,8 +26,13 @@ export const paramDef = {
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" }, state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
username: { type: 'string', default: null }, username: { type: 'string', nullable: true, default: null },
hostname: { type: 'string', default: null }, hostname: {
type: 'string',
nullable: true,
default: null,
description: 'The local host is represented with `null`.',
},
}, },
required: [], required: [],
} as const; } as const;

View file

@ -397,12 +397,14 @@ export default define(meta, paramDef, async (ps, me) => {
} }
await db.transaction(async transactionalEntityManager => { await db.transaction(async transactionalEntityManager => {
const meta = await transactionalEntityManager.findOne(Meta, { const metas = await transactionalEntityManager.find(Meta, {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
}); });
const meta = metas[0];
if (meta) { if (meta) {
await transactionalEntityManager.update(Meta, meta.id, set); await transactionalEntityManager.update(Meta, meta.id, set);
} else { } else {

View file

@ -20,7 +20,7 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
name: { type: 'string', minLength: 1, maxLength: 100 }, name: { type: 'string', minLength: 1, maxLength: 100 },
isPublic: { type: 'boolean' }, isPublic: { type: 'boolean', default: false },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
}, },
required: ['name'], required: ['name'],

View file

@ -48,7 +48,6 @@ export const paramDef = {
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
// @ts-ignore
export default define(meta, paramDef, async (ps, user, _, file, cleanup) => { export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
// Get 'name' parameter // Get 'name' parameter
let name = ps.name || file.originalname; let name = ps.name || file.originalname;

View file

@ -28,22 +28,25 @@ export const meta = {
code: 'ACCESS_DENIED', code: 'ACCESS_DENIED',
id: '25b73c73-68b1-41d0-bad1-381cfdf6579f', id: '25b73c73-68b1-41d0-bad1-381cfdf6579f',
}, },
fileIdOrUrlRequired: {
message: 'fileId or url required.',
code: 'INVALID_PARAM',
id: '89674805-722c-440c-8d88-5641830dc3e4',
},
}, },
} as const; } as const;
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
anyOf: [
{
properties: { properties: {
fileId: { type: 'string', format: 'misskey:id' }, fileId: { type: 'string', format: 'misskey:id' },
},
required: ['fileId'],
},
{
properties: {
url: { type: 'string' }, url: { type: 'string' },
}, },
required: [], required: ['url'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@ -62,8 +65,6 @@ export default define(meta, paramDef, async (ps, user) => {
thumbnailUrl: ps.url, thumbnailUrl: ps.url,
}], }],
}); });
} else {
throw new ApiError(meta.errors.fileIdOrUrlRequired);
} }
if (file == null) { if (file == null) {

View file

@ -22,7 +22,7 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
host: { type: 'string', nullable: true }, host: { type: 'string', nullable: true, description: 'Omit or use `null` to not filter by host.' },
blocked: { type: 'boolean', nullable: true }, blocked: { type: 'boolean', nullable: true },
notResponding: { type: 'boolean', nullable: true }, notResponding: { type: 'boolean', nullable: true },
suspended: { type: 'boolean', nullable: true }, suspended: { type: 'boolean', nullable: true },

View file

@ -50,10 +50,10 @@ export default define(meta, paramDef, async (ps, user) => {
const clientData = JSON.parse(ps.clientDataJSON); const clientData = JSON.parse(ps.clientDataJSON);
if (clientData.type != 'webauthn.create') { if (clientData.type !== 'webauthn.create') {
throw new Error('not a creation attestation'); throw new Error('not a creation attestation');
} }
if (clientData.origin != config.scheme + '://' + config.host) { if (clientData.origin !== config.scheme + '://' + config.host) {
throw new Error('origin mismatch'); throw new Error('origin mismatch');
} }
@ -78,7 +78,7 @@ export default define(meta, paramDef, async (ps, user) => {
const credentialId = authData.slice(55, 55 + credentialIdLength); const credentialId = authData.slice(55, 55 + credentialIdLength);
const publicKeyData = authData.slice(55 + credentialIdLength); const publicKeyData = authData.slice(55 + credentialIdLength);
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData); const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
if (publicKey.get(3) != -7) { if (publicKey.get(3) !== -7) {
throw new Error('alg mismatch'); throw new Error('alg mismatch');
} }

View file

@ -27,7 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
take: ps.limit, take: ps.limit,
skip: ps.offset, skip: ps.offset,
order: { order: {
id: ps.sort == 'asc' ? 1 : -1, id: ps.sort === 'asc' ? 1 : -1,
}, },
}); });

View file

@ -47,14 +47,25 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' },
groupId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },
markAsRead: { type: 'boolean', default: true }, markAsRead: { type: 'boolean', default: true },
}, },
required: [], anyOf: [
{
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
},
{
properties: {
groupId: { type: 'string', format: 'misskey:id' },
},
required: ['groupId'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@ -126,7 +137,5 @@ export default define(meta, paramDef, async (ps, user) => {
return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
populateGroup: false, populateGroup: false,
}))); })));
} else {
throw new Error();
} }
}); });

View file

@ -67,12 +67,23 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' },
groupId: { type: 'string', format: 'misskey:id' },
text: { type: 'string', nullable: true, maxLength: 3000 }, text: { type: 'string', nullable: true, maxLength: 3000 },
fileId: { type: 'string', format: 'misskey:id' }, fileId: { type: 'string', format: 'misskey:id' },
}, },
required: [], anyOf: [
{
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
},
{
properties: {
groupId: { type: 'string', format: 'misskey:id' },
},
required: ['groupId'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -169,6 +169,7 @@ export const meta = {
host: { host: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
description: 'The local host is represented with `null`.',
}, },
url: { url: {
type: 'string', type: 'string',

View file

@ -38,7 +38,11 @@ export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' }, userId: { type: 'string', format: 'misskey:id' },
expiresAt: { type: 'integer', nullable: true }, expiresAt: {
type: 'integer',
nullable: true,
description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.',
},
}, },
required: ['userId'], required: ['userId'],
} as const; } as const;

View file

@ -19,7 +19,7 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
local: { type: 'boolean' }, local: { type: 'boolean', default: false },
reply: { type: 'boolean' }, reply: { type: 'boolean' },
renote: { type: 'boolean' }, renote: { type: 'boolean' },
withFiles: { type: 'boolean' }, withFiles: { type: 'boolean' },
@ -52,19 +52,19 @@ export default define(meta, paramDef, async (ps) => {
query.andWhere('note.userHost IS NULL'); query.andWhere('note.userHost IS NULL');
} }
if (ps.reply != undefined) { if (ps.reply !== undefined) {
query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL');
} }
if (ps.renote != undefined) { if (ps.renote !== undefined) {
query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL');
} }
if (ps.withFiles != undefined) { if (ps.withFiles !== undefined) {
query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`); query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`);
} }
if (ps.poll != undefined) { if (ps.poll !== undefined) {
query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE');
} }

View file

@ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => {
conversation.push(p); conversation.push(p);
} }
if (conversation.length == ps.limit) { if (conversation.length === ps.limit) {
return; return;
} }

View file

@ -59,12 +59,6 @@ export const meta = {
id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15', id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
}, },
contentRequired: {
message: 'Content required. You need to set text, fileIds, renoteId or poll.',
code: 'CONTENT_REQUIRED',
id: '6f57e42b-c348-439b-bc45-993995cc515a',
},
cannotCreateAlreadyExpiredPoll: { cannotCreateAlreadyExpiredPoll: {
message: 'Poll is already expired.', message: 'Poll is already expired.',
code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
@ -92,29 +86,41 @@ export const paramDef = {
visibleUserIds: { type: 'array', uniqueItems: true, items: { visibleUserIds: { type: 'array', uniqueItems: true, items: {
type: 'string', format: 'misskey:id', type: 'string', format: 'misskey:id',
} }, } },
text: { type: 'string', nullable: true, maxLength: MAX_NOTE_TEXT_LENGTH, default: null }, text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
cw: { type: 'string', nullable: true, maxLength: 100 }, cw: { type: 'string', nullable: true, maxLength: 100 },
localOnly: { type: 'boolean', default: false }, localOnly: { type: 'boolean', default: false },
noExtractMentions: { type: 'boolean', default: false }, noExtractMentions: { type: 'boolean', default: false },
noExtractHashtags: { type: 'boolean', default: false }, noExtractHashtags: { type: 'boolean', default: false },
noExtractEmojis: { type: 'boolean', default: false }, noExtractEmojis: { type: 'boolean', default: false },
fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: { fileIds: {
type: 'string', format: 'misskey:id', type: 'array',
} }, uniqueItems: true,
mediaIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: { minItems: 1,
type: 'string', format: 'misskey:id', maxItems: 16,
} }, items: { type: 'string', format: 'misskey:id' },
},
mediaIds: {
deprecated: true,
description: 'Use `fileIds` instead. If both are specified, this property is discarded.',
type: 'array',
uniqueItems: true,
minItems: 1,
maxItems: 16,
items: { type: 'string', format: 'misskey:id' },
},
replyId: { type: 'string', format: 'misskey:id', nullable: true }, replyId: { type: 'string', format: 'misskey:id', nullable: true },
renoteId: { type: 'string', format: 'misskey:id', nullable: true }, renoteId: { type: 'string', format: 'misskey:id', nullable: true },
channelId: { type: 'string', format: 'misskey:id', nullable: true }, channelId: { type: 'string', format: 'misskey:id', nullable: true },
poll: { poll: {
type: 'object', nullable: true, type: 'object',
nullable: true,
properties: { properties: {
choices: { choices: {
type: 'array', uniqueItems: true, minItems: 2, maxItems: 10, type: 'array',
items: { uniqueItems: true,
type: 'string', minLength: 1, maxLength: 50, minItems: 2,
}, maxItems: 10,
items: { type: 'string', minLength: 1, maxLength: 50 },
}, },
multiple: { type: 'boolean', default: false }, multiple: { type: 'boolean', default: false },
expiresAt: { type: 'integer', nullable: true }, expiresAt: { type: 'integer', nullable: true },
@ -123,7 +129,34 @@ export const paramDef = {
required: ['choices'], required: ['choices'],
}, },
}, },
required: [], anyOf: [
{
// (re)note with text, files and poll are optional
properties: {
text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false },
},
required: ['text'],
},
{
// (re)note with files, text and poll are optional
required: ['fileIds'],
},
{
// (re)note with files, text and poll are optional
required: ['mediaIds'],
},
{
// (re)note with poll, text and files are optional
properties: {
poll: { type: 'object', nullable: false, },
},
required: ['poll'],
},
{
// pure renote
required: ['renoteId'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@ -152,7 +185,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (renote == null) { if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget); throw new ApiError(meta.errors.noSuchRenoteTarget);
} else if (renote.renoteId && !renote.text && !renote.fileIds) { } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.poll) {
throw new ApiError(meta.errors.cannotReRenote); throw new ApiError(meta.errors.cannotReRenote);
} }
@ -175,10 +208,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (reply == null) { if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget); throw new ApiError(meta.errors.noSuchReplyTarget);
} } else if (reply.renoteId && !reply.text && !reply.fileIds && !renote.poll) {
// 返信対象が引用でないRenoteだったらエラー
if (reply.renoteId && !reply.text && !reply.fileIds) {
throw new ApiError(meta.errors.cannotReplyToPureRenote); throw new ApiError(meta.errors.cannotReplyToPureRenote);
} }
@ -204,11 +234,6 @@ export default define(meta, paramDef, async (ps, user) => {
} }
} }
// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
if (!(ps.text || files.length || renote || ps.poll)) {
throw new ApiError(meta.errors.contentRequired);
}
let channel: Channel | undefined; let channel: Channel | undefined;
if (ps.channelId != null) { if (ps.channelId != null) {
channel = await Channels.findOneBy({ id: ps.channelId }); channel = await Channels.findOneBy({ id: ps.channelId });

View file

@ -35,7 +35,11 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
withFiles: { type: 'boolean' }, withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },

View file

@ -48,7 +48,11 @@ export const paramDef = {
includeMyRenotes: { type: 'boolean', default: true }, includeMyRenotes: { type: 'boolean', default: true },
includeRenotedMyNotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true },
includeLocalRenotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true },
withFiles: { type: 'boolean' }, withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
}, },
required: [], required: [],
} as const; } as const;

View file

@ -37,7 +37,11 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
withFiles: { type: 'boolean' }, withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
fileType: { type: 'array', items: { fileType: { type: 'array', items: {
type: 'string', type: 'string',
} }, } },

View file

@ -110,7 +110,7 @@ export default define(meta, paramDef, async (ps, user) => {
if (exist.length) { if (exist.length) {
if (poll.multiple) { if (poll.multiple) {
if (exist.some(x => x.choice == ps.choice)) { if (exist.some(x => x.choice === ps.choice)) {
throw new ApiError(meta.errors.alreadyVoted); throw new ApiError(meta.errors.alreadyVoted);
} }
} else { } else {

View file

@ -25,21 +25,44 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
tag: { type: 'string' },
query: { type: 'array', items: {
type: 'array', items: {
type: 'string',
},
} },
reply: { type: 'boolean', nullable: true, default: null }, reply: { type: 'boolean', nullable: true, default: null },
renote: { type: 'boolean', nullable: true, default: null }, renote: { type: 'boolean', nullable: true, default: null },
withFiles: { type: 'boolean' }, withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
poll: { type: 'boolean', nullable: true, default: null }, poll: { type: 'boolean', nullable: true, default: null },
sinceId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
}, },
required: [], anyOf: [
{
properties: {
tag: { type: 'string', minLength: 1 },
},
required: ['tag'],
},
{
properties: {
query: {
type: 'array',
description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.',
items: {
type: 'array',
items: {
type: 'string',
minLength: 1,
},
minItems: 1,
},
minItems: 1,
},
},
required: ['query'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -42,7 +42,11 @@ export const paramDef = {
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 }, offset: { type: 'integer', default: 0 },
host: { type: 'string', nullable: true }, host: {
type: 'string',
nullable: true,
description: 'The local host is represented with `null`.',
},
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
}, },

View file

@ -38,7 +38,11 @@ export const paramDef = {
includeMyRenotes: { type: 'boolean', default: true }, includeMyRenotes: { type: 'boolean', default: true },
includeRenotedMyNotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true },
includeLocalRenotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true },
withFiles: { type: 'boolean' }, withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
}, },
required: [], required: [],
} as const; } as const;

View file

@ -75,7 +75,8 @@ export default define(meta, paramDef, async (ps, user) => {
Accept: 'application/json, */*', Accept: 'application/json, */*',
}, },
body: params, body: params,
timeout: 10000, // TODO
//timeout: 10000,
agent: getAgentByUrl, agent: getAgentByUrl,
}); });

View file

@ -42,7 +42,11 @@ export const paramDef = {
includeMyRenotes: { type: 'boolean', default: true }, includeMyRenotes: { type: 'boolean', default: true },
includeRenotedMyNotes: { type: 'boolean', default: true }, includeRenotedMyNotes: { type: 'boolean', default: true },
includeLocalRenotes: { type: 'boolean', default: true }, includeLocalRenotes: { type: 'boolean', default: true },
withFiles: { type: 'boolean' }, withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
}, },
required: ['listId'], required: ['listId'],
} as const; } as const;

View file

@ -26,12 +26,21 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
anyOf: [
{
properties: { properties: {
pageId: { type: 'string', format: 'misskey:id' }, pageId: { type: 'string', format: 'misskey:id' },
},
required: ['pageId'],
},
{
properties: {
name: { type: 'string' }, name: { type: 'string' },
username: { type: 'string' }, username: { type: 'string' },
}, },
required: [], required: ['name', 'username'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -38,14 +38,29 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' },
username: { type: 'string' },
host: { type: 'string', nullable: true },
sinceId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
}, },
required: [], anyOf: [
{
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
},
{
properties: {
username: { type: 'string' },
host: {
type: 'string',
nullable: true,
description: 'The local host is represented with `null`.',
},
},
required: ['username', 'host'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -38,14 +38,29 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' },
username: { type: 'string' },
host: { type: 'string', nullable: true },
sinceId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
}, },
required: [], anyOf: [
{
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
},
{
properties: {
username: { type: 'string' },
host: {
type: 'string',
nullable: true,
description: 'The local host is represented with `null`.',
},
},
required: ['username', 'host'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -28,7 +28,10 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
detail: { type: 'boolean', default: true }, detail: { type: 'boolean', default: true },
}, },
required: [], anyOf: [
{ required: ['username'] },
{ required: ['host'] },
],
} as const; } as const;
// TODO: avatar,bannerをJOINしたいけどエラーになる // TODO: avatar,bannerをJOINしたいけどエラーになる

View file

@ -46,15 +46,33 @@ export const meta = {
export const paramDef = { export const paramDef = {
type: 'object', type: 'object',
anyOf: [
{
properties: { properties: {
userId: { type: 'string', format: 'misskey:id' }, userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
},
{
properties: {
userIds: { type: 'array', uniqueItems: true, items: { userIds: { type: 'array', uniqueItems: true, items: {
type: 'string', format: 'misskey:id', type: 'string', format: 'misskey:id',
} }, } },
username: { type: 'string' },
host: { type: 'string', nullable: true },
}, },
required: [], required: ['userIds'],
},
{
properties: {
username: { type: 'string' },
host: {
type: 'string',
nullable: true,
description: 'The local host is represented with `null`.',
},
},
required: ['username', 'host'],
},
],
} as const; } as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export

View file

@ -24,17 +24,17 @@ export default async (ctx: Koa.Context) => {
ctx.body = { error }; ctx.body = { error };
} }
if (typeof username != 'string') { if (typeof username !== 'string') {
ctx.status = 400; ctx.status = 400;
return; return;
} }
if (typeof password != 'string') { if (typeof password !== 'string') {
ctx.status = 400; ctx.status = 400;
return; return;
} }
if (token != null && typeof token != 'string') { if (token != null && typeof token !== 'string') {
ctx.status = 400; ctx.status = 400;
return; return;
} }

View file

@ -65,8 +65,7 @@ async function cancelRequest(follower: User, followee: User) {
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'unfollow', {
type: 'unfollow',
user: packed, user: packed,
}); });
} }
@ -118,8 +117,7 @@ async function unFollow(follower: User, followee: User) {
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'unfollow', {
type: 'unfollow',
user: packed, user: packed,
}); });
} }

View file

@ -29,7 +29,7 @@ export async function uploadFromUrl({
sensitive = false, sensitive = false,
force = false, force = false,
isLink = false, isLink = false,
comment = null comment = null,
}: Args): Promise<DriveFile> { }: Args): Promise<DriveFile> {
let name = new URL(url).pathname.split('/').pop() || null; let name = new URL(url).pathname.split('/').pop() || null;
if (name == null || !DriveFiles.validateFileName(name)) { if (name == null || !DriveFiles.validateFileName(name)) {
@ -38,7 +38,7 @@ export async function uploadFromUrl({
// If the comment is same as the name, skip comment // If the comment is same as the name, skip comment
// (image.name is passed in when receiving attachment) // (image.name is passed in when receiving attachment)
if (comment !== null && name == comment) { if (comment !== null && name === comment) {
comment = null; comment = null;
} }

View file

@ -97,7 +97,7 @@ async function fetchNodeinfo(instance: Instance): Promise<NodeInfo> {
} else { } else {
throw e.statusCode || e.message; throw e.statusCode || e.message;
} }
}); }) as Record<string, unknown>;
if (wellknown.links == null || !Array.isArray(wellknown.links)) { if (wellknown.links == null || !Array.isArray(wellknown.links)) {
throw 'No wellknown links'; throw 'No wellknown links';
@ -121,7 +121,7 @@ async function fetchNodeinfo(instance: Instance): Promise<NodeInfo> {
logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`);
return info; return info as NodeInfo;
} catch (e) { } catch (e) {
logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${e}`); logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${e}`);
@ -142,12 +142,12 @@ async function fetchDom(instance: Instance): Promise<DOMWindow['document']> {
return doc; return doc;
} }
async function fetchManifest(instance: Instance): Promise<Record<string, any> | null> { async function fetchManifest(instance: Instance): Promise<Record<string, unknown> | null> {
const url = 'https://' + instance.host; const url = 'https://' + instance.host;
const manifestUrl = url + '/manifest.json'; const manifestUrl = url + '/manifest.json';
const manifest = await getJson(manifestUrl); const manifest = await getJson(manifestUrl) as Record<string, unknown>;
return manifest; return manifest;
} }
@ -167,7 +167,8 @@ async function fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] |
const faviconUrl = url + '/favicon.ico'; const faviconUrl = url + '/favicon.ico';
const favicon = await fetch(faviconUrl, { const favicon = await fetch(faviconUrl, {
timeout: 10000, // TODO
//timeout: 10000,
agent: getAgentByUrl, agent: getAgentByUrl,
}); });

View file

@ -97,8 +97,7 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'follow', {
type: 'follow',
user: packed, user: packed,
}); });
} }
@ -108,12 +107,11 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
// Publish followed event // Publish followed event
if (Users.isLocalUser(followee)) { if (Users.isLocalUser(followee)) {
Users.pack(follower.id, followee).then(async packed => { Users.pack(follower.id, followee).then(async packed => {
publishMainStream(followee.id, 'followed', packed) publishMainStream(followee.id, 'followed', packed);
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'followed', {
type: 'followed',
user: packed, user: packed,
}); });
} }

View file

@ -38,8 +38,7 @@ export default async function(follower: { id: User['id']; host: User['host']; ur
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'unfollow', {
type: 'unfollow',
user: packed, user: packed,
}); });
} }

View file

@ -115,8 +115,7 @@ async function publishUnfollow(followee: Both, follower: Local) {
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'unfollow', {
type: 'unfollow',
user: packedFollowee, user: packedFollowee,
}); });
} }

View file

@ -370,8 +370,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
getActiveWebhooks().then(webhooks => { getActiveWebhooks().then(webhooks => {
webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note')); webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'note', {
type: 'note',
note: noteObj, note: noteObj,
}); });
} }
@ -400,8 +399,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'reply', {
type: 'reply',
note: noteObj, note: noteObj,
}); });
} }
@ -427,8 +425,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'renote', {
type: 'renote',
note: noteObj, note: noteObj,
}); });
} }
@ -662,8 +659,7 @@ async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note,
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention')); const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
for (const webhook of webhooks) { for (const webhook of webhooks) {
webhookDeliver(webhook, { webhookDeliver(webhook, 'mention', {
type: 'mention',
note: detailPackedNote, note: detailPackedNote,
}); });
} }

View file

@ -51,14 +51,14 @@ export function applyTheme(theme: Theme, persist = true) {
if (_theme.base) { if (_theme.base) {
const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); const base = [lightTheme, darkTheme].find(x => x.id === _theme.base);
_theme.props = Object.assign({}, base.props, _theme.props); if (base) _theme.props = Object.assign({}, base.props, _theme.props);
} }
const props = compile(_theme); const props = compile(_theme);
for (const tag of document.head.children) { for (const tag of document.head.children) {
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
tag.setAttribute('content', props['html']); tag.setAttribute('content', props['htmlThemeColor']);
break; break;
} }
} }