Merge branch 'misskey-dev:develop' into sonic
This commit is contained in:
		
						commit
						79b9e6d4ba
					
				
					 66 changed files with 428 additions and 206 deletions
				
			
		
							
								
								
									
										10
									
								
								.github/workflows/lint.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/lint.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -14,12 +14,12 @@ jobs: | |||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         submodules: true | ||||
|     - uses: actions/setup-node@v1 | ||||
|     - uses: actions/setup-node@v3 | ||||
|       with: | ||||
|         node-version: 16.x | ||||
|     - uses: actions/cache@v2 | ||||
|       with: | ||||
|         path: '**/node_modules' | ||||
|         key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} | ||||
|         cache: 'yarn' | ||||
|         cache-dependency-path: | | ||||
|           packages/backend/yarn.lock | ||||
|           packages/client/yarn.lock | ||||
|     - run: yarn install | ||||
|     - run: yarn lint | ||||
|  |  | |||
							
								
								
									
										16
									
								
								.github/workflows/test.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/test.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -33,9 +33,13 @@ jobs: | |||
|       with: | ||||
|         submodules: true | ||||
|     - name: Use Node.js ${{ matrix.node-version }} | ||||
|       uses: actions/setup-node@v1 | ||||
|       uses: actions/setup-node@v3 | ||||
|       with: | ||||
|         node-version: ${{ matrix.node-version }} | ||||
|         cache: 'yarn' | ||||
|         cache-dependency-path: | | ||||
|           packages/backend/yarn.lock | ||||
|           packages/client/yarn.lock | ||||
|     - name: Install dependencies | ||||
|       run: yarn install | ||||
|     - name: Check yarn.lock | ||||
|  | @ -80,13 +84,13 @@ jobs: | |||
|     #- uses: browser-actions/setup-firefox@latest | ||||
|     #  if: ${{ matrix.browser == 'firefox' }} | ||||
|     - name: Use Node.js ${{ matrix.node-version }} | ||||
|       uses: actions/setup-node@v1 | ||||
|       uses: actions/setup-node@v3 | ||||
|       with: | ||||
|         node-version: ${{ matrix.node-version }} | ||||
|     - uses: actions/cache@v2 | ||||
|       with: | ||||
|         path: '**/node_modules' | ||||
|         key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} | ||||
|         cache: 'yarn' | ||||
|         cache-dependency-path: | | ||||
|           packages/backend/yarn.lock | ||||
|           packages/client/yarn.lock | ||||
|     - name: Install dependencies | ||||
|       run: yarn install | ||||
|     - name: Check yarn.lock | ||||
|  |  | |||
							
								
								
									
										18
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								CHANGELOG.md
									
										
									
									
									
								
							|  | @ -10,10 +10,22 @@ | |||
| You should also include the user name that made the change. | ||||
| --> | ||||
| 
 | ||||
| ## 12.109.1 (2022/04/02) | ||||
| ## 12.x.x (unreleased) | ||||
| 
 | ||||
| ### Known issues | ||||
| - two-factor authentication is not working | ||||
| ### Improvements | ||||
| - 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 | ||||
| - API: Renoteが行えない問題を修正 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
| 	"name": "misskey", | ||||
| 	"version": "12.109.1", | ||||
| 	"version": "12.109.2", | ||||
| 	"codename": "indigo", | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
|  |  | |||
|  | @ -209,7 +209,11 @@ export const db = new DataSource({ | |||
| }); | ||||
| 
 | ||||
