mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-14 05:15:35 +02:00
Merge branch 'develop' into room
This commit is contained in:
@@ -41,17 +41,17 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
"@swc/core-darwin-arm64": "1.15.11",
|
||||
"@swc/core-darwin-x64": "1.15.11",
|
||||
"@swc/core-darwin-arm64": "1.15.18",
|
||||
"@swc/core-darwin-x64": "1.15.18",
|
||||
"@swc/core-freebsd-x64": "1.3.11",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.11",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.11",
|
||||
"@swc/core-linux-arm64-musl": "1.15.11",
|
||||
"@swc/core-linux-x64-gnu": "1.15.11",
|
||||
"@swc/core-linux-x64-musl": "1.15.11",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.11",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.11",
|
||||
"@swc/core-win32-x64-msvc": "1.15.11",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.18",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.18",
|
||||
"@swc/core-linux-arm64-musl": "1.15.18",
|
||||
"@swc/core-linux-x64-gnu": "1.15.18",
|
||||
"@swc/core-linux-x64-musl": "1.15.18",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.18",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.18",
|
||||
"@swc/core-win32-x64-msvc": "1.15.18",
|
||||
"@tensorflow/tfjs": "4.22.0",
|
||||
"@tensorflow/tfjs-node": "4.22.0",
|
||||
"bufferutil": "4.1.0",
|
||||
@@ -71,8 +71,8 @@
|
||||
"utf-8-validate": "6.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.990.0",
|
||||
"@aws-sdk/lib-storage": "3.990.0",
|
||||
"@aws-sdk/client-s3": "3.1000.0",
|
||||
"@aws-sdk/lib-storage": "3.1000.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@fastify/accepts": "5.0.4",
|
||||
"@fastify/cors": "11.2.0",
|
||||
@@ -80,21 +80,21 @@
|
||||
"@fastify/http-proxy": "11.4.1",
|
||||
"@fastify/multipart": "9.4.0",
|
||||
"@fastify/static": "9.0.0",
|
||||
"@kitajs/html": "4.2.12",
|
||||
"@kitajs/html": "4.2.13",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@napi-rs/canvas": "0.1.92",
|
||||
"@nestjs/common": "11.1.13",
|
||||
"@nestjs/core": "11.1.13",
|
||||
"@nestjs/testing": "11.1.13",
|
||||
"@napi-rs/canvas": "0.1.95",
|
||||
"@nestjs/common": "11.1.14",
|
||||
"@nestjs/core": "11.1.14",
|
||||
"@nestjs/testing": "11.1.14",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "10.38.0",
|
||||
"@sentry/profiling-node": "10.38.0",
|
||||
"@simplewebauthn/server": "13.2.2",
|
||||
"@sentry/node": "10.40.0",
|
||||
"@sentry/profiling-node": "10.40.0",
|
||||
"@simplewebauthn/server": "13.2.3",
|
||||
"@sinonjs/fake-timers": "15.1.0",
|
||||
"@smithy/node-http-handler": "4.4.10",
|
||||
"@smithy/node-http-handler": "4.4.12",
|
||||
"@swc/cli": "0.8.0",
|
||||
"@swc/core": "1.15.11",
|
||||
"@swc/core": "1.15.18",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.18.0",
|
||||
@@ -103,7 +103,7 @@
|
||||
"bcryptjs": "3.0.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "2.2.2",
|
||||
"bullmq": "5.69.2",
|
||||
"bullmq": "5.70.1",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"chalk": "5.6.2",
|
||||
"chalk-template": "1.1.2",
|
||||
@@ -112,7 +112,7 @@
|
||||
"content-disposition": "1.0.1",
|
||||
"date-fns": "4.1.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "5.7.4",
|
||||
"fastify": "5.8.1",
|
||||
"fastify-raw-body": "5.0.0",
|
||||
"feed": "5.2.0",
|
||||
"file-type": "21.3.0",
|
||||
@@ -122,7 +122,7 @@
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "1.1.3",
|
||||
"i18n": "workspace:*",
|
||||
"ioredis": "5.9.3",
|
||||
"ioredis": "5.10.0",
|
||||
"ip-cidr": "4.0.2",
|
||||
"ipaddr.js": "2.3.0",
|
||||
"is-svg": "6.1.0",
|
||||
@@ -145,7 +145,7 @@
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.5.0",
|
||||
"pg": "8.18.0",
|
||||
"pg": "8.19.0",
|
||||
"pkce-challenge": "6.0.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
@@ -157,14 +157,14 @@
|
||||
"rename": "1.0.4",
|
||||
"rss-parser": "3.13.0",
|
||||
"rxjs": "7.8.2",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sanitize-html": "2.17.1",
|
||||
"secure-json-parse": "4.1.0",
|
||||
"semver": "7.7.4",
|
||||
"sharp": "0.33.5",
|
||||
"slacc": "0.0.10",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.31.0",
|
||||
"systeminformation": "5.31.1",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.5",
|
||||
"tsc-alias": "1.8.16",
|
||||
@@ -178,8 +178,8 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@kitajs/ts-html-plugin": "4.1.4",
|
||||
"@nestjs/platform-express": "11.1.13",
|
||||
"@sentry/vue": "10.38.0",
|
||||
"@nestjs/platform-express": "11.1.14",
|
||||
"@sentry/vue": "10.40.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@types/accepts": "1.3.7",
|
||||
@@ -193,11 +193,11 @@
|
||||
"@types/jsonld": "1.5.15",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/ms": "2.1.0",
|
||||
"@types/node": "24.10.13",
|
||||
"@types/nodemailer": "7.0.9",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/nodemailer": "7.0.11",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.16.0",
|
||||
"@types/pg": "8.18.0",
|
||||
"@types/qrcode": "1.5.6",
|
||||
"@types/random-seed": "0.3.5",
|
||||
"@types/ratelimiter": "3.4.6",
|
||||
@@ -212,8 +212,8 @@
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cbor": "10.0.11",
|
||||
"cross-env": "10.1.0",
|
||||
@@ -224,7 +224,7 @@
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"nodemon": "3.1.11",
|
||||
"nodemon": "3.1.14",
|
||||
"pid-port": "2.0.1",
|
||||
"simple-oauth2": "5.1.0",
|
||||
"supertest": "7.2.2",
|
||||
|
||||
@@ -129,6 +129,9 @@ export interface NoteEventTypes {
|
||||
type NoteStreamEventTypes = {
|
||||
[key in keyof NoteEventTypes]: {
|
||||
id: MiNote['id'];
|
||||
userId: MiNote['userId'];
|
||||
visibility: MiNote['visibility'];
|
||||
visibleUserIds: MiNote['visibleUserIds'];
|
||||
body: NoteEventTypes[key];
|
||||
};
|
||||
};
|
||||
@@ -378,9 +381,12 @@ export class GlobalEventService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishNoteStream<K extends keyof NoteEventTypes>(noteId: MiNote['id'], type: K, value?: NoteEventTypes[K]): void {
|
||||
this.publish(`noteStream:${noteId}`, type, {
|
||||
id: noteId,
|
||||
public publishNoteStream<K extends keyof NoteEventTypes>(note: MiNote, type: K, value?: NoteEventTypes[K]): void {
|
||||
this.publish(`noteStream:${note.id}`, type, {
|
||||
id: note.id,
|
||||
userId: note.userId,
|
||||
visibility: note.visibility,
|
||||
visibleUserIds: note.visibleUserIds,
|
||||
body: value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export class NoteDeleteService {
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
this.globalEventService.publishNoteStream(note.id, 'deleted', {
|
||||
this.globalEventService.publishNoteStream(note, 'deleted', {
|
||||
deletedAt: deletedAt,
|
||||
});
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ export class PollService {
|
||||
const index = choice + 1; // In SQL, array index is 1 based
|
||||
await this.pollsRepository.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`);
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'pollVoted', {
|
||||
this.globalEventService.publishNoteStream(note, 'pollVoted', {
|
||||
choice: choice,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
@@ -259,7 +259,7 @@ export class QueryService {
|
||||
|
||||
@bindThis
|
||||
public generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: MiUser['id'] } | null): void {
|
||||
// This code must always be synchronized with the checks in Notes.isVisibleForMe.
|
||||
// This code must always be synchronized with the checks in NoteEntityService.isVisibleForMe and Stream abstract class Channel.isNoteVisibleForMe.
|
||||
if (me == null) {
|
||||
q.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
|
||||
@@ -244,7 +244,7 @@ export class ReactionService {
|
||||
},
|
||||
});
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'reacted', {
|
||||
this.globalEventService.publishNoteStream(note, 'reacted', {
|
||||
reaction: decodedReaction.reaction,
|
||||
emoji: customEmoji != null ? {
|
||||
name: customEmoji.host ? `${customEmoji.name}@${customEmoji.host}` : `${customEmoji.name}@.`,
|
||||
@@ -318,7 +318,7 @@ export class ReactionService {
|
||||
.execute();
|
||||
}
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'unreacted', {
|
||||
this.globalEventService.publishNoteStream(note, 'unreacted', {
|
||||
reaction: this.decodeReaction(exist.reaction).reaction,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
import type { ReactionService } from '../ReactionService.js';
|
||||
@@ -66,6 +67,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
private reactionService: ReactionService;
|
||||
private reactionsBufferingService: ReactionsBufferingService;
|
||||
private idService: IdService;
|
||||
private cacheService: CacheService;
|
||||
private noteLoader = new DebounceLoader(this.findNoteOrFail);
|
||||
|
||||
constructor(
|
||||
@@ -101,6 +103,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
//private reactionService: ReactionService,
|
||||
//private reactionsBufferingService: ReactionsBufferingService,
|
||||
//private idService: IdService,
|
||||
//private cacheService: CacheService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -111,6 +114,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
this.reactionService = this.moduleRef.get('ReactionService');
|
||||
this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService');
|
||||
this.idService = this.moduleRef.get('IdService');
|
||||
this.cacheService = this.moduleRef.get('CacheService');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -125,75 +129,65 @@ export class NoteEntityService implements OnModuleInit {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
|
||||
if (meId === packedNote.userId) return;
|
||||
|
||||
public async shouldHideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<boolean> {
|
||||
if (meId === packedNote.userId) return false;
|
||||
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
|
||||
let hide = false;
|
||||
|
||||
if (packedNote.user.requireSigninToViewContents && meId == null) {
|
||||
hide = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hide) {
|
||||
const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
|
||||
if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) {
|
||||
hide = true;
|
||||
}
|
||||
const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
|
||||
if (shouldHideNoteByTime(hiddenBefore, packedNote.createdAt)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (!hide) {
|
||||
if (packedNote.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds!.some(id => meId === id);
|
||||
if (packedNote.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
return true;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds!.some(id => meId === id);
|
||||
|
||||
if (!specified) {
|
||||
hide = true;
|
||||
}
|
||||
if (!specified) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
||||
if (!hide) {
|
||||
if (packedNote.visibility === 'followers') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
|
||||
// 自分へのメンション
|
||||
hide = false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
// TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followeeId: packedNote.userId,
|
||||
followerId: meId,
|
||||
},
|
||||
});
|
||||
|
||||
hide = !isFollowing;
|
||||
if (packedNote.visibility === 'followers') {
|
||||
if (meId == null) {
|
||||
return true;
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
return false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
|
||||
// 自分へのメンション
|
||||
return false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
const followings = await this.cacheService.userFollowingsCache.fetch(meId);
|
||||
if (!Object.hasOwn(followings, packedNote.userId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
packedNote.visibleUserIds = undefined;
|
||||
packedNote.fileIds = [];
|
||||
packedNote.files = [];
|
||||
packedNote.text = null;
|
||||
packedNote.poll = undefined;
|
||||
packedNote.cw = null;
|
||||
packedNote.isHidden = true;
|
||||
// TODO: hiddenReason みたいなのを提供しても良さそう
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public hideNote(packedNote: Packed<'Note'>): void {
|
||||
packedNote.visibleUserIds = undefined;
|
||||
packedNote.fileIds = [];
|
||||
packedNote.files = [];
|
||||
packedNote.text = null;
|
||||
packedNote.poll = undefined;
|
||||
packedNote.cw = null;
|
||||
packedNote.isHidden = true;
|
||||
// TODO: hiddenReason みたいなのを提供しても良さそう
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -278,7 +272,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
|
||||
@bindThis
|
||||
public async isVisibleForMe(note: MiNote, meId: MiUser['id'] | null): Promise<boolean> {
|
||||
// This code must always be synchronized with the checks in generateVisibilityQuery.
|
||||
// This code must always be synchronized with the checks in QueryService.generateVisibilityQuery.
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (note.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
@@ -468,8 +462,8 @@ export class NoteEntityService implements OnModuleInit {
|
||||
|
||||
this.treatVisibility(packed);
|
||||
|
||||
if (!opts.skipHide) {
|
||||
await this.hideNote(packed, meId);
|
||||
if (!opts.skipHide && await this.shouldHideNote(packed, meId)) {
|
||||
this.hideNote(packed);
|
||||
}
|
||||
|
||||
return packed;
|
||||
|
||||
@@ -30,9 +30,9 @@ import { bindThis } from '@/decorators.js';
|
||||
import { IActivity } from '@/core/activitypub/type.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||
|
||||
const ACTIVITY_JSON = 'application/activity+json; charset=utf-8';
|
||||
const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
|
||||
@@ -131,6 +131,7 @@ export class ActivityPubServerService {
|
||||
if (signature.params.headers.indexOf('digest') === -1) {
|
||||
// Digest not found.
|
||||
reply.code(401);
|
||||
return;
|
||||
} else {
|
||||
const digest = request.headers.digest;
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import { ChatUserChannel } from './api/stream/channels/chat-user.js';
|
||||
import { ChatRoomChannel } from './api/stream/channels/chat-room.js';
|
||||
import { ReversiChannel } from './api/stream/channels/reversi.js';
|
||||
import { ReversiGameChannel } from './api/stream/channels/reversi-game.js';
|
||||
import { NoteStreamingHidingService } from './api/stream/NoteStreamingHidingService.js';
|
||||
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
|
||||
|
||||
@Module({
|
||||
@@ -98,6 +99,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
|
||||
QueueStatsChannel,
|
||||
ServerStatsChannel,
|
||||
UserListChannel,
|
||||
NoteStreamingHidingService,
|
||||
OpenApiServerService,
|
||||
OAuth2ProviderService,
|
||||
],
|
||||
|
||||
@@ -391,7 +391,7 @@ export * as 'users/featured-notes' from './endpoints/users/featured-notes.js';
|
||||
export * as 'users/flashs' from './endpoints/users/flashs.js';
|
||||
export * as 'users/followers' from './endpoints/users/followers.js';
|
||||
export * as 'users/following' from './endpoints/users/following.js';
|
||||
export * as 'users/get-following-birthday-users' from './endpoints/users/get-following-birthday-users.js';
|
||||
export * as 'users/get-following-users-by-birthday' from './endpoints/users/get-following-users-by-birthday.js';
|
||||
export * as 'users/gallery/posts' from './endpoints/users/gallery/posts.js';
|
||||
export * as 'users/get-frequently-replied-users' from './endpoints/users/get-frequently-replied-users.js';
|
||||
export * as 'users/lists/create' from './endpoints/users/lists/create.js';
|
||||
|
||||
@@ -341,7 +341,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
if (ps.clientOptions !== undefined) {
|
||||
set.clientOptions = {
|
||||
...serverSettings.clientOptions,
|
||||
...this.serverSettings.clientOptions,
|
||||
...ps.clientOptions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userExist = await this.usersRepository.exists({ where: { id: me.id } });
|
||||
if (!userExist) throw new ApiError(meta.errors.noSuchUser);
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId, userId: me.id });
|
||||
if (file === null) throw new ApiError(meta.errors.noSuchFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
const antennas: (_Antenna & { userListAccts: string[] | null })[] = JSON.parse(await this.downloadService.downloadTextFile(file.url));
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId, userId: me.id });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId, userId: me.id });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId, userId: me.id });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
|
||||
@@ -67,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private accountMoveService: AccountMoveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
|
||||
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId, userId: me.id });
|
||||
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
|
||||
@@ -155,7 +155,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
const index = ps.choice + 1; // In SQL, array index is 1 based
|
||||
await this.pollsRepository.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`);
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'pollVoted', {
|
||||
this.globalEventService.publishNoteStream(note, 'pollVoted', {
|
||||
choice: ps.choice,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
@@ -86,7 +86,7 @@ export const paramDef = {
|
||||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
birthday: { ...birthdaySchema, nullable: true, description: '@deprecated use get-following-birthday-users instead.' },
|
||||
birthday: { ...birthdaySchema, nullable: true, description: '@deprecated use get-following-users-by-birthday instead.' },
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -146,7 +146,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.andWhere('following.followerId = :userId', { userId: user.id })
|
||||
.innerJoinAndSelect('following.followee', 'followee');
|
||||
|
||||
// @deprecated use get-following-birthday-users instead.
|
||||
// @deprecated use get-following-users-by-birthday instead.
|
||||
if (ps.birthday) {
|
||||
query.innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId');
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export const meta = {
|
||||
requireCredential: true,
|
||||
kind: 'read:account',
|
||||
|
||||
description: 'Find users who have a birthday on the specified range.',
|
||||
description: 'Retrieve users who have a birthday on the specified range.',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HybridTimelineChannel } from './channels/hybrid-timeline.js';
|
||||
import { LocalTimelineChannel } from './channels/local-timeline.js';
|
||||
import { HomeTimelineChannel } from './channels/home-timeline.js';
|
||||
import { GlobalTimelineChannel } from './channels/global-timeline.js';
|
||||
import { MainChannel } from './channels/main.js';
|
||||
import { ChannelChannel } from './channels/channel.js';
|
||||
import { AdminChannel } from './channels/admin.js';
|
||||
import { ServerStatsChannel } from './channels/server-stats.js';
|
||||
import { QueueStatsChannel } from './channels/queue-stats.js';
|
||||
import { UserListChannel } from './channels/user-list.js';
|
||||
import { AntennaChannel } from './channels/antenna.js';
|
||||
import { DriveChannel } from './channels/drive.js';
|
||||
import { HashtagChannel } from './channels/hashtag.js';
|
||||
import { RoleTimelineChannel } from './channels/role-timeline.js';
|
||||
import { ChatUserChannel } from './channels/chat-user.js';
|
||||
import { ChatRoomChannel } from './channels/chat-room.js';
|
||||
import { ReversiChannel } from './channels/reversi.js';
|
||||
import { ReversiGameChannel } from './channels/reversi-game.js';
|
||||
import type { ChannelConstructor } from './channel.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ChannelsService {
|
||||
constructor(
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public getChannelConstructor(name: string): ChannelConstructor<boolean> {
|
||||
switch (name) {
|
||||
case 'main': return MainChannel;
|
||||
case 'homeTimeline': return HomeTimelineChannel;
|
||||
case 'localTimeline': return LocalTimelineChannel;
|
||||
case 'hybridTimeline': return HybridTimelineChannel;
|
||||
case 'globalTimeline': return GlobalTimelineChannel;
|
||||
case 'userList': return UserListChannel;
|
||||
case 'hashtag': return HashtagChannel;
|
||||
case 'roleTimeline': return RoleTimelineChannel;
|
||||
case 'antenna': return AntennaChannel;
|
||||
case 'channel': return ChannelChannel;
|
||||
case 'drive': return DriveChannel;
|
||||
case 'serverStats': return ServerStatsChannel;
|
||||
case 'queueStats': return QueueStatsChannel;
|
||||
case 'admin': return AdminChannel;
|
||||
case 'chatUser': return ChatUserChannel;
|
||||
case 'chatRoom': return ChatRoomChannel;
|
||||
case 'reversi': return ReversiChannel;
|
||||
case 'reversiGame': return ReversiGameChannel;
|
||||
|
||||
default:
|
||||
throw new Error(`no such channel: ${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,23 +4,19 @@
|
||||
*/
|
||||
|
||||
import * as WebSocket from 'ws';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiAccessToken } from '@/models/AccessToken.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
||||
import type { GlobalEvents, StreamEventEmitter } from '@/core/GlobalEventService.js';
|
||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||
import { isJsonObject } from '@/misc/json-value.js';
|
||||
import type { EventEmitter } from 'events';
|
||||
import type Channel from './channel.js';
|
||||
import type { ChannelConstructor } from './channel.js';
|
||||
import type { ChannelRequest } from './channel.js';
|
||||
import { ContextIdFactory, ModuleRef, REQUEST } from '@nestjs/core';
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { isJsonObject } from '@/misc/json-value.js';
|
||||
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||
import { ChannelMutingService } from '@/core/ChannelMutingService.js';
|
||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||
import type { GlobalEvents, StreamEventEmitter } from '@/core/GlobalEventService.js';
|
||||
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import type { MiAccessToken } from '@/models/AccessToken.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { MainChannel } from '@/server/api/stream/channels/main.js';
|
||||
import { HomeTimelineChannel } from '@/server/api/stream/channels/home-timeline.js';
|
||||
import { LocalTimelineChannel } from '@/server/api/stream/channels/local-timeline.js';
|
||||
@@ -39,20 +35,24 @@ import { ChatUserChannel } from '@/server/api/stream/channels/chat-user.js';
|
||||
import { ChatRoomChannel } from '@/server/api/stream/channels/chat-room.js';
|
||||
import { ReversiChannel } from '@/server/api/stream/channels/reversi.js';
|
||||
import { ReversiGameChannel } from '@/server/api/stream/channels/reversi-game.js';
|
||||
import type { ChannelRequest } from './channel.js';
|
||||
import type { ChannelConstructor } from './channel.js';
|
||||
import type Channel from './channel.js';
|
||||
import type { EventEmitter } from 'events';
|
||||
|
||||
const MAX_CHANNELS_PER_CONNECTION = 32;
|
||||
|
||||
/**
|
||||
* Main stream connection
|
||||
*/
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export default class Connection {
|
||||
public user?: MiUser;
|
||||
public token?: MiAccessToken;
|
||||
private wsConnection: WebSocket.WebSocket;
|
||||
public subscriber: StreamEventEmitter;
|
||||
private channels: Channel[] = [];
|
||||
private channels: Map<string, Channel> = new Map();
|
||||
private subscribingNotes: Partial<Record<string, number>> = {};
|
||||
public userProfile: MiUserProfile | null = null;
|
||||
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
||||
@@ -206,6 +206,19 @@ export default class Connection {
|
||||
|
||||
@bindThis
|
||||
private async onNoteStreamMessage(data: GlobalEvents['note']['payload']) {
|
||||
// 自分自身ではないかつ
|
||||
if (data.body.userId !== this.user?.id) {
|
||||
// 公開範囲が指名で自分が含まれてない
|
||||
if (data.body.visibility === 'specified' && (this.user == null || !data.body.visibleUserIds.includes(this.user.id))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 公開範囲がフォロワーで自分がフォロワーでない
|
||||
if (data.body.visibility === 'followers' && !Object.hasOwn(this.following, data.body.userId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.sendMessageToWs('noteUpdated', {
|
||||
id: data.body.id,
|
||||
type: data.type,
|
||||
@@ -254,7 +267,11 @@ export default class Connection {
|
||||
*/
|
||||
@bindThis
|
||||
public async connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) {
|
||||
if (this.channels.length >= MAX_CHANNELS_PER_CONNECTION) {
|
||||
if (this.channels.has(id)) {
|
||||
this.disconnectChannel(id);
|
||||
}
|
||||
|
||||
if (this.channels.size >= MAX_CHANNELS_PER_CONNECTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,8 +287,12 @@ export default class Connection {
|
||||
}
|
||||
|
||||
// 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視
|
||||
if (channelConstructor.shouldShare && this.channels.some(c => c.chName === channel)) {
|
||||
return;
|
||||
if (channelConstructor.shouldShare) {
|
||||
for (const c of this.channels.values()) {
|
||||
if (c.chName === channel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const contextId = ContextIdFactory.create();
|
||||
@@ -281,8 +302,13 @@ export default class Connection {
|
||||
}, contextId);
|
||||
const ch: Channel = await this.moduleRef.create<Channel>(channelConstructor, contextId);
|
||||
|
||||
this.channels.push(ch);
|
||||
ch.init(params ?? {});
|
||||
this.channels.set(ch.id, ch);
|
||||
const valid = await ch.init(params ?? {});
|
||||
if (typeof valid === 'boolean' && !valid) {
|
||||
// 初期化処理の結果、接続拒否されたので切断
|
||||
this.disconnectChannel(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pong) {
|
||||
this.sendMessageToWs('connected', {
|
||||
@@ -324,11 +350,11 @@ export default class Connection {
|
||||
*/
|
||||
@bindThis
|
||||
public disconnectChannel(id: string) {
|
||||
const channel = this.channels.find(c => c.id === id);
|
||||
const channel = this.channels.get(id);
|
||||
|
||||
if (channel) {
|
||||
if (channel.dispose) channel.dispose();
|
||||
this.channels = this.channels.filter(c => c.id !== id);
|
||||
this.channels.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,7 +369,7 @@ export default class Connection {
|
||||
if (typeof data.type !== 'string') return;
|
||||
if (typeof data.body === 'undefined') return;
|
||||
|
||||
const channel = this.channels.find(c => c.id === data.id);
|
||||
const channel = this.channels.get(data.id);
|
||||
if (channel != null && channel.onMessage != null) {
|
||||
channel.onMessage(data.type, data.body);
|
||||
}
|
||||
@@ -355,7 +381,7 @@ export default class Connection {
|
||||
@bindThis
|
||||
public dispose() {
|
||||
if (this.fetchIntervalId) clearInterval(this.fetchIntervalId);
|
||||
for (const c of this.channels.filter(c => c.dispose)) {
|
||||
for (const c of this.channels.values()) {
|
||||
if (c.dispose) c.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
|
||||
type HiddenLayer = 'note' | 'renote' | 'renoteRenote';
|
||||
|
||||
type LockdownCheckResult =
|
||||
| { shouldSkip: true }
|
||||
| { shouldSkip: false; hiddenLayers: Set<HiddenLayer> };
|
||||
|
||||
/** Streamにおいて、ノートを隠す(hideNote)を適用するためのService */
|
||||
@Injectable()
|
||||
export class NoteStreamingHidingService {
|
||||
constructor(
|
||||
private noteEntityService: NoteEntityService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* ノートの可視性を判定する
|
||||
*
|
||||
* @param note - 判定対象のノート
|
||||
* @param meId - 閲覧者のユーザーID(未ログインの場合はnull)
|
||||
* @returns shouldSkip: true の場合はノートを流さない、false の場合は hiddenLayers に基づいて隠す
|
||||
*/
|
||||
@bindThis
|
||||
public async shouldHide(
|
||||
note: Packed<'Note'>,
|
||||
meId: MiUser['id'] | null,
|
||||
): Promise<LockdownCheckResult> {
|
||||
const hiddenLayers = new Set<HiddenLayer>();
|
||||
|
||||
// 1階層目: note自体
|
||||
const shouldHideThisNote = await this.noteEntityService.shouldHideNote(note, meId);
|
||||
if (shouldHideThisNote) {
|
||||
if (isRenotePacked(note) && isQuotePacked(note)) {
|
||||
// 引用リノートの場合、内容を隠して流す
|
||||
hiddenLayers.add('note');
|
||||
} else if (isRenotePacked(note)) {
|
||||
// 純粋リノートの場合、流さない
|
||||
return { shouldSkip: true };
|
||||
} else {
|
||||
// 通常ノートの場合、内容を隠して流す
|
||||
hiddenLayers.add('note');
|
||||
}
|
||||
}
|
||||
|
||||
// 2階層目: note.renote
|
||||
if (isRenotePacked(note) && note.renote) {
|
||||
const shouldHideRenote = await this.noteEntityService.shouldHideNote(note.renote, meId);
|
||||
if (shouldHideRenote) {
|
||||
if (isQuotePacked(note)) {
|
||||
// noteが引用リノートの場合、renote部分だけ隠す
|
||||
hiddenLayers.add('renote');
|
||||
} else {
|
||||
// noteが純粋リノートの場合、流さない
|
||||
return { shouldSkip: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3階層目: note.renote.renote
|
||||
if (isRenotePacked(note) && note.renote &&
|
||||
isRenotePacked(note.renote) && note.renote.renote) {
|
||||
const shouldHideRenoteRenote = await this.noteEntityService.shouldHideNote(note.renote.renote, meId);
|
||||
if (shouldHideRenoteRenote) {
|
||||
if (isQuotePacked(note.renote)) {
|
||||
// note.renoteが引用リノートの場合、renote.renote部分だけ隠す
|
||||
hiddenLayers.add('renoteRenote');
|
||||
} else {
|
||||
// note.renoteが純粋リノートの場合、note.renoteの意味がなくなるので流さない
|
||||
return { shouldSkip: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { shouldSkip: false, hiddenLayers };
|
||||
}
|
||||
|
||||
/**
|
||||
* hiddenLayersに基づいてノートの内容を隠す。
|
||||
*
|
||||
* この処理は渡された `note` を直接変更します。
|
||||
*
|
||||
* @param note - 処理対象のノート
|
||||
* @param hiddenLayers - 隠す階層のセット
|
||||
*/
|
||||
@bindThis
|
||||
public applyHiding(
|
||||
note: Packed<'Note'>,
|
||||
hiddenLayers: Set<HiddenLayer>,
|
||||
): void {
|
||||
if (hiddenLayers.has('note')) {
|
||||
this.noteEntityService.hideNote(note);
|
||||
}
|
||||
if (hiddenLayers.has('renote') && note.renote) {
|
||||
this.noteEntityService.hideNote(note.renote);
|
||||
}
|
||||
if (hiddenLayers.has('renoteRenote') && note.renote && note.renote.renote) {
|
||||
this.noteEntityService.hideNote(note.renote.renote);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ストリーミング配信用にノートを隠す(あるいはそもそも送信しない)の判定及び処理を行う。
|
||||
*
|
||||
* この処理は渡された `note` を直接変更します。
|
||||
*
|
||||
* @param note - 処理対象のノート(必要に応じて内容が隠される)
|
||||
* @param meId - 閲覧者のユーザーID(未ログインの場合はnull)
|
||||
* @returns shouldSkip: true の場合はノートを流さない
|
||||
*/
|
||||
@bindThis
|
||||
public async processHiding(
|
||||
note: Packed<'Note'>,
|
||||
meId: MiUser['id'] | null,
|
||||
): Promise<{ shouldSkip: boolean }> {
|
||||
const result = await this.shouldHide(note, meId);
|
||||
if (result.shouldSkip) {
|
||||
return { shouldSkip: true };
|
||||
}
|
||||
this.applyHiding(note, result.hiddenLayers);
|
||||
return { shouldSkip: false };
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
|
||||
import { isChannelRelated } from '@/misc/is-channel-related.js';
|
||||
import type { Awaitable } from '@/types.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { JsonObject, JsonValue } from '@/misc/json-value.js';
|
||||
import type Connection from './Connection.js';
|
||||
@@ -64,6 +65,43 @@ export default abstract class Channel {
|
||||
return this.connection.subscriber;
|
||||
}
|
||||
|
||||
protected isNoteVisibleForMe(note: Packed<'Note'>): boolean {
|
||||
// This code must always be synchronized with the checks in QueryService.generateVisibilityQuery.
|
||||
const meId = this.connection.user?.id ?? null;
|
||||
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (note.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
return false;
|
||||
} else if (meId === note.userId) {
|
||||
return true;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
return note.visibleUserIds?.some(id => meId === id) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
||||
if (note.visibility === 'followers') {
|
||||
if (meId == null) {
|
||||
return false;
|
||||
} else if (meId === note.userId) {
|
||||
return true;
|
||||
} else if (note.reply && (meId === note.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
return true;
|
||||
} else if (note.mentions && note.mentions.some(id => meId === id)) {
|
||||
// 自分へのメンション
|
||||
return true;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
return Object.hasOwn(this.following, note.userId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* ミュートとブロックされてるを処理する
|
||||
*/
|
||||
@@ -104,7 +142,14 @@ export default abstract class Channel {
|
||||
});
|
||||
}
|
||||
|
||||
public abstract init(params: JsonObject): void;
|
||||
/**
|
||||
* チャンネルの初期化処理(接続時点での接続可否チェックを兼ねる)
|
||||
*
|
||||
* - `void / Promise<void>` を返す場合は、チェックなし
|
||||
* - `true / Promise<true>` を返す場合は、接続可能
|
||||
* - `false / Promise<false>` を返す場合は、接続不可(接続を切断)
|
||||
*/
|
||||
public abstract init(params: JsonObject): Awaitable<void | boolean>;
|
||||
|
||||
public dispose?(): void;
|
||||
|
||||
|
||||
@@ -4,8 +4,12 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AntennasRepository } from '@/models/_.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
import Channel, { type ChannelRequest } from '../channel.js';
|
||||
@@ -23,19 +27,36 @@ export class AntennaChannel extends Channel {
|
||||
@Inject(REQUEST)
|
||||
request: ChannelRequest,
|
||||
|
||||
@Inject(DI.antennasRepository)
|
||||
private antennasReposiotry: AntennasRepository,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onEvent = this.onEvent.bind(this);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (typeof params.antennaId !== 'string') return;
|
||||
public async init(params: JsonObject): Promise<boolean> {
|
||||
if (typeof params.antennaId !== 'string') return false;
|
||||
if (!this.user) return false;
|
||||
|
||||
this.antennaId = params.antennaId;
|
||||
|
||||
const antennaExists = await this.antennasReposiotry.exists({
|
||||
where: {
|
||||
id: this.antennaId,
|
||||
userId: this.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!antennaExists) return false;
|
||||
|
||||
// Subscribe stream
|
||||
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -43,8 +64,21 @@ export class AntennaChannel extends Channel {
|
||||
if (data.type === 'note') {
|
||||
const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true });
|
||||
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.send('note', note);
|
||||
} else {
|
||||
this.send(data.type, data.body);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||
@@ -26,6 +27,7 @@ export class ChannelChannel extends Channel {
|
||||
request: ChannelRequest,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onNote = this.onNote.bind(this);
|
||||
@@ -48,12 +50,18 @@ export class ChannelChannel extends Channel {
|
||||
if (note.renote && note.renote.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return;
|
||||
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import Channel, { type ChannelRequest } from '../channel.js';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import type { ChatRoomsRepository } from '@/models/_.js';
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class ChatRoomChannel extends Channel {
|
||||
@@ -23,17 +25,31 @@ export class ChatRoomChannel extends Channel {
|
||||
@Inject(REQUEST)
|
||||
request: ChannelRequest,
|
||||
|
||||
@Inject(DI.chatRoomsRepository)
|
||||
private chatRoomsRepository: ChatRoomsRepository,
|
||||
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (typeof params.roomId !== 'string') return;
|
||||
public async init(params: JsonObject): Promise<boolean> {
|
||||
if (typeof params.roomId !== 'string') return false;
|
||||
if (!this.user) return false;
|
||||
|
||||
this.roomId = params.roomId;
|
||||
|
||||
const room = await this.chatRoomsRepository.findOneBy({
|
||||
id: this.roomId,
|
||||
});
|
||||
|
||||
if (room == null) return false;
|
||||
if (!(await this.chatService.hasPermissionToViewRoomTimeline(this.user.id, room))) return false;
|
||||
|
||||
this.subscriber.on(`chatRoomStream:${this.roomId}`, this.onEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
||||
@@ -29,11 +29,16 @@ export class ChatUserChannel extends Channel {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (typeof params.otherId !== 'string') return;
|
||||
public async init(params: JsonObject): Promise<boolean> {
|
||||
if (typeof params.otherId !== 'string') return false;
|
||||
if (!this.user) return false;
|
||||
if (params.otherId === this.user.id) return false;
|
||||
|
||||
this.otherId = params.otherId;
|
||||
|
||||
this.subscriber.on(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent);
|
||||
this.subscriber.on(`chatUserStream:${this.user.id}-${this.otherId}`, this.onEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
@@ -29,6 +30,7 @@ export class GlobalTimelineChannel extends Channel {
|
||||
private metaService: MetaService,
|
||||
private roleService: RoleService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onNote = this.onNote.bind(this);
|
||||
@@ -60,10 +62,15 @@ export class GlobalTimelineChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
import Channel, { type ChannelRequest } from '../channel.js';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class HashtagChannel extends Channel {
|
||||
public readonly chName = 'hashtag';
|
||||
@@ -25,19 +25,26 @@ export class HashtagChannel extends Channel {
|
||||
request: ChannelRequest,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onNote = this.onNote.bind(this);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (!Array.isArray(params.q)) return;
|
||||
if (!params.q.every(x => Array.isArray(x) && x.every(y => typeof y === 'string'))) return;
|
||||
public async init(params: JsonObject): Promise<boolean> {
|
||||
if (!Array.isArray(params.q)) return false;
|
||||
if (!params.q.every((x): x is string[] => (
|
||||
Array.isArray(x) &&
|
||||
x.length >= 1 &&
|
||||
x.every(y => typeof y === 'string')
|
||||
))) return false;
|
||||
this.q = params.q;
|
||||
|
||||
// Subscribe stream
|
||||
this.subscriber.on('notesStream', this.onNote);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -46,12 +53,21 @@ export class HashtagChannel extends Channel {
|
||||
const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag))));
|
||||
if (!matched) return;
|
||||
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
if (note.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (note.renote && note.renote.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
@@ -26,6 +27,7 @@ export class HomeTimelineChannel extends Channel {
|
||||
request: ChannelRequest,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onNote = this.onNote.bind(this);
|
||||
@@ -55,11 +57,7 @@ export class HomeTimelineChannel extends Channel {
|
||||
if (!isMe && !Object.hasOwn(this.following, note.userId)) return;
|
||||
}
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
if (!isMe && !Object.hasOwn(this.following, note.userId)) return;
|
||||
} else if (note.visibility === 'specified') {
|
||||
if (!isMe && !note.visibleUserIds!.includes(this.user!.id)) return;
|
||||
}
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
|
||||
if (note.reply) {
|
||||
const reply = note.reply;
|
||||
@@ -84,10 +82,15 @@ export class HomeTimelineChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
@@ -31,6 +32,7 @@ export class HybridTimelineChannel extends Channel {
|
||||
private metaService: MetaService,
|
||||
private roleService: RoleService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onNote = this.onNote.bind(this);
|
||||
@@ -75,12 +77,7 @@ export class HybridTimelineChannel extends Channel {
|
||||
}
|
||||
}
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
if (!isMe && !Object.hasOwn(this.following, note.userId)) return;
|
||||
} else if (note.visibility === 'specified') {
|
||||
if (!isMe && !note.visibleUserIds!.includes(this.user!.id)) return;
|
||||
}
|
||||
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (note.reply) {
|
||||
@@ -104,10 +101,15 @@ export class HybridTimelineChannel extends Channel {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.user && note.renoteId && !note.text) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
|
||||
@@ -30,6 +31,7 @@ export class LocalTimelineChannel extends Channel {
|
||||
private metaService: MetaService,
|
||||
private roleService: RoleService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onNote = this.onNote.bind(this);
|
||||
@@ -70,10 +72,15 @@ export class LocalTimelineChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,10 @@ export class MainChannel extends Channel {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
// Subscribe main stream channel
|
||||
this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
|
||||
public async init(params: JsonObject): Promise<boolean> {
|
||||
if (!this.user) return false;
|
||||
|
||||
this.subscriber.on(`mainStream:${this.user.id}`, async data => {
|
||||
switch (data.type) {
|
||||
case 'notification': {
|
||||
// Ignore notifications from instances the user has muted
|
||||
@@ -47,8 +48,8 @@ export class MainChannel extends Channel {
|
||||
}
|
||||
case 'mention': {
|
||||
if (isInstanceMuted(data.body, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
|
||||
|
||||
if (this.userIdsWhoMeMuting.has(data.body.userId)) return;
|
||||
if (!this.isNoteVisibleForMe(data.body)) return;
|
||||
if (this.isNoteMutedOrBlocked(data.body)) return;
|
||||
if (data.body.isHidden) {
|
||||
const note = await this.noteEntityService.pack(data.body.id, this.user, {
|
||||
detail: true,
|
||||
@@ -61,5 +62,7 @@ export class MainChannel extends Channel {
|
||||
|
||||
this.send(data.type, data.body);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
import Channel, { type ChannelRequest } from '../channel.js';
|
||||
@@ -25,6 +27,7 @@ export class RoleTimelineChannel extends Channel {
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private roleservice: RoleService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.onNote = this.onNote.bind(this);
|
||||
@@ -47,9 +50,24 @@ export class RoleTimelineChannel extends Channel {
|
||||
return;
|
||||
}
|
||||
if (note.visibility !== 'public') return;
|
||||
if (note.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (note.renote && note.renote.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return;
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.send('note', note);
|
||||
} else {
|
||||
this.send(data.type, data.body);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Inject, Injectable, Scope } from '@nestjs/common';
|
||||
import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteStreamingHidingService } from '../NoteStreamingHidingService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||
@@ -36,6 +37,7 @@ export class UserListChannel extends Channel {
|
||||
request: ChannelRequest,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private noteStreamingHidingService: NoteStreamingHidingService,
|
||||
) {
|
||||
super(request);
|
||||
//this.updateListUsers = this.updateListUsers.bind(this);
|
||||
@@ -43,8 +45,8 @@ export class UserListChannel extends Channel {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (typeof params.listId !== 'string') return;
|
||||
public async init(params: JsonObject): Promise<boolean> {
|
||||
if (typeof params.listId !== 'string') return false;
|
||||
this.listId = params.listId;
|
||||
this.withFiles = !!(params.withFiles ?? false);
|
||||
this.withRenotes = !!(params.withRenotes ?? true);
|
||||
@@ -56,7 +58,7 @@ export class UserListChannel extends Channel {
|
||||
userId: this.user!.id,
|
||||
},
|
||||
});
|
||||
if (!listExist) return;
|
||||
if (!listExist) return false;
|
||||
|
||||
// Subscribe stream
|
||||
this.subscriber.on(`userListStream:${this.listId}`, this.send);
|
||||
@@ -65,6 +67,8 @@ export class UserListChannel extends Channel {
|
||||
|
||||
this.updateListUsers();
|
||||
this.listUsersClock = setInterval(this.updateListUsers, 5000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -96,11 +100,7 @@ export class UserListChannel extends Channel {
|
||||
|
||||
if (!Object.hasOwn(this.membershipsMap, note.userId)) return;
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
if (!isMe && !Object.hasOwn(this.following, note.userId)) return;
|
||||
} else if (note.visibility === 'specified') {
|
||||
if (!note.visibleUserIds!.includes(this.user!.id)) return;
|
||||
}
|
||||
if (!this.isNoteVisibleForMe(note)) return;
|
||||
|
||||
if (note.reply) {
|
||||
const reply = note.reply;
|
||||
@@ -117,10 +117,15 @@ export class UserListChannel extends Channel {
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, this.user?.id ?? null);
|
||||
if (shouldSkip) return;
|
||||
|
||||
if (this.user) {
|
||||
if (isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||
note.renote.myReaction = myRenoteReaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,9 @@ async function discoverClientInformation(logger: Logger, httpRequestService: Htt
|
||||
redirectUris.push(...httpLinkHeader.parse(linkHeader).get('rel', 'redirect_uri').map(r => r.uri));
|
||||
}
|
||||
|
||||
if (res.headers.get('content-type')?.includes('application/json')) {
|
||||
const contentType = res.headers.get('content-type');
|
||||
const mediaType = contentType ? contentType.split(';')[0].trim() : null;
|
||||
if (mediaType === 'application/json') {
|
||||
// Client discovery via JSON document (11 July 2024 spec)
|
||||
// https://indieauth.spec.indieweb.org/#client-metadata
|
||||
// "Clients SHOULD have a JSON [RFC7159] document at their client_id URL containing
|
||||
|
||||
@@ -414,3 +414,5 @@ export type FilterUnionByProperty<
|
||||
Property extends string | number | symbol,
|
||||
Condition,
|
||||
> = Union extends Record<Property, Condition> ? Union : never;
|
||||
|
||||
export type Awaitable<T> = T | Promise<T>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:1.27
|
||||
image: nginx:1.29
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./certificates/rootCA.crt
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/node": "24.10.13",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"rollup": "4.57.1"
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"rollup": "4.59.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"i18n": "workspace:*",
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-js": "workspace:*",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.57.1",
|
||||
"rollup": "4.59.0",
|
||||
"sass": "1.97.3",
|
||||
"shiki": "3.22.0",
|
||||
"shiki": "3.23.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"uuid": "13.0.0",
|
||||
"vite": "7.3.1",
|
||||
"vue": "3.5.28"
|
||||
"vue": "3.5.29"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
@@ -39,29 +39,29 @@
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/micromatch": "4.0.10",
|
||||
"@types/node": "24.10.13",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"@vue/runtime-core": "3.5.28",
|
||||
"acorn": "8.15.0",
|
||||
"@vue/runtime-core": "3.5.29",
|
||||
"acorn": "8.16.0",
|
||||
"cross-env": "10.1.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.8.0",
|
||||
"happy-dom": "20.6.1",
|
||||
"happy-dom": "20.7.0",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"msw": "2.12.10",
|
||||
"nodemon": "3.1.11",
|
||||
"nodemon": "3.1.14",
|
||||
"prettier": "3.8.1",
|
||||
"start-server-and-test": "2.1.3",
|
||||
"start-server-and-test": "2.1.5",
|
||||
"tsx": "4.21.0",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vue-component-type-helpers": "3.2.4",
|
||||
"vue-component-type-helpers": "3.2.5",
|
||||
"vue-eslint-parser": "10.4.0",
|
||||
"vue-tsc": "3.2.4"
|
||||
"vue-tsc": "3.2.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.10.13",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"esbuild": "0.27.3",
|
||||
"eslint-plugin-vue": "10.8.0",
|
||||
"nodemon": "3.1.11",
|
||||
"nodemon": "3.1.14",
|
||||
"vue-eslint-parser": "10.4.0"
|
||||
},
|
||||
"files": [
|
||||
@@ -35,6 +35,6 @@
|
||||
"dependencies": {
|
||||
"i18n": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
"vue": "3.5.28"
|
||||
"vue": "3.5.29"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.3",
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@sentry/vue": "10.38.0",
|
||||
"@sentry/vue": "10.40.0",
|
||||
"@syuilo/aiscript": "1.2.1",
|
||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
@@ -43,13 +43,13 @@
|
||||
"chartjs-chart-matrix": "3.0.0",
|
||||
"chartjs-plugin-gradient": "0.6.1",
|
||||
"chartjs-plugin-zoom": "2.2.0",
|
||||
"chromatic": "15.1.0",
|
||||
"chromatic": "15.2.0",
|
||||
"compare-versions": "6.1.1",
|
||||
"cropperjs": "2.1.0",
|
||||
"date-fns": "4.1.0",
|
||||
"eventemitter3": "5.0.4",
|
||||
"execa": "9.6.1",
|
||||
"exifreader": "4.36.1",
|
||||
"exifreader": "4.36.2",
|
||||
"frontend-shared": "workspace:*",
|
||||
"i18n": "workspace:*",
|
||||
"icons-subsetter": "workspace:*",
|
||||
@@ -59,7 +59,7 @@
|
||||
"is-file-animated": "1.0.2",
|
||||
"json5": "2.2.3",
|
||||
"matter-js": "0.20.0",
|
||||
"mediabunny": "1.34.2",
|
||||
"mediabunny": "1.35.1",
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
@@ -68,38 +68,38 @@
|
||||
"punycode.js": "2.3.1",
|
||||
"qr-code-styling": "1.9.2",
|
||||
"qr-scanner": "1.4.2",
|
||||
"rollup": "4.57.1",
|
||||
"sanitize-html": "2.17.0",
|
||||
"rollup": "4.59.0",
|
||||
"sanitize-html": "2.17.1",
|
||||
"sass": "1.97.3",
|
||||
"shiki": "3.22.0",
|
||||
"shiki": "3.23.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.182.0",
|
||||
"three": "0.183.2",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "7.3.1",
|
||||
"vue": "3.5.28",
|
||||
"vue": "3.5.29",
|
||||
"wanakana": "5.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@storybook/addon-essentials": "8.6.15",
|
||||
"@storybook/addon-interactions": "8.6.15",
|
||||
"@storybook/addon-links": "10.2.8",
|
||||
"@storybook/addon-mdx-gfm": "8.6.15",
|
||||
"@storybook/addon-storysource": "8.6.15",
|
||||
"@storybook/blocks": "8.6.15",
|
||||
"@storybook/components": "8.6.15",
|
||||
"@storybook/core-events": "8.6.15",
|
||||
"@storybook/manager-api": "8.6.15",
|
||||
"@storybook/preview-api": "8.6.15",
|
||||
"@storybook/react": "10.2.8",
|
||||
"@storybook/react-vite": "10.2.8",
|
||||
"@storybook/test": "8.6.15",
|
||||
"@storybook/theming": "8.6.15",
|
||||
"@storybook/types": "8.6.15",
|
||||
"@storybook/vue3": "10.2.8",
|
||||
"@storybook/vue3-vite": "10.2.8",
|
||||
"@storybook/addon-essentials": "8.6.17",
|
||||
"@storybook/addon-interactions": "8.6.17",
|
||||
"@storybook/addon-links": "10.2.13",
|
||||
"@storybook/addon-mdx-gfm": "8.6.17",
|
||||
"@storybook/addon-storysource": "8.6.17",
|
||||
"@storybook/blocks": "8.6.17",
|
||||
"@storybook/components": "8.6.17",
|
||||
"@storybook/core-events": "8.6.17",
|
||||
"@storybook/manager-api": "8.6.17",
|
||||
"@storybook/preview-api": "8.6.17",
|
||||
"@storybook/react": "10.2.13",
|
||||
"@storybook/react-vite": "10.2.13",
|
||||
"@storybook/test": "8.6.17",
|
||||
"@storybook/theming": "8.6.17",
|
||||
"@storybook/types": "8.6.17",
|
||||
"@storybook/vue3": "10.2.13",
|
||||
"@storybook/vue3-vite": "10.2.13",
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/canvas-confetti": "1.9.0",
|
||||
@@ -107,46 +107,46 @@
|
||||
"@types/insert-text-at-cursor": "0.3.2",
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/micromatch": "4.0.10",
|
||||
"@types/node": "24.10.13",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/textarea-caret": "3.0.4",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"@vue/compiler-core": "3.5.28",
|
||||
"acorn": "8.15.0",
|
||||
"@vue/compiler-core": "3.5.29",
|
||||
"acorn": "8.16.0",
|
||||
"astring": "1.9.0",
|
||||
"cross-env": "10.1.0",
|
||||
"cypress": "15.10.0",
|
||||
"cypress": "15.11.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.8.0",
|
||||
"estree-walker": "3.0.3",
|
||||
"happy-dom": "20.6.1",
|
||||
"happy-dom": "20.7.0",
|
||||
"intersection-observer": "0.12.2",
|
||||
"magic-string": "0.30.21",
|
||||
"micromatch": "4.0.8",
|
||||
"minimatch": "10.2.2",
|
||||
"minimatch": "10.2.4",
|
||||
"msw": "2.12.10",
|
||||
"msw-storybook-addon": "2.0.6",
|
||||
"nodemon": "3.1.11",
|
||||
"nodemon": "3.1.14",
|
||||
"prettier": "3.8.1",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "2.1.3",
|
||||
"storybook": "10.2.8",
|
||||
"start-server-and-test": "2.1.5",
|
||||
"storybook": "10.2.13",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"tsx": "4.21.0",
|
||||
"vite-plugin-glsl": "1.5.5",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "4.0.18",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "3.2.4",
|
||||
"vue-component-type-helpers": "3.2.5",
|
||||
"vue-eslint-parser": "10.4.0",
|
||||
"vue-tsc": "3.2.4"
|
||||
"vue-tsc": "3.2.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ function onDragstart(ev: DragEvent, item: T) {
|
||||
|
||||
// Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう
|
||||
// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
|
||||
// SEE: https://issues.chromium.org/issues/41150279
|
||||
window.setTimeout(() => {
|
||||
dragging.value = true;
|
||||
}, 10);
|
||||
|
||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
|
||||
<div v-if="Object.values(form).filter(item => typeof item.hidden !== 'boolean' || item.hidden === true).length > 0" class="_gaps_m">
|
||||
<template v-for="v, k in form">
|
||||
<template v-if="typeof v.hidden == 'function' ? v.hidden(values) : v.hidden"></template>
|
||||
<MkInput v-else-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1" :manualSave="v.manualSave" @savingStateChange="(changed, invalid) => onSavingStateChange(k, changed, invalid)">
|
||||
|
||||
@@ -233,7 +233,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
||||
if (!useAnim) {
|
||||
return genEl(token.children, scale);
|
||||
}
|
||||
return h(MkSparkle, {}, genEl(token.children, scale));
|
||||
return h(MkSparkle, {}, { default: () => genEl(token.children, scale) });
|
||||
}
|
||||
case 'rotate': {
|
||||
const degrees = safeParseFloat(token.props.args.deg) ?? 90;
|
||||
@@ -363,7 +363,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
||||
url: token.props.url,
|
||||
rel: 'nofollow noopener',
|
||||
navigationBehavior: props.linkNavigationBehavior,
|
||||
}, genEl(token.children, scale, true))];
|
||||
}, { default: () => genEl(token.children, scale, true) })];
|
||||
}
|
||||
|
||||
case 'mention': {
|
||||
@@ -381,7 +381,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
||||
to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`,
|
||||
style: 'color:var(--MI_THEME-hashtag);',
|
||||
behavior: props.linkNavigationBehavior,
|
||||
}, `#${token.props.hashtag}`)];
|
||||
}, { default: () => `#${token.props.hashtag}` })];
|
||||
}
|
||||
|
||||
case 'blockCode': {
|
||||
|
||||
@@ -82,8 +82,10 @@ export class Pizzax<T extends StateDef> {
|
||||
this.r = {} as ReactiveState<T>;
|
||||
|
||||
for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) {
|
||||
this.s[k] = v.default;
|
||||
this.r[k] = ref(v.default);
|
||||
// 参照渡しになるのを防ぐためclone
|
||||
const defaultValue = deepClone(v.default);
|
||||
this.s[k] = defaultValue;
|
||||
this.r[k] = ref(defaultValue);
|
||||
}
|
||||
|
||||
this.ready = this.init();
|
||||
@@ -120,7 +122,8 @@ export class Pizzax<T extends StateDef> {
|
||||
} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
|
||||
this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
|
||||
} else {
|
||||
this.r[k].value = this.s[k] = v.default;
|
||||
// 参照渡しになるのを防ぐためclone
|
||||
this.r[k].value = this.s[k] = deepClone(v.default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +151,8 @@ export class Pizzax<T extends StateDef> {
|
||||
this.r[k].value = this.s[k] = (kvs as Partial<T>)[k];
|
||||
cache[k] = (kvs as Partial<T>)[k];
|
||||
} else {
|
||||
this.r[k].value = this.s[k] = v.default;
|
||||
// 参照渡しになるのを防ぐためclone
|
||||
this.r[k].value = this.s[k] = deepClone(v.default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,8 +222,10 @@ export class Pizzax<T extends StateDef> {
|
||||
}
|
||||
|
||||
public reset(key: keyof T) {
|
||||
this.set(key, this.def[key].default);
|
||||
return this.def[key].default;
|
||||
// 参照渡しになるのを防ぐためclone
|
||||
const defaultValue = deepClone(this.def[key].default);
|
||||
this.set(key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
>
|
||||
<template #default="{ item, dragStart }">
|
||||
<div :class="$style.item">
|
||||
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
|
||||
<!-- divが無いとエラーになる -->
|
||||
<RolesEditorFormula
|
||||
:modelValue="item"
|
||||
:dragStartCallback="dragStart"
|
||||
|
||||
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div>
|
||||
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
|
||||
<!-- divが無いとエラーになる -->
|
||||
<component :is="getComponent(item.type) as any" :modelValue="item" @update:modelValue="updateItem" @remove="() => removeItem(item)"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -51,10 +51,12 @@ import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utili
|
||||
import { store } from '@/store.js';
|
||||
import { signout } from '@/signout.js';
|
||||
import { genSearchIndexes } from '@/utility/inapp-search.js';
|
||||
import { enableStoragePersistence, storagePersisted, storagePersistenceSupported, skipStoragePersistence } from '@/utility/storage.js';
|
||||
import { enableStoragePersistence, getStoragePersistenceStatusRef, storagePersistenceSupported, skipStoragePersistence } from '@/utility/storage.js';
|
||||
|
||||
const searchIndex = await import('search-index:settings').then(({ searchIndexes }) => genSearchIndexes(searchIndexes));
|
||||
|
||||
const storagePersisted = await getStoragePersistenceStatusRef();
|
||||
|
||||
const indexInfo = {
|
||||
title: i18n.ts.settings,
|
||||
icon: 'ti ti-settings',
|
||||
|
||||
@@ -165,7 +165,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSlot from '@/components/form/slot.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { enableStoragePersistence, storagePersisted, storagePersistenceSupported } from '@/utility/storage.js';
|
||||
import { enableStoragePersistence, getStoragePersistenceStatusRef, storagePersistenceSupported } from '@/utility/storage.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
@@ -180,6 +180,8 @@ import { cloudBackup } from '@/preferences/utility.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const storagePersisted = await getStoragePersistenceStatusRef();
|
||||
|
||||
const reportError = prefer.model('reportError');
|
||||
const enableCondensedLine = prefer.model('enableCondensedLine');
|
||||
const skipNoteRender = prefer.model('skipNoteRender');
|
||||
|
||||
@@ -14,6 +14,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { deepEqual } from '@/utility/deep-equal.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
|
||||
// NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない
|
||||
|
||||
@@ -122,7 +123,8 @@ export function getInitialPrefValue<K extends keyof PREF>(k: K): ValueOf<K> {
|
||||
if (typeof _default === 'function') { // factory
|
||||
return _default() as ValueOf<K>;
|
||||
} else {
|
||||
return _default as unknown as ValueOf<K>;
|
||||
// 参照渡しになるのを防ぐためclone
|
||||
return deepClone(_default as unknown as ValueOf<K>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ const float PI = 3.141592653589793;
|
||||
const float TWO_PI = 6.283185307179586;
|
||||
const float HALF_PI = 1.5707963267948966;
|
||||
|
||||
const float goldenAngle = 2.39996323;
|
||||
const int sampleCount = 256;
|
||||
const float sampleCountF = float(sampleCount);
|
||||
|
||||
in vec2 in_uv;
|
||||
uniform sampler2D in_texture;
|
||||
uniform vec2 in_resolution;
|
||||
@@ -18,7 +22,6 @@ uniform vec2 u_scale;
|
||||
uniform bool u_ellipse;
|
||||
uniform float u_angle;
|
||||
uniform float u_radius;
|
||||
uniform int u_samples;
|
||||
out vec4 out_color;
|
||||
|
||||
float rand(vec2 value) {
|
||||
@@ -51,17 +54,7 @@ void main() {
|
||||
|
||||
vec4 result = vec4(0.0);
|
||||
float totalSamples = 0.0;
|
||||
|
||||
// Make blur radius resolution-independent by using a percentage of image size
|
||||
float referenceSize = min(in_resolution.x, in_resolution.y);
|
||||
float normalizedRadius = u_radius / 100.0;
|
||||
float radiusPx = normalizedRadius * referenceSize;
|
||||
vec2 texelSize = 1.0 / in_resolution;
|
||||
|
||||
int sampleCount = max(u_samples, 1);
|
||||
float sampleCountF = float(sampleCount);
|
||||
float jitter = rand(in_uv * in_resolution);
|
||||
float goldenAngle = 2.39996323;
|
||||
float jitter = rand(in_uv);
|
||||
|
||||
// Sample in a circular pattern to avoid axis-aligned artifacts
|
||||
for (int i = 0; i < sampleCount; i++) {
|
||||
@@ -69,15 +62,11 @@ void main() {
|
||||
float radius = sqrt((fi + 0.5) / sampleCountF);
|
||||
float theta = (fi + jitter) * goldenAngle;
|
||||
vec2 direction = vec2(cos(theta), sin(theta));
|
||||
vec2 offset = direction * (radiusPx * radius) * texelSize;
|
||||
vec2 sampleUV = in_uv + offset;
|
||||
|
||||
if (sampleUV.x >= 0.0 && sampleUV.x <= 1.0 && sampleUV.y >= 0.0 && sampleUV.y <= 1.0) {
|
||||
float weight = exp(-radius * radius * 4.0);
|
||||
result += texture(in_texture, sampleUV) * weight;
|
||||
totalSamples += weight;
|
||||
}
|
||||
vec2 offset = direction * (u_radius * radius);
|
||||
float weight = exp(-radius * radius * 4.0);
|
||||
result += texture(in_texture, in_uv + offset) * weight;
|
||||
totalSamples += weight;
|
||||
}
|
||||
|
||||
out_color = totalSamples > 0.0 ? result / totalSamples : texture(in_texture, in_uv);
|
||||
out_color = result / totalSamples;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ export const fn = defineImageCompositorFunction<{
|
||||
gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0);
|
||||
gl.uniform1f(u.angle, params.angle / 2);
|
||||
gl.uniform1f(u.radius, params.radius);
|
||||
gl.uniform1i(u.samples, 256);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -84,10 +83,10 @@ export const uiDefinition = {
|
||||
radius: {
|
||||
label: i18n.ts._imageEffector._fxProps.strength,
|
||||
type: 'number',
|
||||
default: 10.0,
|
||||
default: 0.15,
|
||||
min: 0.0,
|
||||
max: 20.0,
|
||||
step: 0.5,
|
||||
max: 0.3,
|
||||
step: 0.01,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
|
||||
@@ -14,12 +14,15 @@ uniform sampler2D in_texture;
|
||||
uniform vec2 in_resolution;
|
||||
uniform vec2 u_pos;
|
||||
uniform float u_frequency;
|
||||
uniform bool u_thresholdEnabled;
|
||||
uniform float u_threshold;
|
||||
uniform float u_outlineThickness;
|
||||
uniform float u_maskSize;
|
||||
uniform bool u_black;
|
||||
out vec4 out_color;
|
||||
|
||||
float remap(float value, float inputMin, float inputMax, float outputMin, float outputMax) {
|
||||
return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 in_color = texture(in_texture, in_uv);
|
||||
vec2 centeredUv = (in_uv - vec2(0.5, 0.5));
|
||||
@@ -33,16 +36,19 @@ void main() {
|
||||
float noiseY = (noiseUV.y + seed) * u_frequency;
|
||||
float noise = (1.0 + snoise(vec3(noiseX, noiseY, time))) / 2.0;
|
||||
|
||||
float t = noise;
|
||||
if (u_thresholdEnabled) t = t < u_threshold ? 1.0 : 0.0;
|
||||
if (noise < u_threshold) {
|
||||
out_color = in_color;
|
||||
} else {
|
||||
float n = remap(noise, u_threshold, 1.0, 0.0, 1.0);
|
||||
|
||||
// TODO: マスクの形自体も揺らぎを与える
|
||||
float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0));
|
||||
float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0)));
|
||||
out_color = vec4(
|
||||
mix(in_color.r, u_black ? 0.0 : 1.0, t * mask),
|
||||
mix(in_color.g, u_black ? 0.0 : 1.0, t * mask),
|
||||
mix(in_color.b, u_black ? 0.0 : 1.0, t * mask),
|
||||
in_color.a
|
||||
);
|
||||
// TODO: マスクの形自体も揺らぎを与える
|
||||
float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0));
|
||||
float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0)));
|
||||
out_color = vec4(
|
||||
mix(in_color.r, n < u_outlineThickness ? 0.0 : 1.0, mask),
|
||||
mix(in_color.g, n < u_outlineThickness ? 0.0 : 1.0, mask),
|
||||
mix(in_color.b, n < u_outlineThickness ? 0.0 : 1.0, mask),
|
||||
in_color.a
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,20 +12,17 @@ export const fn = defineImageCompositorFunction<{
|
||||
x: number;
|
||||
y: number;
|
||||
frequency: number;
|
||||
smoothing: boolean;
|
||||
threshold: number;
|
||||
density: number;
|
||||
outlineThickness: number;
|
||||
maskSize: number;
|
||||
black: boolean;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform2f(u.pos, params.x / 2, params.y / 2);
|
||||
gl.uniform1f(u.frequency, params.frequency * params.frequency);
|
||||
// thresholdの調整が有効な間はsmoothingが利用できない
|
||||
gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1);
|
||||
gl.uniform1f(u.threshold, params.threshold);
|
||||
gl.uniform1f(u.threshold, 1.0 - params.density);
|
||||
gl.uniform1f(u.outlineThickness, params.outlineThickness);
|
||||
gl.uniform1f(u.maskSize, params.maskSize);
|
||||
gl.uniform1i(u.black, params.black ? 1 : 0);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -56,20 +53,22 @@ export const uiDefinition = {
|
||||
max: 15.0,
|
||||
step: 0.1,
|
||||
},
|
||||
smoothing: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesSmoothing,
|
||||
caption: i18n.ts._imageEffector._fxProps.zoomLinesSmoothingDescription,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
threshold: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesThreshold,
|
||||
density: {
|
||||
label: i18n.ts._imageEffector._fxProps.density,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
outlineThickness: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesOutlineThickness,
|
||||
type: 'number',
|
||||
default: 0.25,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
maskSize: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesMaskSize,
|
||||
type: 'number',
|
||||
@@ -78,10 +77,5 @@ export const uiDefinition = {
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
black: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesBlack,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
|
||||
@@ -3,13 +3,21 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { readonly, ref } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
import { store } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const storagePersistenceSupported = window.isSecureContext && 'storage' in navigator;
|
||||
export const storagePersisted = ref(storagePersistenceSupported ? await navigator.storage.persisted() : false);
|
||||
const storagePersisted = ref(false);
|
||||
|
||||
export async function getStoragePersistenceStatusRef() {
|
||||
if (storagePersistenceSupported) {
|
||||
storagePersisted.value = await navigator.storage.persisted().catch(() => false);
|
||||
}
|
||||
|
||||
return readonly(storagePersisted);
|
||||
}
|
||||
|
||||
export async function enableStoragePersistence() {
|
||||
if (!storagePersistenceSupported) return;
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useLowresTime } from '@/composables/use-lowres-time.js';
|
||||
import { userPage, acct } from '@/filters/user.js';
|
||||
|
||||
const props = defineProps<{
|
||||
item: Misskey.entities.UsersGetFollowingBirthdayUsersResponse[number];
|
||||
item: Misskey.entities.UsersGetFollowingUsersByBirthdayResponse[number];
|
||||
}>();
|
||||
|
||||
const now = useLowresTime();
|
||||
|
||||
@@ -106,7 +106,7 @@ const end = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const birthdayUsersPaginator = markRaw(new Paginator('users/get-following-birthday-users', {
|
||||
const birthdayUsersPaginator = markRaw(new Paginator('users/get-following-users-by-birthday', {
|
||||
limit: 18,
|
||||
offsetMode: true,
|
||||
computedParams: computed(() => {
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.10.13",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"chokidar": "5.0.0",
|
||||
"esbuild": "0.27.3",
|
||||
"execa": "9.6.1",
|
||||
"nodemon": "3.1.11",
|
||||
"nodemon": "3.1.14",
|
||||
"tsx": "4.21.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -13109,25 +13109,17 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"centerY": string;
|
||||
/**
|
||||
* スムージング
|
||||
* 密度
|
||||
*/
|
||||
"zoomLinesSmoothing": string;
|
||||
"density": string;
|
||||
/**
|
||||
* スムージングと集中線の幅の設定は併用できません。
|
||||
* 線の影の太さ
|
||||
*/
|
||||
"zoomLinesSmoothingDescription": string;
|
||||
/**
|
||||
* 集中線の幅
|
||||
*/
|
||||
"zoomLinesThreshold": string;
|
||||
"zoomLinesOutlineThickness": string;
|
||||
/**
|
||||
* 中心径
|
||||
*/
|
||||
"zoomLinesMaskSize": string;
|
||||
/**
|
||||
* 黒色にする
|
||||
*/
|
||||
"zoomLinesBlack": string;
|
||||
/**
|
||||
* 円形
|
||||
*/
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.10.13",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/wawoff2": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
"harfbuzzjs": "0.8.0",
|
||||
"harfbuzzjs": "0.10.0",
|
||||
"tsx": "4.21.0",
|
||||
"wawoff2": "2.0.1"
|
||||
},
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/node": "24.10.13",
|
||||
"@types/node": "24.11.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"esbuild": "0.27.3",
|
||||
"execa": "9.6.1",
|
||||
"nodemon": "3.1.11"
|
||||
"nodemon": "3.1.14"
|
||||
},
|
||||
"files": [
|
||||
"built"
|
||||
|
||||
@@ -2128,8 +2128,8 @@ declare namespace entities {
|
||||
UsersFollowingResponse,
|
||||
UsersGalleryPostsRequest,
|
||||
UsersGalleryPostsResponse,
|
||||
UsersGetFollowingBirthdayUsersRequest,
|
||||
UsersGetFollowingBirthdayUsersResponse,
|
||||
UsersGetFollowingUsersByBirthdayRequest,
|
||||
UsersGetFollowingUsersByBirthdayResponse,
|
||||
UsersGetFrequentlyRepliedUsersRequest,
|
||||
UsersGetFrequentlyRepliedUsersResponse,
|
||||
UsersListsCreateRequest,
|
||||
@@ -3741,10 +3741,10 @@ type UsersGalleryPostsRequest = operations['users___gallery___posts']['requestBo
|
||||
type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type UsersGetFollowingBirthdayUsersRequest = operations['users___get-following-birthday-users']['requestBody']['content']['application/json'];
|
||||
type UsersGetFollowingUsersByBirthdayRequest = operations['users___get-following-users-by-birthday']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type UsersGetFollowingBirthdayUsersResponse = operations['users___get-following-birthday-users']['responses']['200']['content']['application/json'];
|
||||
type UsersGetFollowingUsersByBirthdayResponse = operations['users___get-following-users-by-birthday']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json'];
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@readme/openapi-parser": "5.5.0",
|
||||
"@types/node": "24.10.13",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"openapi-types": "12.1.3",
|
||||
"openapi-typescript": "7.13.0",
|
||||
"ts-case-convert": "2.1.0",
|
||||
"tsx": "4.21.0",
|
||||
"eslint": "9.39.2"
|
||||
"eslint": "9.39.3"
|
||||
},
|
||||
"files": [
|
||||
"built"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2026.2.0-beta.0",
|
||||
"version": "2026.3.2-alpha.0",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
@@ -37,15 +37,15 @@
|
||||
"directory": "packages/misskey-js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "7.56.3",
|
||||
"@types/node": "24.10.13",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@microsoft/api-extractor": "7.57.6",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"esbuild": "0.27.3",
|
||||
"execa": "9.6.1",
|
||||
"ncp": "2.0.0",
|
||||
"nodemon": "3.1.11",
|
||||
"nodemon": "3.1.14",
|
||||
"tsd": "0.33.0",
|
||||
"vitest": "4.0.18",
|
||||
"vitest-websocket-mock": "0.5.0"
|
||||
|
||||
@@ -4533,11 +4533,11 @@ declare module '../api.js' {
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* Find users who have a birthday on the specified range.
|
||||
* Retrieve users who have a birthday on the specified range.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||
*/
|
||||
request<E extends 'users/get-following-birthday-users', P extends Endpoints[E]['req']>(
|
||||
request<E extends 'users/get-following-users-by-birthday', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
|
||||
@@ -616,8 +616,8 @@ import type {
|
||||
UsersFollowingResponse,
|
||||
UsersGalleryPostsRequest,
|
||||
UsersGalleryPostsResponse,
|
||||
UsersGetFollowingBirthdayUsersRequest,
|
||||
UsersGetFollowingBirthdayUsersResponse,
|
||||
UsersGetFollowingUsersByBirthdayRequest,
|
||||
UsersGetFollowingUsersByBirthdayResponse,
|
||||
UsersGetFrequentlyRepliedUsersRequest,
|
||||
UsersGetFrequentlyRepliedUsersResponse,
|
||||
UsersListsCreateRequest,
|
||||
@@ -1069,7 +1069,7 @@ export type Endpoints = {
|
||||
'users/followers': { req: UsersFollowersRequest; res: UsersFollowersResponse };
|
||||
'users/following': { req: UsersFollowingRequest; res: UsersFollowingResponse };
|
||||
'users/gallery/posts': { req: UsersGalleryPostsRequest; res: UsersGalleryPostsResponse };
|
||||
'users/get-following-birthday-users': { req: UsersGetFollowingBirthdayUsersRequest; res: UsersGetFollowingBirthdayUsersResponse };
|
||||
'users/get-following-users-by-birthday': { req: UsersGetFollowingUsersByBirthdayRequest; res: UsersGetFollowingUsersByBirthdayResponse };
|
||||
'users/get-frequently-replied-users': { req: UsersGetFrequentlyRepliedUsersRequest; res: UsersGetFrequentlyRepliedUsersResponse };
|
||||
'users/lists/create': { req: UsersListsCreateRequest; res: UsersListsCreateResponse };
|
||||
'users/lists/create-from-public': { req: UsersListsCreateFromPublicRequest; res: UsersListsCreateFromPublicResponse };
|
||||
|
||||
@@ -619,8 +619,8 @@ export type UsersFollowingRequest = operations['users___following']['requestBody
|
||||
export type UsersFollowingResponse = operations['users___following']['responses']['200']['content']['application/json'];
|
||||
export type UsersGalleryPostsRequest = operations['users___gallery___posts']['requestBody']['content']['application/json'];
|
||||
export type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json'];
|
||||
export type UsersGetFollowingBirthdayUsersRequest = operations['users___get-following-birthday-users']['requestBody']['content']['application/json'];
|
||||
export type UsersGetFollowingBirthdayUsersResponse = operations['users___get-following-birthday-users']['responses']['200']['content']['application/json'];
|
||||
export type UsersGetFollowingUsersByBirthdayRequest = operations['users___get-following-users-by-birthday']['requestBody']['content']['application/json'];
|
||||
export type UsersGetFollowingUsersByBirthdayResponse = operations['users___get-following-users-by-birthday']['responses']['200']['content']['application/json'];
|
||||
export type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json'];
|
||||
export type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];
|
||||
export type UsersListsCreateRequest = operations['users___lists___create']['requestBody']['content']['application/json'];
|
||||
|
||||
@@ -3717,14 +3717,14 @@ export type paths = {
|
||||
*/
|
||||
post: operations['users___gallery___posts'];
|
||||
};
|
||||
'/users/get-following-birthday-users': {
|
||||
'/users/get-following-users-by-birthday': {
|
||||
/**
|
||||
* users/get-following-birthday-users
|
||||
* @description Find users who have a birthday on the specified range.
|
||||
* users/get-following-users-by-birthday
|
||||
* @description Retrieve users who have a birthday on the specified range.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||
*/
|
||||
post: operations['users___get-following-birthday-users'];
|
||||
post: operations['users___get-following-users-by-birthday'];
|
||||
};
|
||||
'/users/get-frequently-replied-users': {
|
||||
/**
|
||||
@@ -34882,7 +34882,7 @@ export interface operations {
|
||||
untilDate?: number;
|
||||
/** @default 10 */
|
||||
limit?: number;
|
||||
/** @description @deprecated use get-following-birthday-users instead. */
|
||||
/** @description @deprecated use get-following-users-by-birthday instead. */
|
||||
birthday?: string | null;
|
||||
};
|
||||
};
|
||||
@@ -35018,7 +35018,7 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
'users___get-following-birthday-users': {
|
||||
'users___get-following-users-by-birthday': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.10.13",
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@types/node": "24.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"esbuild": "0.27.3",
|
||||
"execa": "9.6.1",
|
||||
"nodemon": "3.1.11"
|
||||
"nodemon": "3.1.14"
|
||||
},
|
||||
"files": [
|
||||
"built"
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
"misskey-js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"nodemon": "3.1.11"
|
||||
"nodemon": "3.1.14"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user