Merge branch 'develop' into feat-1714
This commit is contained in:
@@ -933,10 +933,13 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
}
|
||||
}
|
||||
|
||||
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
|
||||
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);
|
||||
// 自分自身の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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +279,10 @@ export class UserFollowingService implements OnModuleInit {
|
||||
});
|
||||
|
||||
// 通知を作成
|
||||
this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
|
||||
}, followee.id);
|
||||
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) {
|
||||
this.notificationService.createNotification(userId, 'pollEnded', {
|
||||
noteId: note.id,
|
||||
});
|
||||
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>;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user