Merge branch 'develop' into feat-1714
This commit is contained in:
commit
fa356fa149
|
@ -40,6 +40,9 @@
|
|||
- Fix: 子メニューの高さがウィンドウからはみ出ることがある問題を修正
|
||||
- Fix: 個人宛てのダイアログ形式のお知らせが即時表示されない問題を修正
|
||||
- Fix: 一部の画像がセンシティブ指定されているときに画面に何も表示されないことがあるのを修正
|
||||
- Fix: リアクションしたユーザー一覧のユーザー名がはみ出る問題を修正
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/672)
|
||||
- Fix: `/share`ページにおいて絵文字ピッカーを開くことができない問題を修正
|
||||
|
||||
### Server
|
||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
||||
|
@ -70,6 +73,8 @@
|
|||
- Fix: 一般ユーザーから見たユーザーのバッジの一覧に公開されていないものが含まれることがある問題を修正
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/652)
|
||||
- Fix: ユーザーのリアクション一覧でミュート/ブロックが機能していなかった問題を修正
|
||||
- Fix: FTT有効時にリモートユーザーのノートがHTLにキャッシュされる問題を修正
|
||||
- Fix: 一部の通知がローカル上のリモートユーザーに対して行われていた問題を修正
|
||||
- Fix: エラーメッセージの誤字を修正 (#14213)
|
||||
- Fix: ソーシャルタイムラインにローカルタイムラインに表示される自分へのリプライが表示されない問題を修正
|
||||
- Fix: リノートのミュートが適用されるまでに時間がかかることがある問題を修正
|
||||
|
|
|
@ -20,13 +20,29 @@ Before creating an issue, please check the following:
|
|||
> **Warning**
|
||||
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
||||
|
||||
## Before implementation
|
||||
|
||||
### Recommended discussing before implementation
|
||||
We welcome your purposal.
|
||||
|
||||
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.
|
||||
|
||||
At this point, you also need to clarify the goals of the PR you will create, and make sure that the other members of the team are aware of them.
|
||||
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
|
||||
|
||||
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
|
||||
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask Commiter to assign you).
|
||||
By expressing your intention to work on the Issue, you can prevent conflicts in the work.
|
||||
|
||||
To the Committers: you should not assign someone on it before the Final Decision.
|
||||
|
||||
### How issues are triaged
|
||||
|
||||
The Commiters may:
|
||||
* close an issue that is not reproducible on latest stable release,
|
||||
* merge an issue into another issue,
|
||||
* split an issue into multiple issues,
|
||||
* or re-open that has been closed for some reason which is not applicable anymore.
|
||||
|
||||
@syuilo reserves the Final Desicion rights including whether the project will implement feature and how to implement, these rights are not always exercised.
|
||||
|
||||
## Well-known branches
|
||||
- **`master`** branch is tracking the latest release and used for production purposes.
|
||||
|
|
|
@ -933,12 +933,15 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
|
||||
// 自分自身のHTL
|
||||
if (note.userHost == null) {
|
||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
|
||||
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
|
||||
if (note.fileIds.length > 0) {
|
||||
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自分自身以外への返信
|
||||
if (isReply(note)) {
|
||||
|
|
|
@ -505,14 +505,15 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
|
||||
if (role.isPublic) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
|
||||
if (role.isPublic && user.host === null) {
|
||||
this.notificationService.createNotification(userId, 'roleAssigned', {
|
||||
roleId: roleId,
|
||||
});
|
||||
}
|
||||
|
||||
if (moderator) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
this.moderationLogService.log(moderator, 'assignRole', {
|
||||
roleId: roleId,
|
||||
roleName: role.name,
|
||||
|
|
|
@ -279,9 +279,11 @@ export class UserFollowingService implements OnModuleInit {
|
|||
});
|
||||
|
||||
// 通知を作成
|
||||
if (follower.host === null) {
|
||||
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
|
||||
}, followee.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (alreadyFollowed) return;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { PollVotesRepository, NotesRepository } from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
|
@ -24,6 +25,7 @@ export class EndedPollNotificationProcessorService {
|
|||
@Inject(DI.pollVotesRepository)
|
||||
private pollVotesRepository: PollVotesRepository,
|
||||
|
||||
private cacheService: CacheService,
|
||||
private notificationService: NotificationService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
|
@ -47,9 +49,12 @@ export class EndedPollNotificationProcessorService {
|
|||
const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])];
|
||||
|
||||
for (const userId of userIds) {
|
||||
const profile = await this.cacheService.userProfileCache.fetch(userId);
|
||||
if (profile.userHost === null) {
|
||||
this.notificationService.createNotification(userId, 'pollEnded', {
|
||||
noteId: note.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
import * as assert from 'assert';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { Redis } from 'ioredis';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl } from '../utils.js';
|
||||
import { loadConfig } from '@/config.js';
|
||||
|
||||
function genHost() {
|
||||
return randomString() + '.example.com';
|
||||
|
@ -492,6 +492,44 @@ describe('Timelines', () => {
|
|||
|
||||
assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('FTT: ローカルユーザーの HTL にはプッシュされる', async () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
const aliceNote = await post(alice, { text: 'I\'m Alice.' });
|
||||
const bobNote = await post(bob, { text: 'I\'m Bob.' });
|
||||
const carolNote = await post(carol, { text: 'I\'m Carol.' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
// NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
|
||||
assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 1);
|
||||
|
||||
const bobHTL = await redisForTimelines.lrange(`list:homeTimeline:${bob.id}`, 0, -1);
|
||||
assert.strictEqual(bobHTL.includes(aliceNote.id), true);
|
||||
assert.strictEqual(bobHTL.includes(bobNote.id), true);
|
||||
assert.strictEqual(bobHTL.includes(carolNote.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('FTT: リモートユーザーの HTL にはプッシュされない', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||
|
||||
await api('following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
await post(alice, { text: 'I\'m Alice.' });
|
||||
await post(bob, { text: 'I\'m Bob.' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
// NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
|
||||
assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Local TL', () => {
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
import { createApp, defineAsyncComponent } from 'vue';
|
||||
import { common } from './common.js';
|
||||
import type { CommonBootOptions } from './common.js';
|
||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||
|
||||
export async function subBoot(options?: Partial<CommonBootOptions>) {
|
||||
const { isClientUpdated } = await common(() => createApp(
|
||||
defineAsyncComponent(() => import('@/ui/minimum.vue')),
|
||||
), options);
|
||||
|
||||
emojiPicker.init();
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkInput>
|
||||
</section>
|
||||
<section v-else-if="expiration === 'after'">
|
||||
<MkInput v-model="after" small type="number" class="input">
|
||||
<MkInput v-model="after" small type="number" min="1" class="input">
|
||||
<template #label>{{ i18n.ts._poll.duration }}</template>
|
||||
</MkInput>
|
||||
<MkSelect v-model="unit" small>
|
||||
|
|
|
@ -81,6 +81,7 @@ function getReactionName(reaction: string): string {
|
|||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
line-height: 24px;
|
||||
padding-top: 4px;
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -53,7 +53,7 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
|
|||
const current = resolveNested(router.current)!;
|
||||
const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
|
||||
const currentPageProps = ref(current.props);
|
||||
const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
|
||||
const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));
|
||||
|
||||
function onChange({ resolved, key: newKey }) {
|
||||
const current = resolveNested(resolved);
|
||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkInput v-if="!noExpirationDate" v-model="expiresAt" type="datetime-local">
|
||||
<template #label>{{ i18n.ts.expirationDate }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="createCount" type="number">
|
||||
<MkInput v-model="createCount" type="number" min="1">
|
||||
<template #label>{{ i18n.ts.createCount }}</template>
|
||||
</MkInput>
|
||||
<MkButton primary rounded @click="createWithOptions">{{ i18n.ts.create }}</MkButton>
|
||||
|
|
|
@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSwitch v-model="statusbar.props.shuffle">
|
||||
<template #label>{{ i18n.ts.shuffle }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number">
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1">
|
||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||
</MkInput>
|
||||
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
||||
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
</template>
|
||||
<template v-else-if="statusbar.type === 'federation'">
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number">
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1">
|
||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||
</MkInput>
|
||||
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
||||
|
|
|
@ -64,6 +64,7 @@ const devConfig: UserConfig = {
|
|||
'/bios': httpUrl,
|
||||
'/cli': httpUrl,
|
||||
'/inbox': httpUrl,
|
||||
'/emoji/': httpUrl,
|
||||
'/notes': {
|
||||
target: httpUrl,
|
||||
bypass: varyHandler,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import tsParser from '@typescript-eslint/parser';
|
||||
import sharedConfig from '../shared/eslint.config.js';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default [
|
||||
...sharedConfig,
|
||||
{
|
||||
|
|
|
@ -551,7 +551,7 @@ type Channel = components['schemas']['Channel'];
|
|||
// Warning: (ae-forgotten-export) The symbol "AnyOf" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
export abstract class ChannelConnection<Channel extends AnyOf<Channels> = any> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> {
|
||||
export abstract class ChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> {
|
||||
constructor(stream: Stream, channel: string, name?: string);
|
||||
// (undocumented)
|
||||
channel: string;
|
||||
|
@ -771,12 +771,12 @@ export type Channels = {
|
|||
user1: boolean;
|
||||
user2: boolean;
|
||||
}) => void;
|
||||
updateSettings: (payload: {
|
||||
updateSettings: <K extends ReversiUpdateKey>(payload: {
|
||||
userId: User['id'];
|
||||
key: string;
|
||||
value: any;
|
||||
key: K;
|
||||
value: ReversiGameDetailed[K];
|
||||
}) => void;
|
||||
log: (payload: Record<string, any>) => void;
|
||||
log: (payload: Record<string, unknown>) => void;
|
||||
};
|
||||
receives: {
|
||||
putStone: {
|
||||
|
@ -785,10 +785,7 @@ export type Channels = {
|
|||
};
|
||||
ready: boolean;
|
||||
cancel: null | Record<string, never>;
|
||||
updateSettings: {
|
||||
key: string;
|
||||
value: any;
|
||||
};
|
||||
updateSettings: ReversiUpdateSettings<ReversiUpdateKey>;
|
||||
claimTimeIsUp: null | Record<string, never>;
|
||||
};
|
||||
};
|
||||
|
@ -2782,7 +2779,7 @@ type PagesUnlikeRequest = operations['pages___unlike']['requestBody']['content']
|
|||
type PagesUpdateRequest = operations['pages___update']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
function parse(acct: string): Acct;
|
||||
function parse(_acct: string): Acct;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Values" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
|
@ -3019,7 +3016,7 @@ export class Stream extends EventEmitter<StreamEvents> implements IStream {
|
|||
constructor(origin: string, user: {
|
||||
token: string;
|
||||
} | null, options?: {
|
||||
WebSocket?: any;
|
||||
WebSocket?: WebSocket;
|
||||
});
|
||||
// (undocumented)
|
||||
close(): void;
|
||||
|
@ -3036,9 +3033,9 @@ export class Stream extends EventEmitter<StreamEvents> implements IStream {
|
|||
// (undocumented)
|
||||
send(typeOrPayload: string): void;
|
||||
// (undocumented)
|
||||
send(typeOrPayload: string, payload: any): void;
|
||||
send(typeOrPayload: string, payload: unknown): void;
|
||||
// (undocumented)
|
||||
send(typeOrPayload: Record<string, any> | any[]): void;
|
||||
send(typeOrPayload: Record<string, unknown> | unknown[]): void;
|
||||
// (undocumented)
|
||||
state: 'initializing' | 'reconnecting' | 'connected';
|
||||
// (undocumented)
|
||||
|
@ -3281,7 +3278,9 @@ type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['
|
|||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/entities.ts:34:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:35:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:220:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:230:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ export type Acct = {
|
|||
host: string | null;
|
||||
};
|
||||
|
||||
export function parse(acct: string): Acct {
|
||||
export function parse(_acct: string): Acct {
|
||||
let acct = _acct;
|
||||
if (acct.startsWith('@')) acct = acct.substring(1);
|
||||
const split = acct.split('@', 2);
|
||||
return { username: split[0], host: split[1] || null };
|
||||
|
|
|
@ -14,6 +14,7 @@ export type APIError = {
|
|||
code: string;
|
||||
message: string;
|
||||
kind: 'client' | 'server';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
info: Record<string, any>;
|
||||
};
|
||||
|
||||
|
@ -29,6 +30,7 @@ export type FetchLike = (input: string, init?: {
|
|||
headers: { [key in string]: string }
|
||||
}) => Promise<{
|
||||
status: number;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
json(): Promise<any>;
|
||||
}>;
|
||||
|
||||
|
@ -49,6 +51,7 @@ export class APIClient {
|
|||
this.fetch = opts.fetch ?? ((...args) => fetch(...args));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private assertIsRecord<T>(obj: T): obj is T & Record<string, any> {
|
||||
return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
|
||||
}
|
||||
|
|
|
@ -28,11 +28,13 @@ type StrictExtract<Union, Cond> = Cond extends Union ? Union : never;
|
|||
|
||||
type IsCaseMatched<E extends keyof Endpoints, P extends Endpoints[E]['req'], C extends number> =
|
||||
Endpoints[E]['res'] extends SwitchCase
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
? IsNeverType<StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>> extends false ? true : false
|
||||
: false
|
||||
|
||||
type GetCaseResult<E extends keyof Endpoints, P extends Endpoints[E]['req'], C extends number> =
|
||||
Endpoints[E]['res'] extends SwitchCase
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
? StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>[1]
|
||||
: never
|
||||
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
import type { operations } from './autogen/types.js';
|
||||
import type {
|
||||
AbuseReportNotificationRecipient, Ad,
|
||||
Announcement,
|
||||
EmojiDetailed, InviteCode,
|
||||
MetaDetailed,
|
||||
Note,
|
||||
Role, SystemWebhook, UserLite,
|
||||
} from './autogen/models.js';
|
||||
|
||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned'] as const;
|
||||
|
||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
|
||||
|
@ -135,10 +145,30 @@ export const moderationLogTypes = [
|
|||
'unsetUserBanner',
|
||||
] as const;
|
||||
|
||||
// See: packages/backend/src/core/ReversiService.ts@L410
|
||||
export const reversiUpdateKeys = [
|
||||
'map',
|
||||
'bw',
|
||||
'isLlotheo',
|
||||
'canPutEverywhere',
|
||||
'loopedBoard',
|
||||
'timeLimitForEachTurn',
|
||||
] as const;
|
||||
|
||||
export type ReversiUpdateKey = typeof reversiUpdateKeys[number];
|
||||
|
||||
type AvatarDecoration = UserLite['avatarDecorations'][number];
|
||||
|
||||
type ReceivedAbuseReport = {
|
||||
reportId: AbuseReportNotificationRecipient['id'];
|
||||
report: operations['admin___abuse-user-reports']['responses'][200]['content']['application/json'];
|
||||
forwarded: boolean;
|
||||
};
|
||||
|
||||
export type ModerationLogPayloads = {
|
||||
updateServerSettings: {
|
||||
before: any | null;
|
||||
after: any | null;
|
||||
before: MetaDetailed | null;
|
||||
after: MetaDetailed | null;
|
||||
};
|
||||
suspend: {
|
||||
userId: string;
|
||||
|
@ -159,16 +189,16 @@ export type ModerationLogPayloads = {
|
|||
};
|
||||
addCustomEmoji: {
|
||||
emojiId: string;
|
||||
emoji: any;
|
||||
emoji: EmojiDetailed;
|
||||
};
|
||||
updateCustomEmoji: {
|
||||
emojiId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: EmojiDetailed;
|
||||
after: EmojiDetailed;
|
||||
};
|
||||
deleteCustomEmoji: {
|
||||
emojiId: string;
|
||||
emoji: any;
|
||||
emoji: EmojiDetailed;
|
||||
};
|
||||
assignRole: {
|
||||
userId: string;
|
||||
|
@ -187,16 +217,16 @@ export type ModerationLogPayloads = {
|
|||
};
|
||||
createRole: {
|
||||
roleId: string;
|
||||
role: any;
|
||||
role: Role;
|
||||
};
|
||||
updateRole: {
|
||||
roleId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: Role;
|
||||
after: Role;
|
||||
};
|
||||
deleteRole: {
|
||||
roleId: string;
|
||||
role: any;
|
||||
role: Role;
|
||||
};
|
||||
clearQueue: Record<string, never>;
|
||||
promoteQueue: Record<string, never>;
|
||||
|
@ -211,39 +241,39 @@ export type ModerationLogPayloads = {
|
|||
noteUserId: string;
|
||||
noteUserUsername: string;
|
||||
noteUserHost: string | null;
|
||||
note: any;
|
||||
note: Note;
|
||||
};
|
||||
createGlobalAnnouncement: {
|
||||
announcementId: string;
|
||||
announcement: any;
|
||||
announcement: Announcement;
|
||||
};
|
||||
createUserAnnouncement: {
|
||||
announcementId: string;
|
||||
announcement: any;
|
||||
announcement: Announcement;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
updateGlobalAnnouncement: {
|
||||
announcementId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: Announcement;
|
||||
after: Announcement;
|
||||
};
|
||||
updateUserAnnouncement: {
|
||||
announcementId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: Announcement;
|
||||
after: Announcement;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
deleteGlobalAnnouncement: {
|
||||
announcementId: string;
|
||||
announcement: any;
|
||||
announcement: Announcement;
|
||||
};
|
||||
deleteUserAnnouncement: {
|
||||
announcementId: string;
|
||||
announcement: any;
|
||||
announcement: Announcement;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
|
@ -281,37 +311,37 @@ export type ModerationLogPayloads = {
|
|||
};
|
||||
resolveAbuseReport: {
|
||||
reportId: string;
|
||||
report: any;
|
||||
report: ReceivedAbuseReport;
|
||||
forwarded: boolean;
|
||||
};
|
||||
createInvitation: {
|
||||
invitations: any[];
|
||||
invitations: InviteCode[];
|
||||
};
|
||||
createAd: {
|
||||
adId: string;
|
||||
ad: any;
|
||||
ad: Ad;
|
||||
};
|
||||
updateAd: {
|
||||
adId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: Ad;
|
||||
after: Ad;
|
||||
};
|
||||
deleteAd: {
|
||||
adId: string;
|
||||
ad: any;
|
||||
ad: Ad;
|
||||
};
|
||||
createAvatarDecoration: {
|
||||
avatarDecorationId: string;
|
||||
avatarDecoration: any;
|
||||
avatarDecoration: AvatarDecoration;
|
||||
};
|
||||
updateAvatarDecoration: {
|
||||
avatarDecorationId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: AvatarDecoration;
|
||||
after: AvatarDecoration;
|
||||
};
|
||||
deleteAvatarDecoration: {
|
||||
avatarDecorationId: string;
|
||||
avatarDecoration: any;
|
||||
avatarDecoration: AvatarDecoration;
|
||||
};
|
||||
unsetUserAvatar: {
|
||||
userId: string;
|
||||
|
@ -327,28 +357,28 @@ export type ModerationLogPayloads = {
|
|||
};
|
||||
createSystemWebhook: {
|
||||
systemWebhookId: string;
|
||||
webhook: any;
|
||||
webhook: SystemWebhook;
|
||||
};
|
||||
updateSystemWebhook: {
|
||||
systemWebhookId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: SystemWebhook;
|
||||
after: SystemWebhook;
|
||||
};
|
||||
deleteSystemWebhook: {
|
||||
systemWebhookId: string;
|
||||
webhook: any;
|
||||
webhook: SystemWebhook;
|
||||
};
|
||||
createAbuseReportNotificationRecipient: {
|
||||
recipientId: string;
|
||||
recipient: any;
|
||||
recipient: AbuseReportNotificationRecipient;
|
||||
};
|
||||
updateAbuseReportNotificationRecipient: {
|
||||
recipientId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
before: AbuseReportNotificationRecipient;
|
||||
after: AbuseReportNotificationRecipient;
|
||||
};
|
||||
deleteAbuseReportNotificationRecipient: {
|
||||
recipientId: string;
|
||||
recipient: any;
|
||||
recipient: AbuseReportNotificationRecipient;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
Role,
|
||||
RolePolicies,
|
||||
User,
|
||||
UserDetailedNotMe
|
||||
UserDetailedNotMe,
|
||||
} from './autogen/models.js';
|
||||
|
||||
export * from './autogen/entities.js';
|
||||
|
@ -19,6 +19,7 @@ export type DateString = string;
|
|||
export type PageEvent = {
|
||||
pageId: Page['id'];
|
||||
event: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
var: any;
|
||||
userId: User['id'];
|
||||
user: User;
|
||||
|
|
|
@ -15,7 +15,7 @@ export function urlQuery(obj: Record<string, string | number | boolean | undefin
|
|||
.join('&');
|
||||
}
|
||||
|
||||
type AnyOf<T extends Record<any, any>> = T[keyof T];
|
||||
type AnyOf<T extends Record<PropertyKey, unknown>> = T[keyof T];
|
||||
|
||||
export type StreamEvents = {
|
||||
_connected_: void;
|
||||
|
@ -41,6 +41,7 @@ export interface IStream extends EventEmitter<StreamEvents> {
|
|||
/**
|
||||
* Misskey stream connection
|
||||
*/
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class Stream extends EventEmitter<StreamEvents> implements IStream {
|
||||
private stream: _ReconnectingWebsocket.default;
|
||||
public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
|
||||
|
@ -50,7 +51,7 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
|
|||
private idCounter = 0;
|
||||
|
||||
constructor(origin: string, user: { token: string; } | null, options?: {
|
||||
WebSocket?: any;
|
||||
WebSocket?: WebSocket;
|
||||
}) {
|
||||
super();
|
||||
|
||||
|
@ -67,6 +68,7 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
|
|||
this.send = this.send.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
options = options ?? { };
|
||||
|
||||
const query = urlQuery({
|
||||
|
@ -107,8 +109,8 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
|
|||
this.sharedConnectionPools.push(pool);
|
||||
}
|
||||
|
||||
const connection = new SharedConnection(this, channel, pool, name);
|
||||
this.sharedConnections.push(connection);
|
||||
const connection = new SharedConnection<Channels[C]>(this, channel, pool, name);
|
||||
this.sharedConnections.push(connection as unknown as SharedConnection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
@ -122,7 +124,7 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
|
|||
|
||||
private connectToChannel<C extends keyof Channels>(channel: C, params: Channels[C]['params']): NonSharedConnection<Channels[C]> {
|
||||
const connection = new NonSharedConnection(this, channel, this.genId(), params);
|
||||
this.nonSharedConnections.push(connection);
|
||||
this.nonSharedConnections.push(connection as unknown as NonSharedConnection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
@ -190,9 +192,9 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
|
|||
* ! ストリーム上のやり取りはすべてJSONで行われます !
|
||||
*/
|
||||
public send(typeOrPayload: string): void
|
||||
public send(typeOrPayload: string, payload: any): void
|
||||
public send(typeOrPayload: Record<string, any> | any[]): void
|
||||
public send(typeOrPayload: string | Record<string, any> | any[], payload?: any): void {
|
||||
public send(typeOrPayload: string, payload: unknown): void
|
||||
public send(typeOrPayload: Record<string, unknown> | unknown[]): void
|
||||
public send(typeOrPayload: string | Record<string, unknown> | unknown[], payload?: unknown): void {
|
||||
if (typeof typeOrPayload === 'string') {
|
||||
this.stream.send(JSON.stringify({
|
||||
type: typeOrPayload,
|
||||
|
@ -227,7 +229,7 @@ class Pool {
|
|||
public id: string;
|
||||
protected stream: Stream;
|
||||
public users = 0;
|
||||
private disposeTimerId: any;
|
||||
private disposeTimerId: ReturnType<typeof setTimeout> | null = null;
|
||||
private isConnected = false;
|
||||
|
||||
constructor(stream: Stream, channel: string, id: string) {
|
||||
|
@ -302,7 +304,7 @@ export interface IChannelConnection<Channel extends AnyOf<Channels> = any> exten
|
|||
dispose(): void;
|
||||
}
|
||||
|
||||
export abstract class Connection<Channel extends AnyOf<Channels> = any> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> {
|
||||
export abstract class Connection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> {
|
||||
public channel: string;
|
||||
protected stream: Stream;
|
||||
public abstract id: string;
|
||||
|
@ -336,7 +338,7 @@ export abstract class Connection<Channel extends AnyOf<Channels> = any> extends
|
|||
public abstract dispose(): void;
|
||||
}
|
||||
|
||||
class SharedConnection<Channel extends AnyOf<Channels> = any> extends Connection<Channel> {
|
||||
class SharedConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends Connection<Channel> {
|
||||
private pool: Pool;
|
||||
|
||||
public get id(): string {
|
||||
|
@ -355,11 +357,11 @@ class SharedConnection<Channel extends AnyOf<Channels> = any> extends Connection
|
|||
public dispose(): void {
|
||||
this.pool.dec();
|
||||
this.removeAllListeners();
|
||||
this.stream.removeSharedConnection(this);
|
||||
this.stream.removeSharedConnection(this as unknown as SharedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
class NonSharedConnection<Channel extends AnyOf<Channels> = any> extends Connection<Channel> {
|
||||
class NonSharedConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends Connection<Channel> {
|
||||
public id: string;
|
||||
protected params: Channel['params'];
|
||||
|
||||
|
@ -386,6 +388,6 @@ class NonSharedConnection<Channel extends AnyOf<Channels> = any> extends Connect
|
|||
public dispose(): void {
|
||||
this.removeAllListeners();
|
||||
this.stream.send('disconnect', { id: this.id });
|
||||
this.stream.disconnectToChannel(this);
|
||||
this.stream.disconnectToChannel(this as unknown as NonSharedConnection);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,14 @@ import {
|
|||
ServerStatsLog,
|
||||
ReversiGameDetailed,
|
||||
} from './entities.js';
|
||||
import {
|
||||
ReversiUpdateKey,
|
||||
} from './consts.js';
|
||||
|
||||
type ReversiUpdateSettings<K extends ReversiUpdateKey> = {
|
||||
key: K;
|
||||
value: ReversiGameDetailed[K];
|
||||
};
|
||||
|
||||
export type Channels = {
|
||||
main: {
|
||||
|
@ -51,6 +59,7 @@ export type Channels = {
|
|||
registryUpdated: (payload: {
|
||||
scope?: string[];
|
||||
key: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any | null;
|
||||
}) => void;
|
||||
driveFileCreated: (payload: DriveFile) => void;
|
||||
|
@ -208,8 +217,8 @@ export type Channels = {
|
|||
ended: (payload: { winnerId: User['id'] | null; game: ReversiGameDetailed; }) => void;
|
||||
canceled: (payload: { userId: User['id']; }) => void;
|
||||
changeReadyStates: (payload: { user1: boolean; user2: boolean; }) => void;
|
||||
updateSettings: (payload: { userId: User['id']; key: string; value: any; }) => void;
|
||||
log: (payload: Record<string, any>) => void;
|
||||
updateSettings: <K extends ReversiUpdateKey>(payload: { userId: User['id']; key: K; value: ReversiGameDetailed[K]; }) => void;
|
||||
log: (payload: Record<string, unknown>) => void;
|
||||
};
|
||||
receives: {
|
||||
putStone: {
|
||||
|
@ -218,10 +227,7 @@ export type Channels = {
|
|||
};
|
||||
ready: boolean;
|
||||
cancel: null | Record<string, never>;
|
||||
updateSettings: {
|
||||
key: string;
|
||||
value: any;
|
||||
};
|
||||
updateSettings: ReversiUpdateSettings<ReversiUpdateKey>;
|
||||
claimTimeIsUp: null | Record<string, never>;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue