Refator: separate files
This commit is contained in:
		@@ -1,576 +0,0 @@
 | 
			
		||||
import endpoints from './endpoints';
 | 
			
		||||
import { Context } from 'cafy';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
 | 
			
		||||
const basicErrors = {
 | 
			
		||||
	'400': {
 | 
			
		||||
		'INVALID_PARAM': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Invalid param.',
 | 
			
		||||
					code: 'INVALID_PARAM',
 | 
			
		||||
					id: '3d81ceae-475f-4600-b2a8-2bc116157532',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'401': {
 | 
			
		||||
		'CREDENTIAL_REQUIRED': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Credential required.',
 | 
			
		||||
					code: 'CREDENTIAL_REQUIRED',
 | 
			
		||||
					id: '1384574d-a912-4b81-8601-c7b1c4085df1',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'403': {
 | 
			
		||||
		'AUTHENTICATION_FAILED': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Authentication failed. Please ensure your token is correct.',
 | 
			
		||||
					code: 'AUTHENTICATION_FAILED',
 | 
			
		||||
					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'418': {
 | 
			
		||||
		'I_AM_AI': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'You sent a request to Ai-chan, Misskey\'s showgirl, instead of the server.',
 | 
			
		||||
					code: 'I_AM_AI',
 | 
			
		||||
					id: '60c46cd1-f23a-46b1-bebe-5d2b73951a84',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'429': {
 | 
			
		||||
		'RATE_LIMIT_EXCEEDED': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Rate limit exceeded. Please try again later.',
 | 
			
		||||
					code: 'RATE_LIMIT_EXCEEDED',
 | 
			
		||||
					id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'500': {
 | 
			
		||||
		'INTERNAL_ERROR': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Internal error occurred. Please contact us if the error persists.',
 | 
			
		||||
					code: 'INTERNAL_ERROR',
 | 
			
		||||
					id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const schemas = {
 | 
			
		||||
	Error: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			error: {
 | 
			
		||||
				type: 'object',
 | 
			
		||||
				description: 'An error object.',
 | 
			
		||||
				properties: {
 | 
			
		||||
					code: {
 | 
			
		||||
						type: 'string',
 | 
			
		||||
						description: 'An error code.',
 | 
			
		||||
					},
 | 
			
		||||
					message: {
 | 
			
		||||
						type: 'string',
 | 
			
		||||
						description: 'An error message.',
 | 
			
		||||
					},
 | 
			
		||||
					id: {
 | 
			
		||||
						type: 'string',
 | 
			
		||||
						format: 'uuid',
 | 
			
		||||
						description: 'An error ID. This ID is static.',
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				required: ['code', 'id', 'message']
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['error']
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	User: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				description: 'The unique identifier for this User.'
 | 
			
		||||
			},
 | 
			
		||||
			username: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				description: 'The screen name, handle, or alias that this user identifies themselves with.',
 | 
			
		||||
				example: 'ai'
 | 
			
		||||
			},
 | 
			
		||||
			name: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				description: 'The name of the user, as they’ve defined it.',
 | 
			
		||||
				example: '藍'
 | 
			
		||||
			},
 | 
			
		||||
			host: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				example: 'misskey.example.com'
 | 
			
		||||
			},
 | 
			
		||||
			description: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				description: 'The user-defined UTF-8 string describing their account.',
 | 
			
		||||
				example: 'Hi masters, I am Ai!'
 | 
			
		||||
			},
 | 
			
		||||
			createdAt: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'date-time',
 | 
			
		||||
				description: 'The date that the user account was created on Misskey.'
 | 
			
		||||
			},
 | 
			
		||||
			followersCount: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The number of followers this account currently has.'
 | 
			
		||||
			},
 | 
			
		||||
			followingCount: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The number of users this account is following.'
 | 
			
		||||
			},
 | 
			
		||||
			notesCount: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The number of Notes (including renotes) issued by the user.'
 | 
			
		||||
			},
 | 
			
		||||
			isBot: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this account is a bot.'
 | 
			
		||||
			},
 | 
			
		||||
			isCat: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this account is a cat.'
 | 
			
		||||
			},
 | 
			
		||||
			isAdmin: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this account is the admin.'
 | 
			
		||||
			},
 | 
			
		||||
			isVerified: {
 | 
			
		||||
				type: 'boolean'
 | 
			
		||||
			},
 | 
			
		||||
			isLocked: {
 | 
			
		||||
				type: 'boolean'
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['id', 'name', 'username', 'createdAt']
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	Note: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				description: 'The unique identifier for this Note.'
 | 
			
		||||
			},
 | 
			
		||||
			createdAt: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'date-time',
 | 
			
		||||
				description: 'The date that the Note was created on Misskey.'
 | 
			
		||||
			},
 | 
			
		||||
			text: {
 | 
			
		||||
				type: 'string'
 | 
			
		||||
			},
 | 
			
		||||
			cw: {
 | 
			
		||||
				type: 'string'
 | 
			
		||||
			},
 | 
			
		||||
			userId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
			},
 | 
			
		||||
			user: {
 | 
			
		||||
				$ref: '#/components/schemas/User'
 | 
			
		||||
			},
 | 
			
		||||
			replyId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
			},
 | 
			
		||||
			renoteId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
			},
 | 
			
		||||
			reply: {
 | 
			
		||||
				$ref: '#/components/schemas/Note'
 | 
			
		||||
			},
 | 
			
		||||
			renote: {
 | 
			
		||||
				$ref: '#/components/schemas/Note'
 | 
			
		||||
			},
 | 
			
		||||
			viaMobile: {
 | 
			
		||||
				type: 'boolean'
 | 
			
		||||
			},
 | 
			
		||||
			visibility: {
 | 
			
		||||
				type: 'string'
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['id', 'userId', 'createdAt']
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	DriveFile: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				description: 'The unique identifier for this Drive file.'
 | 
			
		||||
			},
 | 
			
		||||
			createdAt: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'date-time',
 | 
			
		||||
				description: 'The date that the Drive file was created on Misskey.'
 | 
			
		||||
			},
 | 
			
		||||
			name: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				description: 'The file name with extension.',
 | 
			
		||||
				example: 'lenna.jpg'
 | 
			
		||||
			},
 | 
			
		||||
			type: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				description: 'The MIME type of this Drive file.',
 | 
			
		||||
				example: 'image/jpeg'
 | 
			
		||||
			},
 | 
			
		||||
			md5: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'md5',
 | 
			
		||||
				description: 'The MD5 hash of this Drive file.',
 | 
			
		||||
				example: '15eca7fba0480996e2245f5185bf39f2'
 | 
			
		||||
			},
 | 
			
		||||
			datasize: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The size of this Drive file. (bytes)',
 | 
			
		||||
				example: 51469
 | 
			
		||||
			},
 | 
			
		||||
			folderId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				description: 'The parent folder ID of this Drive file.',
 | 
			
		||||
			},
 | 
			
		||||
			isSensitive: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this Drive file is sensitive.',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['id', 'createdAt', 'name', 'type', 'datasize', 'md5']
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const desc = `
 | 
			
		||||
## Usage
 | 
			
		||||
APIはすべてPOSTでリクエスト/レスポンスともにJSON形式です。
 | 
			
		||||
一部のAPIは認証情報(アクセストークン)が必要です。リクエストの際に\`i\`というパラメータでアクセストークンを添付してください。
 | 
			
		||||
 | 
			
		||||
### アクセストークンを取得する
 | 
			
		||||
#### 自分のアカウントのアクセストークンを取得する
 | 
			
		||||
「設定 > API」で、自分のアクセストークンを取得できます。
 | 
			
		||||
 | 
			
		||||
> アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。
 | 
			
		||||
 | 
			
		||||
### アプリケーションとしてアクセストークンを取得する
 | 
			
		||||
直接ユーザーのアクセストークンをアプリケーションが扱うのは危険なので、
 | 
			
		||||
アプリケーションからAPIを利用する際には、アプリケーションとアプリケーションを利用するユーザーが結び付けられた専用のアクセストークンをMisskeyに発行してもらいます。
 | 
			
		||||
 | 
			
		||||
#### 1.アプリケーションを登録する
 | 
			
		||||
まず、あなたのアプリケーションやWebサービス(以後、あなたのアプリと呼びます)をMisskeyに登録します。
 | 
			
		||||
[デベロッパーセンター](/dev)にアクセスし、「アプリ > アプリ作成」からアプリを作成してください。
 | 
			
		||||
フォームの記入欄の説明は以下の通りです:
 | 
			
		||||
 | 
			
		||||
| 名前 | 説明 |
 | 
			
		||||
|---|---|
 | 
			
		||||
| アプリケーション名 | あなたのアプリの名称。 |
 | 
			
		||||
| アプリの概要 | あなたのアプリの簡単な説明や紹介。 |
 | 
			
		||||
| コールバックURL | ユーザーが後述する認証フォームで認証を終えた際にリダイレクトするURLを設定できます。あなたのアプリがWebサービスである場合に有用です。 |
 | 
			
		||||
| 権限 | あなたのアプリが要求する権限。ここで要求した機能だけがAPIからアクセスできます。 |
 | 
			
		||||
 | 
			
		||||
登録が済むとあなたのアプリのシークレットキーが入手できます。このシークレットキーは後で使用します。
 | 
			
		||||
 | 
			
		||||
> アプリに成りすまされる可能性があるため、極力このシークレットキーは公開しないようにしてください。</p>
 | 
			
		||||
 | 
			
		||||
#### 2.ユーザーに認証させる
 | 
			
		||||
アプリを使ってもらうには、ユーザーにアカウントへのアクセスの許可をもらう必要があります。
 | 
			
		||||
 | 
			
		||||
認証セッションを開始するには、%API_URL%/auth/session/generate へパラメータに appSecret としてシークレットキーを含めたリクエストを送信します。
 | 
			
		||||
リクエスト形式はJSONで、メソッドはPOSTです。
 | 
			
		||||
レスポンスとして認証セッションのトークンや認証フォームのURLが取得できるので、認証フォームのURLをブラウザで表示し、ユーザーにフォームを提示してください。
 | 
			
		||||
 | 
			
		||||
あなたのアプリがコールバックURLを設定している場合、
 | 
			
		||||
ユーザーがあなたのアプリの連携を許可すると設定しているコールバックURLに token という名前でセッションのトークンが含まれたクエリを付けてリダイレクトします。
 | 
			
		||||
 | 
			
		||||
あなたのアプリがコールバックURLを設定していない場合、ユーザーがあなたのアプリの連携を許可したことを(何らかの方法で(たとえばボタンを押させるなど))確認出来るようにしてください。
 | 
			
		||||
 | 
			
		||||
#### 3.ユーザートークンを取得する
 | 
			
		||||
ユーザーが連携を許可したら、%API_URL%/auth/session/userkey へ次のパラメータを含むリクエストを送信します:
 | 
			
		||||
 | 
			
		||||
| 名前 | 型 | 説明 |
 | 
			
		||||
|---|---|---|
 | 
			
		||||
| appSecret | string | アプリのシークレットキー |
 | 
			
		||||
| token | string | セッションのトークン |
 | 
			
		||||
 | 
			
		||||
上手くいけば、認証したユーザーのユーザートークンがレスポンスとして取得できます。おめでとうございます!
 | 
			
		||||
 | 
			
		||||
ユーザートークンが取得できたら、「ユーザーのユーザートークン+あなたのアプリのシークレットキーをsha256したもの」をアクセストークンとして、APIにリクエストできます。
 | 
			
		||||
 | 
			
		||||
アクセストークンの生成方法を擬似コードで表すと次のようになります:
 | 
			
		||||
<pre><code>const i = sha256(userToken + secretKey);</code></pre>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export function genOpenapiSpec(lang = 'ja-JP') {
 | 
			
		||||
	const spec = {
 | 
			
		||||
		openapi: '3.0.0',
 | 
			
		||||
 | 
			
		||||
		info: {
 | 
			
		||||
			version: 'v1',
 | 
			
		||||
			title: 'Misskey API',
 | 
			
		||||
			description: '**Misskey is a decentralized microblogging platform.**\n\n' + desc,
 | 
			
		||||
			'x-logo': { url: '/assets/api-doc.png' }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		externalDocs: {
 | 
			
		||||
			description: 'Repository',
 | 
			
		||||
			url: 'https://github.com/syuilo/misskey'
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		servers: [{
 | 
			
		||||
			url: config.api_url
 | 
			
		||||
		}],
 | 
			
		||||
 | 
			
		||||
		paths: {} as any,
 | 
			
		||||
 | 
			
		||||
		components: {
 | 
			
		||||
			schemas: schemas,
 | 
			
		||||
 | 
			
		||||
			securitySchemes: {
 | 
			
		||||
				ApiKeyAuth: {
 | 
			
		||||
					type: 'apiKey',
 | 
			
		||||
					in: 'body',
 | 
			
		||||
					name: 'i'
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	function genProps(props: { [key: string]: Context & { desc: any, default: any }; }) {
 | 
			
		||||
		const properties = {} as any;
 | 
			
		||||
 | 
			
		||||
		const kvs = Object.entries(props);
 | 
			
		||||
 | 
			
		||||
		for (const kv of kvs) {
 | 
			
		||||
			properties[kv[0]] = genProp(kv[1], kv[1].desc, kv[1].default);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return properties;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function genProp(param: Context, desc?: string, _default?: any): any {
 | 
			
		||||
		const required = param.name === 'Object' ? (param as any).props ? Object.entries((param as any).props).filter(([k, v]: any) => !v.isOptional).map(([k, v]) => k) : [] : [];
 | 
			
		||||
		return {
 | 
			
		||||
			description: desc,
 | 
			
		||||
			default: _default,
 | 
			
		||||
			...(_default ? { default: _default } : {}),
 | 
			
		||||
			type: param.name === 'ID' ? 'string' : param.name.toLowerCase(),
 | 
			
		||||
			...(param.name === 'ID' ? { example: 'xxxxxxxxxxxxxxxxxxxxxxxx', format: 'id' } : {}),
 | 
			
		||||
			nullable: param.isNullable,
 | 
			
		||||
			...(param.name === 'String' ? {
 | 
			
		||||
				...((param as any).enum ? { enum: (param as any).enum } : {}),
 | 
			
		||||
				...((param as any).minLength ? { minLength: (param as any).minLength } : {}),
 | 
			
		||||
				...((param as any).maxLength ? { maxLength: (param as any).maxLength } : {}),
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(param.name === 'Number' ? {
 | 
			
		||||
				...((param as any).minimum ? { minimum: (param as any).minimum } : {}),
 | 
			
		||||
				...((param as any).maximum ? { maximum: (param as any).maximum } : {}),
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(param.name === 'Object' ? {
 | 
			
		||||
				...(required.length > 0 ? { required } : {}),
 | 
			
		||||
				properties: (param as any).props ? genProps((param as any).props) : {}
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(param.name === 'Array' ? {
 | 
			
		||||
				items: (param as any).ctx ? genProp((param as any).ctx) : {}
 | 
			
		||||
			} : {})
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
 | 
			
		||||
		const porops = {} as any;
 | 
			
		||||
		const errors = {} as any;
 | 
			
		||||
 | 
			
		||||
		if (endpoint.meta.errors) {
 | 
			
		||||
			for (const e of Object.values(endpoint.meta.errors)) {
 | 
			
		||||
				errors[e.code] = {
 | 
			
		||||
					value: {
 | 
			
		||||
						error: e
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (endpoint.meta.params) {
 | 
			
		||||
			for (const kv of Object.entries(endpoint.meta.params)) {
 | 
			
		||||
				if (kv[1].desc) (kv[1].validator as any).desc = kv[1].desc[lang];
 | 
			
		||||
				if (kv[1].default) (kv[1].validator as any).default = kv[1].default;
 | 
			
		||||
				porops[kv[0]] = kv[1].validator;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];
 | 
			
		||||
 | 
			
		||||
		const resSchema = endpoint.meta.res ? renderType(endpoint.meta.res) : {};
 | 
			
		||||
 | 
			
		||||
		function renderType(x: any) {
 | 
			
		||||
			const res = {} as any;
 | 
			
		||||
 | 
			
		||||
			if (['User', 'Note', 'DriveFile'].includes(x.type)) {
 | 
			
		||||
				res['$ref'] = `#/components/schemas/${x.type}`;
 | 
			
		||||
			} else if (x.type === 'object') {
 | 
			
		||||
				res['type'] = 'object';
 | 
			
		||||
				if (x.props) {
 | 
			
		||||
					const props = {} as any;
 | 
			
		||||
					for (const kv of Object.entries(x.props)) {
 | 
			
		||||
						props[kv[0]] = renderType(kv[1]);
 | 
			
		||||
					}
 | 
			
		||||
					res['properties'] = props;
 | 
			
		||||
				}
 | 
			
		||||
			} else if (x.type === 'array') {
 | 
			
		||||
				res['type'] = 'array';
 | 
			
		||||
				if (x.items) {
 | 
			
		||||
					res['items'] = renderType(x.items);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				res['type'] = x.type;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return res;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const info = {
 | 
			
		||||
			operationId: endpoint.name,
 | 
			
		||||
			summary: endpoint.name,
 | 
			
		||||
			description: endpoint.meta.desc ? endpoint.meta.desc[lang] : 'No description provided.',
 | 
			
		||||
			externalDocs: {
 | 
			
		||||
				description: 'Source code',
 | 
			
		||||
				url: `https://github.com/syuilo/misskey/blob/develop/src/server/api/endpoints/${endpoint.name}.ts`
 | 
			
		||||
			},
 | 
			
		||||
			...(endpoint.meta.tags ? {
 | 
			
		||||
				tags: endpoint.meta.tags
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(endpoint.meta.requireCredential ? {
 | 
			
		||||
				security: [{
 | 
			
		||||
					ApiKeyAuth: []
 | 
			
		||||
				}]
 | 
			
		||||
			} : {}),
 | 
			
		||||
			requestBody: {
 | 
			
		||||
				required: true,
 | 
			
		||||
				content: {
 | 
			
		||||
					'application/json': {
 | 
			
		||||
						schema: {
 | 
			
		||||
							type: 'object',
 | 
			
		||||
							...(required.length > 0 ? { required } : {}),
 | 
			
		||||
							properties: endpoint.meta.params ? genProps(porops) : {}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			responses: {
 | 
			
		||||
				...(endpoint.meta.res ? {
 | 
			
		||||
					'200': {
 | 
			
		||||
						description: 'OK (with results)',
 | 
			
		||||
						content: {
 | 
			
		||||
							'application/json': {
 | 
			
		||||
								schema: resSchema
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} : {
 | 
			
		||||
					'204': {
 | 
			
		||||
						description: 'OK (without any results)',
 | 
			
		||||
					}
 | 
			
		||||
				}),
 | 
			
		||||
				'400': {
 | 
			
		||||
					description: 'Client error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: { ...errors, ...basicErrors['400'] }
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				'401': {
 | 
			
		||||
					description: 'Authentication error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['401']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				'403': {
 | 
			
		||||
					description: 'Forbiddon error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['403']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				'418': {
 | 
			
		||||
					description: 'I\'m Ai',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['418']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				...(endpoint.meta.limit ? {
 | 
			
		||||
					'429': {
 | 
			
		||||
						description: 'To many requests',
 | 
			
		||||
						content: {
 | 
			
		||||
							'application/json': {
 | 
			
		||||
								schema: {
 | 
			
		||||
									$ref: '#/components/schemas/Error'
 | 
			
		||||
								},
 | 
			
		||||
								examples: basicErrors['429']
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} : {}),
 | 
			
		||||
				'500': {
 | 
			
		||||
					description: 'Internal server error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['500']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		spec.paths['/' + endpoint.name] = {
 | 
			
		||||
			post: info
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return spec;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								src/server/api/openapi/description.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/server/api/openapi/description.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
export const description = `
 | 
			
		||||
## Usage
 | 
			
		||||
APIはすべてPOSTでリクエスト/レスポンスともにJSON形式です。
 | 
			
		||||
一部のAPIは認証情報(アクセストークン)が必要です。リクエストの際に\`i\`というパラメータでアクセストークンを添付してください。
 | 
			
		||||
 | 
			
		||||
### アクセストークンを取得する
 | 
			
		||||
#### 自分のアカウントのアクセストークンを取得する
 | 
			
		||||
「設定 > API」で、自分のアクセストークンを取得できます。
 | 
			
		||||
 | 
			
		||||
> アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。
 | 
			
		||||
 | 
			
		||||
### アプリケーションとしてアクセストークンを取得する
 | 
			
		||||
直接ユーザーのアクセストークンをアプリケーションが扱うのは危険なので、
 | 
			
		||||
アプリケーションからAPIを利用する際には、アプリケーションとアプリケーションを利用するユーザーが結び付けられた専用のアクセストークンをMisskeyに発行してもらいます。
 | 
			
		||||
 | 
			
		||||
#### 1.アプリケーションを登録する
 | 
			
		||||
まず、あなたのアプリケーションやWebサービス(以後、あなたのアプリと呼びます)をMisskeyに登録します。
 | 
			
		||||
[デベロッパーセンター](/dev)にアクセスし、「アプリ > アプリ作成」からアプリを作成してください。
 | 
			
		||||
フォームの記入欄の説明は以下の通りです:
 | 
			
		||||
 | 
			
		||||
| 名前 | 説明 |
 | 
			
		||||
|---|---|
 | 
			
		||||
| アプリケーション名 | あなたのアプリの名称。 |
 | 
			
		||||
| アプリの概要 | あなたのアプリの簡単な説明や紹介。 |
 | 
			
		||||
| コールバックURL | ユーザーが後述する認証フォームで認証を終えた際にリダイレクトするURLを設定できます。あなたのアプリがWebサービスである場合に有用です。 |
 | 
			
		||||
| 権限 | あなたのアプリが要求する権限。ここで要求した機能だけがAPIからアクセスできます。 |
 | 
			
		||||
 | 
			
		||||
登録が済むとあなたのアプリのシークレットキーが入手できます。このシークレットキーは後で使用します。
 | 
			
		||||
 | 
			
		||||
> アプリに成りすまされる可能性があるため、極力このシークレットキーは公開しないようにしてください。</p>
 | 
			
		||||
 | 
			
		||||
#### 2.ユーザーに認証させる
 | 
			
		||||
アプリを使ってもらうには、ユーザーにアカウントへのアクセスの許可をもらう必要があります。
 | 
			
		||||
 | 
			
		||||
認証セッションを開始するには、%API_URL%/auth/session/generate へパラメータに appSecret としてシークレットキーを含めたリクエストを送信します。
 | 
			
		||||
リクエスト形式はJSONで、メソッドはPOSTです。
 | 
			
		||||
レスポンスとして認証セッションのトークンや認証フォームのURLが取得できるので、認証フォームのURLをブラウザで表示し、ユーザーにフォームを提示してください。
 | 
			
		||||
 | 
			
		||||
あなたのアプリがコールバックURLを設定している場合、
 | 
			
		||||
ユーザーがあなたのアプリの連携を許可すると設定しているコールバックURLに token という名前でセッションのトークンが含まれたクエリを付けてリダイレクトします。
 | 
			
		||||
 | 
			
		||||
あなたのアプリがコールバックURLを設定していない場合、ユーザーがあなたのアプリの連携を許可したことを(何らかの方法で(たとえばボタンを押させるなど))確認出来るようにしてください。
 | 
			
		||||
 | 
			
		||||
#### 3.ユーザートークンを取得する
 | 
			
		||||
ユーザーが連携を許可したら、%API_URL%/auth/session/userkey へ次のパラメータを含むリクエストを送信します:
 | 
			
		||||
 | 
			
		||||
| 名前 | 型 | 説明 |
 | 
			
		||||
|---|---|---|
 | 
			
		||||
| appSecret | string | アプリのシークレットキー |
 | 
			
		||||
| token | string | セッションのトークン |
 | 
			
		||||
 | 
			
		||||
上手くいけば、認証したユーザーのユーザートークンがレスポンスとして取得できます。おめでとうございます!
 | 
			
		||||
 | 
			
		||||
ユーザートークンが取得できたら、「ユーザーのユーザートークン+あなたのアプリのシークレットキーをsha256したもの」をアクセストークンとして、APIにリクエストできます。
 | 
			
		||||
 | 
			
		||||
アクセストークンの生成方法を擬似コードで表すと次のようになります:
 | 
			
		||||
<pre><code>const i = sha256(userToken + secretKey);</code></pre>
 | 
			
		||||
`;
 | 
			
		||||
							
								
								
									
										69
									
								
								src/server/api/openapi/errors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/server/api/openapi/errors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
 | 
			
		||||
export const errors = {
 | 
			
		||||
	'400': {
 | 
			
		||||
		'INVALID_PARAM': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Invalid param.',
 | 
			
		||||
					code: 'INVALID_PARAM',
 | 
			
		||||
					id: '3d81ceae-475f-4600-b2a8-2bc116157532',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'401': {
 | 
			
		||||
		'CREDENTIAL_REQUIRED': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Credential required.',
 | 
			
		||||
					code: 'CREDENTIAL_REQUIRED',
 | 
			
		||||
					id: '1384574d-a912-4b81-8601-c7b1c4085df1',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'403': {
 | 
			
		||||
		'AUTHENTICATION_FAILED': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Authentication failed. Please ensure your token is correct.',
 | 
			
		||||
					code: 'AUTHENTICATION_FAILED',
 | 
			
		||||
					id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'418': {
 | 
			
		||||
		'I_AM_AI': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'You sent a request to Ai-chan, Misskey\'s showgirl, instead of the server.',
 | 
			
		||||
					code: 'I_AM_AI',
 | 
			
		||||
					id: '60c46cd1-f23a-46b1-bebe-5d2b73951a84',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'429': {
 | 
			
		||||
		'RATE_LIMIT_EXCEEDED': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Rate limit exceeded. Please try again later.',
 | 
			
		||||
					code: 'RATE_LIMIT_EXCEEDED',
 | 
			
		||||
					id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	'500': {
 | 
			
		||||
		'INTERNAL_ERROR': {
 | 
			
		||||
			value: {
 | 
			
		||||
				error: {
 | 
			
		||||
					message: 'Internal error occurred. Please contact us if the error persists.',
 | 
			
		||||
					code: 'INTERNAL_ERROR',
 | 
			
		||||
					id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										255
									
								
								src/server/api/openapi/gen-spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								src/server/api/openapi/gen-spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,255 @@
 | 
			
		||||
import endpoints from '../endpoints';
 | 
			
		||||
import { Context } from 'cafy';
 | 
			
		||||
import config from '../../../config';
 | 
			
		||||
import { errors as basicErrors } from './errors';
 | 
			
		||||
import { schemas } from './schemas';
 | 
			
		||||
import { description } from './description';
 | 
			
		||||
 | 
			
		||||
export function genOpenapiSpec(lang = 'ja-JP') {
 | 
			
		||||
	const spec = {
 | 
			
		||||
		openapi: '3.0.0',
 | 
			
		||||
 | 
			
		||||
		info: {
 | 
			
		||||
			version: 'v1',
 | 
			
		||||
			title: 'Misskey API',
 | 
			
		||||
			description: '**Misskey is a decentralized microblogging platform.**\n\n' + description,
 | 
			
		||||
			'x-logo': { url: '/assets/api-doc.png' }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		externalDocs: {
 | 
			
		||||
			description: 'Repository',
 | 
			
		||||
			url: 'https://github.com/syuilo/misskey'
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		servers: [{
 | 
			
		||||
			url: config.api_url
 | 
			
		||||
		}],
 | 
			
		||||
 | 
			
		||||
		paths: {} as any,
 | 
			
		||||
 | 
			
		||||
		components: {
 | 
			
		||||
			schemas: schemas,
 | 
			
		||||
 | 
			
		||||
			securitySchemes: {
 | 
			
		||||
				ApiKeyAuth: {
 | 
			
		||||
					type: 'apiKey',
 | 
			
		||||
					in: 'body',
 | 
			
		||||
					name: 'i'
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	function genProps(props: { [key: string]: Context & { desc: any, default: any }; }) {
 | 
			
		||||
		const properties = {} as any;
 | 
			
		||||
 | 
			
		||||
		const kvs = Object.entries(props);
 | 
			
		||||
 | 
			
		||||
		for (const kv of kvs) {
 | 
			
		||||
			properties[kv[0]] = genProp(kv[1], kv[1].desc, kv[1].default);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return properties;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function genProp(param: Context, desc?: string, _default?: any): any {
 | 
			
		||||
		const required = param.name === 'Object' ? (param as any).props ? Object.entries((param as any).props).filter(([k, v]: any) => !v.isOptional).map(([k, v]) => k) : [] : [];
 | 
			
		||||
		return {
 | 
			
		||||
			description: desc,
 | 
			
		||||
			default: _default,
 | 
			
		||||
			...(_default ? { default: _default } : {}),
 | 
			
		||||
			type: param.name === 'ID' ? 'string' : param.name.toLowerCase(),
 | 
			
		||||
			...(param.name === 'ID' ? { example: 'xxxxxxxxxxxxxxxxxxxxxxxx', format: 'id' } : {}),
 | 
			
		||||
			nullable: param.isNullable,
 | 
			
		||||
			...(param.name === 'String' ? {
 | 
			
		||||
				...((param as any).enum ? { enum: (param as any).enum } : {}),
 | 
			
		||||
				...((param as any).minLength ? { minLength: (param as any).minLength } : {}),
 | 
			
		||||
				...((param as any).maxLength ? { maxLength: (param as any).maxLength } : {}),
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(param.name === 'Number' ? {
 | 
			
		||||
				...((param as any).minimum ? { minimum: (param as any).minimum } : {}),
 | 
			
		||||
				...((param as any).maximum ? { maximum: (param as any).maximum } : {}),
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(param.name === 'Object' ? {
 | 
			
		||||
				...(required.length > 0 ? { required } : {}),
 | 
			
		||||
				properties: (param as any).props ? genProps((param as any).props) : {}
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(param.name === 'Array' ? {
 | 
			
		||||
				items: (param as any).ctx ? genProp((param as any).ctx) : {}
 | 
			
		||||
			} : {})
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
 | 
			
		||||
		const porops = {} as any;
 | 
			
		||||
		const errors = {} as any;
 | 
			
		||||
 | 
			
		||||
		if (endpoint.meta.errors) {
 | 
			
		||||
			for (const e of Object.values(endpoint.meta.errors)) {
 | 
			
		||||
				errors[e.code] = {
 | 
			
		||||
					value: {
 | 
			
		||||
						error: e
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (endpoint.meta.params) {
 | 
			
		||||
			for (const kv of Object.entries(endpoint.meta.params)) {
 | 
			
		||||
				if (kv[1].desc) (kv[1].validator as any).desc = kv[1].desc[lang];
 | 
			
		||||
				if (kv[1].default) (kv[1].validator as any).default = kv[1].default;
 | 
			
		||||
				porops[kv[0]] = kv[1].validator;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];
 | 
			
		||||
 | 
			
		||||
		const resSchema = endpoint.meta.res ? renderType(endpoint.meta.res) : {};
 | 
			
		||||
 | 
			
		||||
		function renderType(x: any) {
 | 
			
		||||
			const res = {} as any;
 | 
			
		||||
 | 
			
		||||
			if (['User', 'Note', 'DriveFile'].includes(x.type)) {
 | 
			
		||||
				res['$ref'] = `#/components/schemas/${x.type}`;
 | 
			
		||||
			} else if (x.type === 'object') {
 | 
			
		||||
				res['type'] = 'object';
 | 
			
		||||
				if (x.props) {
 | 
			
		||||
					const props = {} as any;
 | 
			
		||||
					for (const kv of Object.entries(x.props)) {
 | 
			
		||||
						props[kv[0]] = renderType(kv[1]);
 | 
			
		||||
					}
 | 
			
		||||
					res['properties'] = props;
 | 
			
		||||
				}
 | 
			
		||||
			} else if (x.type === 'array') {
 | 
			
		||||
				res['type'] = 'array';
 | 
			
		||||
				if (x.items) {
 | 
			
		||||
					res['items'] = renderType(x.items);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				res['type'] = x.type;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return res;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const info = {
 | 
			
		||||
			operationId: endpoint.name,
 | 
			
		||||
			summary: endpoint.name,
 | 
			
		||||
			description: endpoint.meta.desc ? endpoint.meta.desc[lang] : 'No description provided.',
 | 
			
		||||
			externalDocs: {
 | 
			
		||||
				description: 'Source code',
 | 
			
		||||
				url: `https://github.com/syuilo/misskey/blob/develop/src/server/api/endpoints/${endpoint.name}.ts`
 | 
			
		||||
			},
 | 
			
		||||
			...(endpoint.meta.tags ? {
 | 
			
		||||
				tags: endpoint.meta.tags
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(endpoint.meta.requireCredential ? {
 | 
			
		||||
				security: [{
 | 
			
		||||
					ApiKeyAuth: []
 | 
			
		||||
				}]
 | 
			
		||||
			} : {}),
 | 
			
		||||
			requestBody: {
 | 
			
		||||
				required: true,
 | 
			
		||||
				content: {
 | 
			
		||||
					'application/json': {
 | 
			
		||||
						schema: {
 | 
			
		||||
							type: 'object',
 | 
			
		||||
							...(required.length > 0 ? { required } : {}),
 | 
			
		||||
							properties: endpoint.meta.params ? genProps(porops) : {}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			responses: {
 | 
			
		||||
				...(endpoint.meta.res ? {
 | 
			
		||||
					'200': {
 | 
			
		||||
						description: 'OK (with results)',
 | 
			
		||||
						content: {
 | 
			
		||||
							'application/json': {
 | 
			
		||||
								schema: resSchema
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} : {
 | 
			
		||||
					'204': {
 | 
			
		||||
						description: 'OK (without any results)',
 | 
			
		||||
					}
 | 
			
		||||
				}),
 | 
			
		||||
				'400': {
 | 
			
		||||
					description: 'Client error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: { ...errors, ...basicErrors['400'] }
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				'401': {
 | 
			
		||||
					description: 'Authentication error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['401']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				'403': {
 | 
			
		||||
					description: 'Forbiddon error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['403']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				'418': {
 | 
			
		||||
					description: 'I\'m Ai',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['418']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				...(endpoint.meta.limit ? {
 | 
			
		||||
					'429': {
 | 
			
		||||
						description: 'To many requests',
 | 
			
		||||
						content: {
 | 
			
		||||
							'application/json': {
 | 
			
		||||
								schema: {
 | 
			
		||||
									$ref: '#/components/schemas/Error'
 | 
			
		||||
								},
 | 
			
		||||
								examples: basicErrors['429']
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} : {}),
 | 
			
		||||
				'500': {
 | 
			
		||||
					description: 'Internal server error',
 | 
			
		||||
					content: {
 | 
			
		||||
						'application/json': {
 | 
			
		||||
							schema: {
 | 
			
		||||
								$ref: '#/components/schemas/Error'
 | 
			
		||||
							},
 | 
			
		||||
							examples: basicErrors['500']
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		spec.paths['/' + endpoint.name] = {
 | 
			
		||||
			post: info
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return spec;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										196
									
								
								src/server/api/openapi/schemas.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/server/api/openapi/schemas.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,196 @@
 | 
			
		||||
 | 
			
		||||
export const schemas = {
 | 
			
		||||
	Error: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			error: {
 | 
			
		||||
				type: 'object',
 | 
			
		||||
				description: 'An error object.',
 | 
			
		||||
				properties: {
 | 
			
		||||
					code: {
 | 
			
		||||
						type: 'string',
 | 
			
		||||
						description: 'An error code.',
 | 
			
		||||
					},
 | 
			
		||||
					message: {
 | 
			
		||||
						type: 'string',
 | 
			
		||||
						description: 'An error message.',
 | 
			
		||||
					},
 | 
			
		||||
					id: {
 | 
			
		||||
						type: 'string',
 | 
			
		||||
						format: 'uuid',
 | 
			
		||||
						description: 'An error ID. This ID is static.',
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				required: ['code', 'id', 'message']
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['error']
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	User: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				description: 'The unique identifier for this User.'
 | 
			
		||||
			},
 | 
			
		||||
			username: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				description: 'The screen name, handle, or alias that this user identifies themselves with.',
 | 
			
		||||
				example: 'ai'
 | 
			
		||||
			},
 | 
			
		||||
			name: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				description: 'The name of the user, as they’ve defined it.',
 | 
			
		||||
				example: '藍'
 | 
			
		||||
			},
 | 
			
		||||
			host: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				example: 'misskey.example.com'
 | 
			
		||||
			},
 | 
			
		||||
			description: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				description: 'The user-defined UTF-8 string describing their account.',
 | 
			
		||||
				example: 'Hi masters, I am Ai!'
 | 
			
		||||
			},
 | 
			
		||||
			createdAt: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'date-time',
 | 
			
		||||
				description: 'The date that the user account was created on Misskey.'
 | 
			
		||||
			},
 | 
			
		||||
			followersCount: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The number of followers this account currently has.'
 | 
			
		||||
			},
 | 
			
		||||
			followingCount: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The number of users this account is following.'
 | 
			
		||||
			},
 | 
			
		||||
			notesCount: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The number of Notes (including renotes) issued by the user.'
 | 
			
		||||
			},
 | 
			
		||||
			isBot: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this account is a bot.'
 | 
			
		||||
			},
 | 
			
		||||
			isCat: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this account is a cat.'
 | 
			
		||||
			},
 | 
			
		||||
			isAdmin: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this account is the admin.'
 | 
			
		||||
			},
 | 
			
		||||
			isVerified: {
 | 
			
		||||
				type: 'boolean'
 | 
			
		||||
			},
 | 
			
		||||
			isLocked: {
 | 
			
		||||
				type: 'boolean'
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['id', 'name', 'username', 'createdAt']
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	Note: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				description: 'The unique identifier for this Note.'
 | 
			
		||||
			},
 | 
			
		||||
			createdAt: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'date-time',
 | 
			
		||||
				description: 'The date that the Note was created on Misskey.'
 | 
			
		||||
			},
 | 
			
		||||
			text: {
 | 
			
		||||
				type: 'string'
 | 
			
		||||
			},
 | 
			
		||||
			cw: {
 | 
			
		||||
				type: 'string'
 | 
			
		||||
			},
 | 
			
		||||
			userId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
			},
 | 
			
		||||
			user: {
 | 
			
		||||
				$ref: '#/components/schemas/User'
 | 
			
		||||
			},
 | 
			
		||||
			replyId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
			},
 | 
			
		||||
			renoteId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
			},
 | 
			
		||||
			reply: {
 | 
			
		||||
				$ref: '#/components/schemas/Note'
 | 
			
		||||
			},
 | 
			
		||||
			renote: {
 | 
			
		||||
				$ref: '#/components/schemas/Note'
 | 
			
		||||
			},
 | 
			
		||||
			viaMobile: {
 | 
			
		||||
				type: 'boolean'
 | 
			
		||||
			},
 | 
			
		||||
			visibility: {
 | 
			
		||||
				type: 'string'
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['id', 'userId', 'createdAt']
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	DriveFile: {
 | 
			
		||||
		type: 'object',
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				description: 'The unique identifier for this Drive file.'
 | 
			
		||||
			},
 | 
			
		||||
			createdAt: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'date-time',
 | 
			
		||||
				description: 'The date that the Drive file was created on Misskey.'
 | 
			
		||||
			},
 | 
			
		||||
			name: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				description: 'The file name with extension.',
 | 
			
		||||
				example: 'lenna.jpg'
 | 
			
		||||
			},
 | 
			
		||||
			type: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				description: 'The MIME type of this Drive file.',
 | 
			
		||||
				example: 'image/jpeg'
 | 
			
		||||
			},
 | 
			
		||||
			md5: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'md5',
 | 
			
		||||
				description: 'The MD5 hash of this Drive file.',
 | 
			
		||||
				example: '15eca7fba0480996e2245f5185bf39f2'
 | 
			
		||||
			},
 | 
			
		||||
			datasize: {
 | 
			
		||||
				type: 'number',
 | 
			
		||||
				description: 'The size of this Drive file. (bytes)',
 | 
			
		||||
				example: 51469
 | 
			
		||||
			},
 | 
			
		||||
			folderId: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
				format: 'id',
 | 
			
		||||
				nullable: true,
 | 
			
		||||
				description: 'The parent folder ID of this Drive file.',
 | 
			
		||||
			},
 | 
			
		||||
			isSensitive: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				description: 'Whether this Drive file is sensitive.',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		required: ['id', 'createdAt', 'name', 'type', 'datasize', 'md5']
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@@ -21,7 +21,7 @@ import getNoteSummary from '../../misc/get-note-summary';
 | 
			
		||||
import fetchMeta from '../../misc/fetch-meta';
 | 
			
		||||
import Emoji from '../../models/emoji';
 | 
			
		||||
import * as pkg from '../../../package.json';
 | 
			
		||||
import { genOpenapiSpec } from '../api/gen-openapi-spec';
 | 
			
		||||
import { genOpenapiSpec } from '../api/openapi/gen-spec';
 | 
			
		||||
 | 
			
		||||
const client = `${__dirname}/../../client/`;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user