| export async function initDb() { | ||||
| 	await db.connect(); | ||||
| 	if (db.isInitialized) { | ||||
| 		// nop
 | ||||
| 	} else { | ||||
| 		await db.connect(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export async function resetDb() { | ||||
|  |  | |||
|  | @ -42,7 +42,8 @@ async function getCaptchaResponse(url: string, secret: string, response: string) | |||
| 		headers: { | ||||
| 			'User-Agent': config.userAgent, | ||||
| 		}, | ||||
| 		timeout: 10 * 1000, | ||||
| 		// TODO
 | ||||
| 		//timeout: 10 * 1000,
 | ||||
| 		agent: getAgentByUrl, | ||||
| 	}).catch(e => { | ||||
| 		throw `${e.message || e}`; | ||||
|  |  | |||
|  | @ -120,9 +120,9 @@ export const httpsAgent = config.proxy | |||
|  */ | ||||
| export function getAgentByUrl(url: URL, bypassProxy = false) { | ||||
| 	if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) { | ||||
| 		return url.protocol == 'http:' ? _http : _https; | ||||
| 		return url.protocol === 'http:' ? _http : _https; | ||||
| 	} else { | ||||
| 		return url.protocol == 'http:' ? httpAgent : httpsAgent; | ||||
| 		return url.protocol === 'http:' ? httpAgent : httpsAgent; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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})`; | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ export const packedEmojiSchema = { | |||
| 		host: { | ||||
| 			type: 'string', | ||||
| 			optional: false, nullable: true, | ||||
| 			description: 'The local host is represented with `null`.', | ||||
| 		}, | ||||
| 		url: { | ||||
| 			type: 'string', | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ export const packedUserLiteSchema = { | |||
| 			type: 'string', | ||||
| 			nullable: true, optional: false, | ||||
| 			example: 'misskey.example.com', | ||||
| 			description: 'The local host is represented with `null`.', | ||||
| 		}, | ||||
| 		avatarUrl: { | ||||
| 			type: 'string', | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import httpSignature from 'http-signature'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| 
 | ||||
| import config from '@/config/index.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 { ThinUser } from './types.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 { | ||||
| 	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 = { | ||||
| 		type, | ||||
| 		content, | ||||
| 		webhookId: webhook.id, | ||||
| 		userId: webhook.userId, | ||||
| 		to: webhook.url, | ||||
| 		secret: webhook.secret, | ||||
| 		createdAt: Date.now(), | ||||
| 		eventId: uuid(), | ||||
| 	}; | ||||
| 
 | ||||
| 	return webhookDeliverQueue.add(data, { | ||||
|  |  | |||
|  | @ -8,13 +8,9 @@ import config from '@/config/index.js'; | |||
| 
 | ||||
| const logger = new Logger('webhook'); | ||||
| 
 | ||||
| let latest: string | null = null; | ||||
| 
 | ||||
| export default async (job: Bull.Job<WebhookDeliverJobData>) => { | ||||
| 	try { | ||||
| 		if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) { | ||||
| 			logger.debug(`delivering ${latest}`); | ||||
| 		} | ||||
| 		logger.debug(`delivering ${job.data.webhookId}`); | ||||
| 
 | ||||
| 		const res = await getResponse({ | ||||
| 			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-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 }, { | ||||
|  |  | |||
|  | @ -48,10 +48,14 @@ export type EndedPollNotificationJobData = { | |||
| }; | ||||
| 
 | ||||
| export type WebhookDeliverJobData = { | ||||
| 	type: string; | ||||
| 	content: unknown; | ||||
| 	webhookId: Webhook['id']; | ||||
| 	userId: User['id']; | ||||
| 	to: string; | ||||
| 	secret: string; | ||||
| 	createdAt: number; | ||||
| 	eventId: string; | ||||
| }; | ||||
| 
 | ||||
| export type ThinUser = { | ||||
|  |  | |||
|  | @ -95,7 +95,7 @@ function genSigningString(request: Request, includeHeaders: string[]) { | |||
| 
 | ||||
| function lcObjectKey(src: 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; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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
 | ||||
| 			isDirect(recipe) | ||||
| 			// check that shared inbox has not been added yet
 | ||||
| 			&& !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) | ||||
| 			// check that they actually have an inbox
 | ||||
| 			&& recipe.to.inbox | ||||
| 		}) | ||||
| 		.forEach(recipe => inboxes.add(recipe.to.inbox)); | ||||
| 			&& recipe.to.inbox != null, | ||||
| 		) | ||||
| 		.forEach(recipe => inboxes.add(recipe.to.inbox!)); | ||||
| 
 | ||||
| 		// deliver
 | ||||
| 		for (const inbox of inboxes) { | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ export const performReadActivity = async (actor: CacheableRemoteUser, activity: | |||
| 		return `skip: message not found`; | ||||
| 	} | ||||
| 
 | ||||
| 	if (actor.id != message.recipientId) { | ||||
| 	if (actor.id !== message.recipientId) { | ||||
| 		return `skip: actor is not a message recipient`; | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import unfollow from '@/services/following/delete.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 { Followings } from '@/models/index.js'; | ||||
| import DbResolver from '../../db-resolver.js'; | ||||
|  |  | |||
|  | @ -113,7 +113,8 @@ export class LdSignature { | |||
| 			headers: { | ||||
| 				Accept: 'application/ld+json, application/json', | ||||
| 			}, | ||||
| 			timeout: this.loderTimeout, | ||||
| 			// TODO
 | ||||
| 			//timeout: this.loderTimeout,
 | ||||
| 			agent: u => u.protocol === 'http:' ? httpAgent : httpsAgent, | ||||
| 		}).then(res => { | ||||
| 			if (!res.ok) { | ||||
|  |  | |||
|  | @ -69,7 +69,7 @@ export async function updateQuestion(value: any) { | |||
| 		const oldCount = poll.votes[poll.choices.indexOf(choice)]; | ||||
| 		const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems; | ||||
| 
 | ||||
| 		if (oldCount != newCount) { | ||||
| 		if (oldCount !== newCount) { | ||||
| 			changed = true; | ||||
| 			poll.votes[poll.choices.indexOf(choice)] = newCount; | ||||
| 		} | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ type IWebFinger = { | |||
| export default async function(query: string): Promise<IWebFinger> { | ||||
| 	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) { | ||||
|  |  | |||
|  | @ -121,14 +121,14 @@ export function verifyLogin({ | |||
| 	signature: Buffer, | ||||
| 	challenge: string | ||||
| }) { | ||||
| 	if (clientData.type != 'webauthn.get') { | ||||
| 	if (clientData.type !== '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'); | ||||
| 	} | ||||
| 	if (clientData.origin != config.scheme + '://' + config.host) { | ||||
| 	if (clientData.origin !== config.scheme + '://' + config.host) { | ||||
| 		throw new Error('origin mismatch'); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -148,11 +148,11 @@ export const procedures = { | |||
| 		verify({ publicKey }: {publicKey: Map<number, Buffer>}) { | ||||
| 			const negTwo = publicKey.get(-2); | ||||
| 
 | ||||
| 			if (!negTwo || negTwo.length != 32) { | ||||
| 			if (!negTwo || negTwo.length !== 32) { | ||||
| 				throw new Error('invalid or no -2 key given'); | ||||
| 			} | ||||
| 			const negThree = publicKey.get(-3); | ||||
| 			if (!negThree || negThree.length != 32) { | ||||
| 			if (!negThree || negThree.length !== 32) { | ||||
| 				throw new Error('invalid or no -3 key given'); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -183,7 +183,7 @@ export const procedures = { | |||
| 			rpIdHash: Buffer, | ||||
| 			credentialId: Buffer, | ||||
| 		}) { | ||||
| 			if (attStmt.alg != -7) { | ||||
| 			if (attStmt.alg !== -7) { | ||||
| 				throw new Error('alg mismatch'); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -196,11 +196,11 @@ export const procedures = { | |||
| 
 | ||||
| 			const negTwo = publicKey.get(-2); | ||||
| 
 | ||||
| 			if (!negTwo || negTwo.length != 32) { | ||||
| 			if (!negTwo || negTwo.length !== 32) { | ||||
| 				throw new Error('invalid or no -2 key given'); | ||||
| 			} | ||||
| 			const negThree = publicKey.get(-3); | ||||
| 			if (!negThree || negThree.length != 32) { | ||||
| 			if (!negThree || negThree.length !== 32) { | ||||
| 				throw new Error('invalid or no -3 key given'); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -263,7 +263,7 @@ export const procedures = { | |||
| 				.map((key: any) => PEMString(key)) | ||||
| 				.concat([GSR2]); | ||||
| 
 | ||||
| 			if (getCertSubject(certificateChain[0]).CN != 'attest.android.com') { | ||||
| 			if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') { | ||||
| 				throw new Error('invalid common name'); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -283,11 +283,11 @@ export const procedures = { | |||
| 
 | ||||
| 			const negTwo = publicKey.get(-2); | ||||
| 
 | ||||
| 			if (!negTwo || negTwo.length != 32) { | ||||
| 			if (!negTwo || negTwo.length !== 32) { | ||||
| 				throw new Error('invalid or no -2 key given'); | ||||
| 			} | ||||
| 			const negThree = publicKey.get(-3); | ||||
| 			if (!negThree || negThree.length != 32) { | ||||
| 			if (!negThree || negThree.length !== 32) { | ||||
| 				throw new Error('invalid or no -3 key given'); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -332,11 +332,11 @@ export const procedures = { | |||
| 
 | ||||
| 				const negTwo = publicKey.get(-2); | ||||
| 
 | ||||
| 				if (!negTwo || negTwo.length != 32) { | ||||
| 				if (!negTwo || negTwo.length !== 32) { | ||||
| 					throw new Error('invalid or no -2 key given'); | ||||
| 				} | ||||
| 				const negThree = publicKey.get(-3); | ||||
| 				if (!negThree || negThree.length != 32) { | ||||
| 				if (!negThree || negThree.length !== 32) { | ||||
| 					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
 | ||||
| 				throw new Error('ECDAA-Verify is not supported'); | ||||
| 			} 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'); | ||||
| 			} | ||||
|  | @ -377,7 +377,7 @@ export const procedures = { | |||
| 			credentialId: Buffer | ||||
| 		}) { | ||||
| 			const x5c: Buffer[] = attStmt.x5c; | ||||
| 			if (x5c.length != 1) { | ||||
| 			if (x5c.length !== 1) { | ||||
| 				throw new Error('x5c length does not match expectation'); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -387,11 +387,11 @@ export const procedures = { | |||
| 
 | ||||
| 			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'); | ||||
| 			} | ||||
| 			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'); | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,12 @@ export const paramDef = { | |||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
| 		type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) }, | ||||
| 		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: [], | ||||
| } as const; | ||||
|  |  | |||
|  | @ -40,6 +40,7 @@ export const meta = { | |||
| 			userHost: { | ||||
| 				type: 'string', | ||||
| 				optional: false, nullable: true, | ||||
| 				description: 'The local host is represented with `null`.', | ||||
| 			}, | ||||
| 			md5: { | ||||
| 				type: 'string', | ||||
|  | @ -151,11 +152,20 @@ export const meta = { | |||
| 
 | ||||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		fileId: { type: 'string', format: 'misskey:id' }, | ||||
| 		url: { type: 'string' }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| 	anyOf: [ | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				fileId: { type: 'string', format: 'misskey:id' }, | ||||
| 			}, | ||||
| 			required: ['fileId'], | ||||
| 		}, | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				url: { type: 'string' }, | ||||
| 			}, | ||||
| 			required: ['url'], | ||||
| 		}, | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  |  | |||
|  | @ -40,6 +40,7 @@ export const meta = { | |||
| 				host: { | ||||
| 					type: 'string', | ||||
| 					optional: false, nullable: true, | ||||
| 					description: 'The local host is represented with `null`.', | ||||
| 				}, | ||||
| 				url: { | ||||
| 					type: 'string', | ||||
|  | @ -54,7 +55,12 @@ export const paramDef = { | |||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		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 }, | ||||
| 		sinceId: { type: 'string', format: 'misskey:id' }, | ||||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
|  |  | |||
|  | @ -38,8 +38,9 @@ export const meta = { | |||
| 					optional: false, nullable: true, | ||||
| 				}, | ||||
| 				host: { | ||||
| 					type: 'string', | ||||
| 					optional: false, nullable: true, | ||||
| 					type: 'null', | ||||
| 					optional: false, | ||||
| 					description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.', | ||||
| 				}, | ||||
| 				url: { | ||||
| 					type: 'string', | ||||
|  |  | |||
|  | @ -17,7 +17,11 @@ export const paramDef = { | |||
| 		ids: { type: 'array', items: { | ||||
| 			type: 'string', format: 'misskey:id', | ||||
| 		} }, | ||||
| 		category: { type: 'string', nullable: true }, | ||||
| 		category: { | ||||
| 			type: 'string', | ||||
| 			nullable: true, | ||||
| 			description: 'Use `null` to reset the category.', | ||||
| 		}, | ||||
| 	}, | ||||
| 	required: ['ids'], | ||||
| } as const; | ||||
|  |  | |||
|  | @ -23,7 +23,11 @@ export const paramDef = { | |||
| 	properties: { | ||||
| 		id: { type: 'string', format: 'misskey:id' }, | ||||
| 		name: { type: 'string' }, | ||||
| 		category: { type: 'string', nullable: true }, | ||||
| 		category: { | ||||
| 			type: 'string', | ||||
| 			nullable: true, | ||||
| 			description: 'Use `null` to reset the category.', | ||||
| 		}, | ||||
| 		aliases: { type: 'array', items: { | ||||
| 			type: 'string', | ||||
| 		} }, | ||||
|  |  | |||
|  | @ -26,8 +26,13 @@ export const paramDef = { | |||
| 		sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, | ||||
| 		state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" }, | ||||
| 		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, | ||||
| 		username: { type: 'string', default: null }, | ||||
| 		hostname: { type: 'string', default: null }, | ||||
| 		username: { type: 'string', nullable: true, default: null }, | ||||
| 		hostname: { | ||||
| 			type: 'string', | ||||
| 			nullable: true, | ||||
| 			default: null, | ||||
| 			description: 'The local host is represented with `null`.', | ||||
| 		}, | ||||
| 	}, | ||||
| 	required: [], | ||||
| } as const; | ||||
|  |  | |||
|  | @ -397,12 +397,14 @@ export default define(meta, paramDef, async (ps, me) => { | |||
| 	} | ||||
| 
 | ||||
| 	await db.transaction(async transactionalEntityManager => { | ||||
| 		const meta = await transactionalEntityManager.findOne(Meta, { | ||||
| 		const metas = await transactionalEntityManager.find(Meta, { | ||||
| 			order: { | ||||
| 				id: 'DESC', | ||||
| 			}, | ||||
| 		}); | ||||
| 
 | ||||
| 		const meta = metas[0]; | ||||
| 
 | ||||
| 		if (meta) { | ||||
| 			await transactionalEntityManager.update(Meta, meta.id, set); | ||||
| 		} else { | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ export const paramDef = { | |||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		name: { type: 'string', minLength: 1, maxLength: 100 }, | ||||
| 		isPublic: { type: 'boolean' }, | ||||
| 		isPublic: { type: 'boolean', default: false }, | ||||
| 		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, | ||||
| 	}, | ||||
| 	required: ['name'], | ||||
|  |  | |||
|  | @ -48,7 +48,6 @@ export const paramDef = { | |||
| } as const; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
| // @ts-ignore
 | ||||
| export default define(meta, paramDef, async (ps, user, _, file, cleanup) => { | ||||
| 	// Get 'name' parameter
 | ||||
| 	let name = ps.name || file.originalname; | ||||
|  |  | |||
|  | @ -28,22 +28,25 @@ export const meta = { | |||
| 			code: 'ACCESS_DENIED', | ||||
| 			id: '25b73c73-68b1-41d0-bad1-381cfdf6579f', | ||||
| 		}, | ||||
| 
 | ||||
| 		fileIdOrUrlRequired: { | ||||
| 			message: 'fileId or url required.', | ||||
| 			code: 'INVALID_PARAM', | ||||
| 			id: '89674805-722c-440c-8d88-5641830dc3e4', | ||||
| 		}, | ||||
| 	}, | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		fileId: { type: 'string', format: 'misskey:id' }, | ||||
| 		url: { type: 'string' }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| 	anyOf: [ | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				fileId: { type: 'string', format: 'misskey:id' }, | ||||
| 			}, | ||||
| 			required: ['fileId'], | ||||
| 		}, | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				url: { type: 'string' }, | ||||
| 			}, | ||||
| 			required: ['url'], | ||||
| 		}, | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  | @ -62,8 +65,6 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 				thumbnailUrl: ps.url, | ||||
| 			}], | ||||
| 		}); | ||||
| 	} else { | ||||
| 		throw new ApiError(meta.errors.fileIdOrUrlRequired); | ||||
| 	} | ||||
| 
 | ||||
| 	if (file == null) { | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	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 }, | ||||
| 		notResponding: { type: 'boolean', nullable: true }, | ||||
| 		suspended: { type: 'boolean', nullable: true }, | ||||
|  |  | |||
|  | @ -50,10 +50,10 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 
 | ||||
| 	const clientData = JSON.parse(ps.clientDataJSON); | ||||
| 
 | ||||
| 	if (clientData.type != 'webauthn.create') { | ||||
| 	if (clientData.type !== 'webauthn.create') { | ||||
| 		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'); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -78,7 +78,7 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 	const credentialId = authData.slice(55, 55 + credentialIdLength); | ||||
| 	const publicKeyData = authData.slice(55 + credentialIdLength); | ||||
| 	const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData); | ||||
| 	if (publicKey.get(3) != -7) { | ||||
| 	if (publicKey.get(3) !== -7) { | ||||
| 		throw new Error('alg mismatch'); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 		take: ps.limit, | ||||
| 		skip: ps.offset, | ||||
| 		order: { | ||||
| 			id: ps.sort == 'asc' ? 1 : -1, | ||||
| 			id: ps.sort === 'asc' ? 1 : -1, | ||||
| 		}, | ||||
| 	}); | ||||
| 
 | ||||
|  |  | |||
|  | @ -47,14 +47,25 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		userId: { type: 'string', format: 'misskey:id' }, | ||||
| 		groupId: { type: 'string', format: 'misskey:id' }, | ||||
| 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, | ||||
| 		sinceId: { type: 'string', format: 'misskey:id' }, | ||||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
| 		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; | ||||
| 
 | ||||
| // 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, { | ||||
| 			populateGroup: false, | ||||
| 		}))); | ||||
| 	} else { | ||||
| 		throw new Error(); | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -67,12 +67,23 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		userId: { type: 'string', format: 'misskey:id' }, | ||||
| 		groupId: { type: 'string', format: 'misskey:id' }, | ||||
| 		text: { type: 'string', nullable: true, maxLength: 3000 }, | ||||
| 		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; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  |  | |||
|  | @ -169,6 +169,7 @@ export const meta = { | |||
| 						host: { | ||||
| 							type: 'string', | ||||
| 							optional: false, nullable: true, | ||||
| 							description: 'The local host is represented with `null`.', | ||||
| 						}, | ||||
| 						url: { | ||||
| 							type: 'string', | ||||
|  |  | |||
|  | @ -38,7 +38,11 @@ export const paramDef = { | |||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		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'], | ||||
| } as const; | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		local: { type: 'boolean' }, | ||||
| 		local: { type: 'boolean', default: false }, | ||||
| 		reply: { type: 'boolean' }, | ||||
| 		renote: { type: 'boolean' }, | ||||
| 		withFiles: { type: 'boolean' }, | ||||
|  | @ -52,19 +52,19 @@ export default define(meta, paramDef, async (ps) => { | |||
| 		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'); | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.renote != undefined) { | ||||
| 	if (ps.renote !== undefined) { | ||||
| 		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 = '{}'`); | ||||
| 	} | ||||
| 
 | ||||
| 	if (ps.poll != undefined) { | ||||
| 	if (ps.poll !== undefined) { | ||||
| 		query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 			conversation.push(p); | ||||
| 		} | ||||
| 
 | ||||
| 		if (conversation.length == ps.limit) { | ||||
| 		if (conversation.length === ps.limit) { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -59,12 +59,6 @@ export const meta = { | |||
| 			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: { | ||||
| 			message: 'Poll is already expired.', | ||||
| 			code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL', | ||||
|  | @ -92,29 +86,41 @@ export const paramDef = { | |||
| 		visibleUserIds: { type: 'array', uniqueItems: true, items: { | ||||
| 			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 }, | ||||
| 		localOnly: { type: 'boolean', default: false }, | ||||
| 		noExtractMentions: { type: 'boolean', default: false }, | ||||
| 		noExtractHashtags: { type: 'boolean', default: false }, | ||||
| 		noExtractEmojis: { type: 'boolean', default: false }, | ||||
| 		fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: { | ||||
| 			type: 'string', format: 'misskey:id', | ||||
| 		} }, | ||||
| 		mediaIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: { | ||||
| 			type: 'string', format: 'misskey:id', | ||||
| 		} }, | ||||
| 		fileIds: { | ||||
| 			type: 'array', | ||||
| 			uniqueItems: true, | ||||
| 			minItems: 1, | ||||
| 			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 }, | ||||
| 		renoteId: { type: 'string', format: 'misskey:id', nullable: true }, | ||||
| 		channelId: { type: 'string', format: 'misskey:id', nullable: true }, | ||||
| 		poll: { | ||||
| 			type: 'object', nullable: true, | ||||
| 			type: 'object', | ||||
| 			nullable: true, | ||||
| 			properties: { | ||||
| 				choices: { | ||||
| 					type: 'array', uniqueItems: true, minItems: 2, maxItems: 10,  | ||||
| 					items: { | ||||
| 						type: 'string', minLength: 1, maxLength: 50, | ||||
| 					}, | ||||
| 					type: 'array', | ||||
| 					uniqueItems: true, | ||||
| 					minItems: 2, | ||||
| 					maxItems: 10, | ||||
| 					items: { type: 'string', minLength: 1, maxLength: 50 }, | ||||
| 				}, | ||||
| 				multiple: { type: 'boolean', default: false }, | ||||
| 				expiresAt: { type: 'integer', nullable: true }, | ||||
|  | @ -123,7 +129,34 @@ export const paramDef = { | |||
| 			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; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  | @ -152,7 +185,7 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 
 | ||||
| 		if (renote == null) { | ||||
| 			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); | ||||
| 		} | ||||
| 
 | ||||
|  | @ -175,10 +208,7 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 
 | ||||
| 		if (reply == null) { | ||||
| 			throw new ApiError(meta.errors.noSuchReplyTarget); | ||||
| 		} | ||||
| 
 | ||||
| 		// 返信対象が引用でないRenoteだったらエラー
 | ||||
| 		if (reply.renoteId && !reply.text && !reply.fileIds) { | ||||
| 		} else if (reply.renoteId && !reply.text && !reply.fileIds && !renote.poll) { | ||||
| 			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; | ||||
| 	if (ps.channelId != null) { | ||||
| 		channel = await Channels.findOneBy({ id: ps.channelId }); | ||||
|  |  | |||
|  | @ -35,7 +35,11 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	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 }, | ||||
| 		sinceId: { type: 'string', format: 'misskey:id' }, | ||||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
|  |  | |||
|  | @ -48,7 +48,11 @@ export const paramDef = { | |||
| 		includeMyRenotes: { type: 'boolean', default: true }, | ||||
| 		includeRenotedMyNotes: { 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: [], | ||||
| } as const; | ||||
|  |  | |||
|  | @ -37,7 +37,11 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		withFiles: { type: 'boolean' }, | ||||
| 		withFiles: { | ||||
| 			type: 'boolean', | ||||
| 			default: false, | ||||
| 			description: 'Only show notes that have attached files.', | ||||
| 		}, | ||||
| 		fileType: { type: 'array', items: { | ||||
| 			type: 'string', | ||||
| 		} }, | ||||
|  |  | |||
|  | @ -110,7 +110,7 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 
 | ||||
| 	if (exist.length) { | ||||
| 		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); | ||||
| 			} | ||||
| 		} else { | ||||
|  |  | |||
|  | @ -25,21 +25,44 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		tag: { type: 'string' }, | ||||
| 		query: { type: 'array', items: { | ||||
| 			type: 'array', items: { | ||||
| 				type: 'string', | ||||
| 			}, | ||||
| 		} }, | ||||
| 		reply: { 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 }, | ||||
| 		sinceId: { type: 'string', format: 'misskey:id' }, | ||||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
| 		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; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  |  | |||
|  | @ -42,7 +42,11 @@ export const paramDef = { | |||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
| 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, | ||||
| 		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 }, | ||||
| 		channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, | ||||
| 	}, | ||||
|  |  | |||
|  | @ -38,7 +38,11 @@ export const paramDef = { | |||
| 		includeMyRenotes: { type: 'boolean', default: true }, | ||||
| 		includeRenotedMyNotes: { 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: [], | ||||
| } as const; | ||||
|  |  | |||
|  | @ -75,7 +75,8 @@ export default define(meta, paramDef, async (ps, user) => { | |||
| 			Accept: 'application/json, */*', | ||||
| 		}, | ||||
| 		body: params, | ||||
| 		timeout: 10000, | ||||
| 		// TODO
 | ||||
| 		//timeout: 10000,
 | ||||
| 		agent: getAgentByUrl, | ||||
| 	}); | ||||
| 
 | ||||
|  |  | |||
|  | @ -42,7 +42,11 @@ export const paramDef = { | |||
| 		includeMyRenotes: { type: 'boolean', default: true }, | ||||
| 		includeRenotedMyNotes: { 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'], | ||||
| } as const; | ||||
|  |  | |||
|  | @ -26,12 +26,21 @@ export const meta = { | |||
| 
 | ||||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		pageId: { type: 'string', format: 'misskey:id' }, | ||||
| 		name: { type: 'string' }, | ||||
| 		username: { type: 'string' }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| 	anyOf: [ | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				pageId: { type: 'string', format: 'misskey:id' }, | ||||
| 			}, | ||||
| 			required: ['pageId'], | ||||
| 		}, | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				name: { type: 'string' }, | ||||
| 				username: { type: 'string' }, | ||||
| 			}, | ||||
| 			required: ['name', 'username'], | ||||
| 		}, | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  |  | |||
|  | @ -38,14 +38,29 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		userId: { type: 'string', format: 'misskey:id' }, | ||||
| 		username: { type: 'string' }, | ||||
| 		host: { type: 'string', nullable: true }, | ||||
| 		sinceId: { type: 'string', format: 'misskey:id' }, | ||||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
| 		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; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  |  | |||
|  | @ -38,14 +38,29 @@ export const meta = { | |||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		userId: { type: 'string', format: 'misskey:id' }, | ||||
| 		username: { type: 'string' }, | ||||
| 		host: { type: 'string', nullable: true }, | ||||
| 		sinceId: { type: 'string', format: 'misskey:id' }, | ||||
| 		untilId: { type: 'string', format: 'misskey:id' }, | ||||
| 		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; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  |  | |||
|  | @ -28,7 +28,10 @@ export const paramDef = { | |||
| 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, | ||||
| 		detail: { type: 'boolean', default: true }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| 	anyOf: [ | ||||
| 		{ required: ['username'] }, | ||||
| 		{ required: ['host'] }, | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
| // TODO: avatar,bannerをJOINしたいけどエラーになる
 | ||||
|  |  | |||
|  | @ -46,15 +46,33 @@ export const meta = { | |||
| 
 | ||||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		userId: { type: 'string', format: 'misskey:id' }, | ||||
| 		userIds: { type: 'array', uniqueItems: true, items: { | ||||
| 			type: 'string', format: 'misskey:id', | ||||
| 		} }, | ||||
| 		username: { type: 'string' }, | ||||
| 		host: { type: 'string', nullable: true }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| 	anyOf: [ | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				userId: { type: 'string', format: 'misskey:id' }, | ||||
| 			}, | ||||
| 			required: ['userId'], | ||||
| 		}, | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				userIds: { type: 'array', uniqueItems: true, items: { | ||||
| 					type: 'string', format: 'misskey:id', | ||||
| 				} }, | ||||
| 			}, | ||||
| 			required: ['userIds'], | ||||
| 		}, | ||||
| 		{ | ||||
| 			properties: { | ||||
| 				username: { type: 'string' }, | ||||
| 				host: { | ||||
| 					type: 'string', | ||||
| 					nullable: true, | ||||
| 					description: 'The local host is represented with `null`.', | ||||
| 				}, | ||||
| 			}, | ||||
| 			required: ['username', 'host'], | ||||
| 		}, | ||||
| 	], | ||||
| } as const; | ||||
| 
 | ||||
| // eslint-disable-next-line import/no-default-export
 | ||||
|  |  | |||
|  | @ -24,17 +24,17 @@ export default async (ctx: Koa.Context) => { | |||
| 		ctx.body = { error }; | ||||
| 	} | ||||
| 
 | ||||
| 	if (typeof username != 'string') { | ||||
| 	if (typeof username !== 'string') { | ||||
| 		ctx.status = 400; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (typeof password != 'string') { | ||||
| 	if (typeof password !== 'string') { | ||||
| 		ctx.status = 400; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (token != null && typeof token != 'string') { | ||||
| 	if (token != null && typeof token !== 'string') { | ||||
| 		ctx.status = 400; | ||||
| 		return; | ||||
| 	} | ||||
|  |  | |||
|  | @ -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')); | ||||
| 			for (const webhook of webhooks) { | ||||
| 				webhookDeliver(webhook, { | ||||
| 					type: 'unfollow', | ||||
| 				webhookDeliver(webhook, 'unfollow', { | ||||
| 					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')); | ||||
| 			for (const webhook of webhooks) { | ||||
| 				webhookDeliver(webhook, { | ||||
| 					type: 'unfollow', | ||||
| 				webhookDeliver(webhook, 'unfollow', { | ||||
| 					user: packed, | ||||
| 				}); | ||||
| 			} | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ export async function uploadFromUrl({ | |||
| 	sensitive = false, | ||||
| 	force = false, | ||||
| 	isLink = false, | ||||
| 	comment = null | ||||
| 	comment = null, | ||||
| }: Args): Promise<DriveFile> { | ||||
| 	let name = new URL(url).pathname.split('/').pop() || null; | ||||
| 	if (name == null || !DriveFiles.validateFileName(name)) { | ||||
|  | @ -38,7 +38,7 @@ export async function uploadFromUrl({ | |||
| 
 | ||||
| 	// If the comment is same as the name, skip comment
 | ||||
| 	// (image.name is passed in when receiving attachment)
 | ||||
| 	if (comment !== null && name == comment) { | ||||
| 	if (comment !== null && name === comment) { | ||||
| 		comment = null; | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -97,7 +97,7 @@ async function fetchNodeinfo(instance: Instance): Promise<NodeInfo> { | |||
| 				} else { | ||||
| 					throw e.statusCode || e.message; | ||||
| 				} | ||||
| 			}); | ||||
| 			}) as Record<string, unknown>; | ||||
| 
 | ||||
| 		if (wellknown.links == null || !Array.isArray(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}`); | ||||
| 
 | ||||
| 		return info; | ||||
| 		return info as NodeInfo; | ||||
| 	} catch (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; | ||||
| } | ||||
| 
 | ||||
| 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 manifestUrl = url + '/manifest.json'; | ||||
| 
 | ||||
| 	const manifest = await getJson(manifestUrl); | ||||
| 	const manifest = await getJson(manifestUrl) as Record<string, unknown>; | ||||
| 
 | ||||
| 	return manifest; | ||||
| } | ||||
|  | @ -167,7 +167,8 @@ async function fetchFaviconUrl(instance: Instance, doc: DOMWindow['document'] | | |||
| 	const faviconUrl = url + '/favicon.ico'; | ||||
| 
 | ||||
| 	const favicon = await fetch(faviconUrl, { | ||||
| 		timeout: 10000, | ||||
| 		// TODO
 | ||||
| 		//timeout: 10000,
 | ||||
| 		agent: getAgentByUrl, | ||||
| 	}); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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')); | ||||
| 			for (const webhook of webhooks) { | ||||
| 				webhookDeliver(webhook, { | ||||
| 					type: 'follow', | ||||
| 				webhookDeliver(webhook, 'follow', { | ||||
| 					user: packed, | ||||
| 				}); | ||||
| 			} | ||||
|  | @ -108,12 +107,11 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ | |||
| 	// Publish followed event
 | ||||
| 	if (Users.isLocalUser(followee)) { | ||||
| 		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')); | ||||
| 			for (const webhook of webhooks) { | ||||
| 				webhookDeliver(webhook, { | ||||
| 					type: 'followed', | ||||
| 				webhookDeliver(webhook, 'followed', { | ||||
| 					user: packed, | ||||
| 				}); | ||||
| 			} | ||||
|  |  | |||
|  | @ -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')); | ||||
| 			for (const webhook of webhooks) { | ||||
| 				webhookDeliver(webhook, { | ||||
| 					type: 'unfollow', | ||||
| 				webhookDeliver(webhook, 'unfollow', { | ||||
| 					user: packed, | ||||
| 				}); | ||||
| 			} | ||||
|  |  | |||
|  | @ -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')); | ||||
| 	for (const webhook of webhooks) { | ||||
| 		webhookDeliver(webhook, { | ||||
| 			type: 'unfollow', | ||||
| 		webhookDeliver(webhook, 'unfollow', { | ||||
| 			user: packedFollowee, | ||||
| 		}); | ||||
| 	} | ||||
|  |  | |||
|  | @ -370,8 +370,7 @@ export default async (user: { id: User['id']; username: User['username']; host: | |||
| 		getActiveWebhooks().then(webhooks => { | ||||
| 			webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note')); | ||||
| 			for (const webhook of webhooks) { | ||||
| 				webhookDeliver(webhook, { | ||||
| 					type: 'note', | ||||
| 				webhookDeliver(webhook, 'note', { | ||||
| 					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')); | ||||
| 					for (const webhook of webhooks) { | ||||
| 						webhookDeliver(webhook, { | ||||
| 							type: 'reply', | ||||
| 						webhookDeliver(webhook, 'reply', { | ||||
| 							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')); | ||||
| 				for (const webhook of webhooks) { | ||||
| 					webhookDeliver(webhook, { | ||||
| 						type: 'renote', | ||||
| 					webhookDeliver(webhook, 'renote', { | ||||
| 						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')); | ||||
| 		for (const webhook of webhooks) { | ||||
| 			webhookDeliver(webhook, { | ||||
| 				type: 'mention', | ||||
| 			webhookDeliver(webhook, 'mention', { | ||||
| 				note: detailPackedNote, | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -51,14 +51,14 @@ export function applyTheme(theme: Theme, persist = true) { | |||
| 
 | ||||
| 	if (_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); | ||||
| 
 | ||||
| 	for (const tag of document.head.children) { | ||||
| 		if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { | ||||
| 			tag.setAttribute('content', props['html']); | ||||
| 			tag.setAttribute('content', props['htmlThemeColor']); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue