Merge remote-tracking branch 'misskey-dev/develop' into io

This commit is contained in:
まっちゃとーにゅ 2024-03-28 19:45:50 +09:00
commit 9723d01277
No known key found for this signature in database
GPG Key ID: 6AFBBF529601C1DB
10 changed files with 1873 additions and 1837 deletions

View File

@ -18,6 +18,7 @@
- 実装の都合により、プラグインは1つエラーを起こした時に即時停止するようになりました
- Enhance: ページのデザインを変更
- Enhance: 2要素認証ワンタイムパスワードの入力欄を改善
- Enhance: 「今日誕生日のフォロー中ユーザー」ウィジェットを手動でリロードできるように
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
- Fix: 周年の実績が閏年を考慮しない問題を修正
- Fix: ローカルURLのプレビューポップアップが左上に表示される
@ -27,6 +28,7 @@
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/528)
- Fix: コードブロックのシンタックスハイライトで使用される定義ファイルをCDNから取得するように #13177
- CDNから取得せずMisskey本体にバンドルする場合は`pacakges/frontend/vite.config.ts`を修正してください。
- Fix: タイムゾーンによっては、「今日誕生日のフォロー中ユーザー」ウィジェットが正しく動作しない問題を修正
### Server
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに

2
locales/index.d.ts vendored
View File

@ -4865,7 +4865,7 @@ export interface Locale extends ILocale {
*/
"wellKnownWebsites": string;
/**
* AND指定になりOR指定になります
* AND指定になりOR指定になります
*/
"wellKnownWebsitesDescription": string;
/**

View File

@ -6,6 +6,7 @@
import { IsNull } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository, FollowingsRepository, UserProfilesRepository } from '@/models/_.js';
import { birthdaySchema } from '@/models/User.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
@ -66,7 +67,7 @@ export const paramDef = {
description: 'The local host is represented with `null`.',
},
birthday: { type: 'string', nullable: true },
birthday: { ...birthdaySchema, nullable: true },
},
anyOf: [
{ required: ['userId'] },
@ -127,9 +128,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.birthday) {
try {
const d = new Date(ps.birthday);
d.setHours(0, 0, 0, 0);
const birthday = `${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
const birthday = ps.birthday.substring(5, 10);
const birthdayUserQuery = this.userProfilesRepository.createQueryBuilder('user_profile');
birthdayUserQuery.select('user_profile.userId')
.where(`SUBSTR(user_profile.birthday, 6, 5) = '${birthday}'`);

View File

@ -93,7 +93,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
const hasBody = (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length >= 1);
const info = {
operationId: endpoint.name,
operationId: endpoint.name.replaceAll('/', '___'), // NOTE: スラッシュは使えない
summary: endpoint.name,
description: desc,
externalDocs: {

View File

@ -160,19 +160,17 @@ describe('Streaming', () => {
assert.strictEqual(fired, true);
});
/*
test('フォローしているユーザーの visibility: followers な投稿への返信が流れる', async () => {
const note = await api('notes/create', { text: 'foo', visibility: 'followers' }, kyoko);
const note = await post(kyoko, { text: 'foo', visibility: 'followers' });
const fired = await waitFire(
ayano, 'homeTimeline', // ayano:home
() => api('notes/create', { text: 'bar', visibility: 'followers', replyId: note.body.id }, kyoko), // kyoko posts
() => api('notes/create', { text: 'bar', visibility: 'followers', replyId: note.id }, kyoko), // kyoko posts
msg => msg.type === 'note' && msg.body.userId === kyoko.id && msg.body.reply.text === 'foo',
);
assert.strictEqual(fired, true);
});
*/
test('フォローしているユーザーのフォローしていないユーザーの visibility: followers な投稿への返信が流れない', async () => {
const chitoseNote = await post(chitose, { text: 'followers-only post', visibility: 'followers' });

View File

@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkContainer :showHeader="widgetProps.showHeader" class="mkw-bdayfollowings">
<template #icon><i class="ti ti-cake"></i></template>
<template #header>{{ i18n.ts._widgets.birthdayFollowings }}</template>
<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="actualFetch()"><i class="ti ti-refresh"></i></button></template>
<div :class="$style.bdayFRoot">
<MkLoading v-if="fetching"/>
@ -53,7 +54,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
emit,
);
const users = ref<Misskey.entities.FollowingFolloweePopulated[]>([]);
const users = ref<Misskey.Endpoints['users/following']['res']>([]);
const fetching = ref(true);
let lastFetchedAt = '1970-01-01';
@ -70,19 +71,35 @@ const fetch = () => {
now.setHours(0, 0, 0, 0);
if (now > lfAtD) {
misskeyApi('users/following', {
limit: 18,
birthday: now.toISOString(),
userId: $i.id,
}).then(res => {
users.value = res;
fetching.value = false;
});
actualFetch();
lastFetchedAt = now.toISOString();
}
};
function actualFetch() {
if ($i == null) {
users.value = [];
fetching.value = false;
return;
}
const now = new Date();
now.setHours(0, 0, 0, 0);
fetching.value = true;
misskeyApi('users/following', {
limit: 18,
birthday: `${now.getFullYear().toString().padStart(4, '0')}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`,
userId: $i.id,
}).then(res => {
users.value = res;
window.setTimeout(() => {
//
fetching.value = false;
}, 100);
});
}
useInterval(fetch, 1000 * 60, {
immediate: true,
afterMounted: true,

File diff suppressed because it is too large Load Diff

View File

@ -60,13 +60,17 @@ async function generateEndpoints(
// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
const paths = openApiDocs.paths ?? {};
const postPathItems = Object.keys(paths)
.map(it => paths[it]?.post)
.map(it => ({
_path_: it.replace(/^\//, ''),
...paths[it]?.post,
}))
.filter(filterUndefined);
for (const operation of postPathItems) {
const path = operation._path_;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const operationId = operation.operationId!;
const endpoint = new Endpoint(operationId);
const endpoint = new Endpoint(path);
endpoints.push(endpoint);
if (isRequestBodyObject(operation.requestBody)) {
@ -76,19 +80,21 @@ async function generateEndpoints(
// いまのところ複数のメディアタイプをとるエンドポイントは無いので決め打ちする
endpoint.request = new OperationTypeAlias(
operationId,
path,
supportMediaTypes[0],
OperationsAliasType.REQUEST,
);
}
}
if (isResponseObject(operation.responses['200']) && operation.responses['200'].content) {
if (operation.responses && isResponseObject(operation.responses['200']) && operation.responses['200'].content) {
const resContent = operation.responses['200'].content;
const supportMediaTypes = Object.keys(resContent);
if (supportMediaTypes.length > 0) {
// いまのところ複数のメディアタイプを返すエンドポイントは無いので決め打ちする
endpoint.response = new OperationTypeAlias(
operationId,
path,
supportMediaTypes[0],
OperationsAliasType.RESPONSE,
);
@ -98,6 +104,8 @@ async function generateEndpoints(
const entitiesOutputLine: string[] = [];
entitiesOutputLine.push('/* eslint @typescript-eslint/naming-convention: 0 */');
entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`);
entitiesOutputLine.push('');
@ -138,12 +146,19 @@ async function generateApiClientJSDoc(
endpointsFileName: string,
warningsOutputPath: string,
) {
const endpoints: { operationId: string; description: string; }[] = [];
const endpoints: {
operationId: string;
path: string;
description: string;
}[] = [];
// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
const paths = openApiDocs.paths ?? {};
const postPathItems = Object.keys(paths)
.map(it => paths[it]?.post)
.map(it => ({
_path_: it.replace(/^\//, ''),
...paths[it]?.post,
}))
.filter(filterUndefined);
for (const operation of postPathItems) {
@ -153,6 +168,7 @@ async function generateApiClientJSDoc(
if (operation.description) {
endpoints.push({
operationId: operationId,
path: operation._path_,
description: operation.description,
});
}
@ -173,7 +189,7 @@ async function generateApiClientJSDoc(
' /**',
` * ${endpoint.description.split('\n').join('\n * ')}`,
' */',
` request<E extends '${endpoint.operationId}', P extends Endpoints[E][\'req\']>(`,
` request<E extends '${endpoint.path}', P extends Endpoints[E][\'req\']>(`,
' endpoint: E,',
' params: P,',
' credential?: string | null,',
@ -232,21 +248,24 @@ interface IOperationTypeAlias {
class OperationTypeAlias implements IOperationTypeAlias {
public readonly operationId: string;
public readonly path: string;
public readonly mediaType: string;
public readonly type: OperationsAliasType;
constructor(
operationId: string,
path: string,
mediaType: string,
type: OperationsAliasType,
) {
this.operationId = operationId;
this.path = path;
this.mediaType = mediaType;
this.type = type;
}
generateName(): string {
const nameBase = this.operationId.replace(/\//g, '-');
const nameBase = this.path.replace(/\//g, '-');
return toPascal(nameBase + this.type);
}
@ -279,19 +298,19 @@ const emptyRequest = new EmptyTypeAlias(OperationsAliasType.REQUEST);
const emptyResponse = new EmptyTypeAlias(OperationsAliasType.RESPONSE);
class Endpoint {
public readonly operationId: string;
public readonly path: string;
public request?: IOperationTypeAlias;
public response?: IOperationTypeAlias;
constructor(operationId: string) {
this.operationId = operationId;
constructor(path: string) {
this.path = path;
}
toLine(): string {
const reqName = this.request?.generateName() ?? emptyRequest.generateName();
const resName = this.response?.generateName() ?? emptyResponse.generateName();
return `'${this.operationId}': { req: ${reqName}; res: ${resName} };`;
return `'${this.path}': { req: ${reqName}; res: ${resName} };`;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff