Merge upstream

This commit is contained in:
オスカー、 2024-12-22 12:44:09 +09:00
commit 29c25555b8
Signed by: SWREI
GPG Key ID: 8947F3AF5B0B4BFE
40 changed files with 1183 additions and 667 deletions

8
locales/index.d.ts vendored
View File

@ -5516,6 +5516,14 @@ export interface Locale extends ILocale {
* IDを一行に一つずつ書きます
*/
"endingCreditMembersDescription": string;
/**
*
*/
"emailAddressLogin": string;
/**
*
*/
"usernameLogin": string;
"_bubbleGame": {
/**
*

View File

@ -1372,6 +1372,8 @@ muteNotification: "通知をミュートする"
unmuteNotification: "通知をミュート解除する"
endingCreditMembers: "スタッフロールに表示するユーザー"
endingCreditMembersDescription: "サーバー情報ページに表示するユーザーのIDを一行に一つずつ書きます。"
emailAddressLogin: "メールアドレスでログイン"
usernameLogin: "ユーザー名でログイン"
_bubbleGame:
howToPlay: "遊び方"

View File

@ -48,11 +48,11 @@
"resolutions": {
"@tensorflow/tfjs-core": "4.22.0",
"axios": "1.7.9",
"chokidar": "4.0.2",
"chokidar": "4.0.3",
"cookie": "1.0.2",
"cookie-signature": "1.2.2",
"debug": "4.4.0",
"esbuild": "0.24.0",
"esbuild": "0.24.2",
"jpeg-js": "0.4.4",
"lodash": "4.17.21",
"punycode": "npm:punycode.js@2.3.1",

View File

@ -63,8 +63,8 @@
},
"dependencies": {
"@authenio/samlify-node-xmllint": "2.0.0",
"@aws-sdk/client-s3": "3.714.0",
"@aws-sdk/lib-storage": "3.714.0",
"@aws-sdk/client-s3": "3.717.0",
"@aws-sdk/lib-storage": "3.717.0",
"@bull-board/api": "6.5.3",
"@bull-board/fastify": "6.5.3",
"@bull-board/ui": "6.5.3",
@ -80,7 +80,7 @@
"@fastify/static": "8.0.3",
"@fastify/view": "10.0.1",
"@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "MisskeyIO/summaly#5.1.2",
"@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3",
"@napi-rs/canvas": "0.1.65",
"@nestjs/common": "10.4.15",
"@nestjs/core": "10.4.15",
@ -99,12 +99,12 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.3",
"bullmq": "5.34.2",
"bullmq": "5.34.4",
"cacheable-lookup": "7.0.0",
"cbor": "10.0.3",
"chalk": "5.3.0",
"chalk": "5.4.0",
"chalk-template": "1.1.0",
"chokidar": "4.0.2",
"chokidar": "4.0.3",
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
@ -122,7 +122,7 @@
"hpagent": "1.2.0",
"htmlescape": "1.1.1",
"http-link-header": "1.1.3",
"ioredis": "5.4.1",
"ioredis": "5.4.2",
"ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0",
"is-svg": "5.1.0",
@ -169,13 +169,13 @@
"rss-parser": "3.13.0",
"rxjs": "7.8.1",
"samlify": "2.8.11",
"sanitize-html": "2.13.1",
"sanitize-html": "2.14.0",
"secure-json-parse": "3.0.1",
"sharp": "0.33.5",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"systeminformation": "5.23.13",
"systeminformation": "5.23.14",
"tinycolor2": "1.6.0",
"tmp": "0.2.3",
"tsc-alias": "1.8.10",

View File

@ -389,6 +389,7 @@ import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___users_get_security_info from './endpoints/users/get-security-info.js';
import * as ep___users_achievements from './endpoints/users/achievements.js';
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
@ -765,6 +766,7 @@ const $users_following: Provider = { provide: 'ep:users/following', useClass: ep
const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
const $users_getFollowingBirthdayUsers: Provider = { provide: 'ep:users/get-following-birthday-users', useClass: ep___users_getFollowingBirthdayUsers.default };
const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
const $users_getSecurityInfo: Provider = { provide: 'ep:users/get-security-info', useClass: ep___users_get_security_info.default };
const $users_getSkebStatus: Provider = { provide: 'ep:users/get-skeb-status', useClass: ep___users_getSkebStatus.default };
const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
@ -1170,6 +1172,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_gallery_posts,
$users_getFollowingBirthdayUsers,
$users_getFrequentlyRepliedUsers,
$users_getSecurityInfo,
$users_getSkebStatus,
$users_featuredNotes,
$users_lists_create,
@ -1567,6 +1570,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$users_gallery_posts,
$users_getFollowingBirthdayUsers,
$users_getFrequentlyRepliedUsers,
$users_getSecurityInfo,
$users_getSkebStatus,
$users_featuredNotes,
$users_lists_create,

View File

@ -3,11 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type {
MiUserProfile,
SigninsRepository,
UserProfilesRepository,
UsersRepository,
@ -27,7 +29,6 @@ import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
import type { AuthenticationResponseJSON } from '@simplewebauthn/server';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { randomUUID } from 'node:crypto';
@Injectable()
export class SigninApiService {
@ -122,22 +123,34 @@ export class SigninApiService {
}
// Fetch user
const user = await this.usersRepository.findOneBy({
usernameLower: username.toLowerCase(),
host: IsNull(),
}) as MiLocalUser;
const profile = await this.userProfilesRepository.findOne({
relations: ['user'],
where: username.includes('@') ? {
email: username,
emailVerified: true,
user: {
host: IsNull(),
}
} : {
user: {
usernameLower: username.toLowerCase(),
host: IsNull(),
}
}
});
const user = (profile?.user as MiLocalUser) ?? null;
if (user == null) {
if (!user || !profile) {
logger.error('No such user.');
return error(404, {
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
return error(403, {
id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
});
}
if (user.isDeleted && user.isSuspended) {
logger.error('No such user. (logical deletion)');
return error(404, {
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
return error(403, {
id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
});
}
@ -148,8 +161,6 @@ export class SigninApiService {
});
}
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
// Compare password
const same = await bcrypt.compare(password, profile.password!);

View File

@ -388,6 +388,7 @@ import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_get_security_info from './endpoints/users/get-security-info.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___users_achievements from './endpoints/users/achievements.js';
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
@ -787,6 +788,7 @@ const eps = [
['users/search-by-username-and-host', ep___users_searchByUsernameAndHost],
['users/search', ep___users_search],
['users/show', ep___users_show],
['users/get-security-info', ep___users_get_security_info],
['users/stats', ep___users_stats],
['users/achievements', ep___users_achievements],
['users/update-memo', ep___users_updateMemo],

View File

@ -11,6 +11,8 @@ export const meta = {
tags: ['meta'],
requireCredential: false,
allowGet: true,
cacheSec: 60,
res: {
type: 'object',

View File

@ -12,6 +12,8 @@ import UsersChart from '@/core/chart/charts/users.js';
export const meta = {
requireCredential: false,
allowGet: true,
cacheSec: 60,
tags: ['meta'],

View File

@ -0,0 +1,72 @@
import { Inject, Injectable } from '@nestjs/common';
import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import bcrypt from 'bcryptjs';
import ms from 'ms';
export const meta = {
tags: ['users'],
requireCredential: false,
limit: {
duration: ms('1hour'),
max: 30,
},
res: {
type: 'object',
properties: {
twoFactorEnabled: { type: 'boolean' },
usePasswordLessLogin: { type: 'boolean' },
securityKeys: { type: 'boolean' },
},
},
errors: {
},
} as const;
export const paramDef = {
type: 'object',
properties: {
email: { type: 'string' },
password: { type: 'string' },
},
required: ['email', 'password'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
@Inject(DI.userSecurityKeysRepository)
private userSecurityKeysRepository: UserSecurityKeysRepository,
) {
super(meta, paramDef, async (ps, me) => {
const profile = await this.userProfilesRepository.findOneBy({
email: ps.email,
emailVerified: true,
});
const passwordMatched = await bcrypt.compare(ps.password, profile?.password ?? '');
if (!profile || !passwordMatched) {
return {
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
};
}
return {
twoFactorEnabled: profile.twoFactorEnabled,
usePasswordLessLogin: profile.usePasswordLessLogin,
securityKeys: profile.twoFactorEnabled
? await this.userSecurityKeysRepository.countBy({ userId: profile.userId }).then(result => result >= 1)
: false,
};
});
}
}

View File

@ -15,6 +15,7 @@ class AntennaChannel extends Channel {
public static readonly requireCredential = true as const;
public static readonly kind = 'read:account';
private antennaId: string;
private idOnly: boolean;
constructor(
private noteEntityService: NoteEntityService,
@ -29,6 +30,7 @@ class AntennaChannel extends Channel {
@bindThis
public async init(params: any) {
this.antennaId = params.antennaId as string;
this.idOnly = params.idOnly ?? false;
// Subscribe stream
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
@ -57,9 +59,13 @@ class AntennaChannel extends Channel {
if (this.isNoteMutedOrBlocked(note.reply)) return;
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
} else {
this.send(data.type, data.body);
}

View File

@ -15,6 +15,7 @@ class ChannelChannel extends Channel {
public static readonly shouldShare = false;
public static readonly requireCredential = false as const;
private channelId: string;
private idOnly: boolean;
constructor(
private noteEntityService: NoteEntityService,
@ -29,6 +30,7 @@ class ChannelChannel extends Channel {
@bindThis
public async init(params: any) {
this.channelId = params.channelId as string;
this.idOnly = params.idOnly ?? false;
// Subscribe stream
this.subscriber.on('notesStream', this.onNote);
@ -63,9 +65,13 @@ class ChannelChannel extends Channel {
}
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}
@bindThis

View File

@ -18,6 +18,7 @@ class GlobalTimelineChannel extends Channel {
public static readonly requireCredential = false as const;
private withRenotes: boolean;
private withFiles: boolean;
private idOnly: boolean;
constructor(
private metaService: MetaService,
@ -38,6 +39,7 @@ class GlobalTimelineChannel extends Channel {
this.withRenotes = params.withRenotes ?? true;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;
// Subscribe events
this.subscriber.on('notesStream', this.onNote);
@ -93,9 +95,13 @@ class GlobalTimelineChannel extends Channel {
}
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}
@bindThis

View File

@ -17,6 +17,7 @@ class HomeTimelineChannel extends Channel {
public static readonly kind = 'read:account';
private withRenotes: boolean;
private withFiles: boolean;
private idOnly: boolean;
constructor(
private noteEntityService: NoteEntityService,
@ -32,6 +33,7 @@ class HomeTimelineChannel extends Channel {
public async init(params: any) {
this.withRenotes = params.withRenotes ?? true;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;
this.subscriber.on('notesStream', this.onNote);
}
@ -94,9 +96,13 @@ class HomeTimelineChannel extends Channel {
}
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}
@bindThis

View File

@ -20,6 +20,7 @@ class HybridTimelineChannel extends Channel {
private withRenotes: boolean;
private withReplies: boolean;
private withFiles: boolean;
private idOnly: boolean;
constructor(
private metaService: MetaService,
@ -41,6 +42,7 @@ class HybridTimelineChannel extends Channel {
this.withRenotes = params.withRenotes ?? true;
this.withReplies = params.withReplies ?? false;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;
// Subscribe events
this.subscriber.on('notesStream', this.onNote);
@ -110,9 +112,13 @@ class HybridTimelineChannel extends Channel {
}
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}
@bindThis

View File

@ -19,6 +19,7 @@ class LocalTimelineChannel extends Channel {
private withRenotes: boolean;
private withReplies: boolean;
private withFiles: boolean;
private idOnly: boolean;
constructor(
private metaService: MetaService,
@ -40,6 +41,7 @@ class LocalTimelineChannel extends Channel {
this.withRenotes = params.withRenotes ?? true;
this.withReplies = params.withReplies ?? false;
this.withFiles = params.withFiles ?? false;
this.idOnly = params.idOnly ?? false;
// Subscribe events
this.subscriber.on('notesStream', this.onNote);
@ -93,9 +95,13 @@ class LocalTimelineChannel extends Channel {
}
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}
@bindThis

View File

@ -16,6 +16,7 @@ class RoleTimelineChannel extends Channel {
public static readonly shouldShare = false;
public static readonly requireCredential = false as const;
private roleId: string;
private idOnly: boolean;
constructor(
private noteEntityService: NoteEntityService,
@ -31,6 +32,7 @@ class RoleTimelineChannel extends Channel {
@bindThis
public async init(params: any) {
this.roleId = params.roleId as string;
this.idOnly = params.idOnly ?? false;
this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent);
}
@ -79,9 +81,13 @@ class RoleTimelineChannel extends Channel {
}
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
} else {
this.send(data.type, data.body);
}

View File

@ -21,6 +21,7 @@ class UserListChannel extends Channel {
private listUsersClock: NodeJS.Timeout;
private withFiles: boolean;
private withRenotes: boolean;
private idOnly: boolean;
constructor(
private userListsRepository: UserListsRepository,
@ -40,6 +41,7 @@ class UserListChannel extends Channel {
this.listId = params.listId as string;
this.withFiles = params.withFiles ?? false;
this.withRenotes = params.withRenotes ?? true;
this.idOnly = params.idOnly ?? false;
// Check existence and owner
const listExist = await this.userListsRepository.exists({
@ -128,9 +130,13 @@ class UserListChannel extends Channel {
}
}
this.connection.cacheNote(note);
this.send('note', note);
if (this.idOnly && ['public', 'home'].includes(note.visibility)) {
const idOnlyNote = { id: note.id };
this.send('note', idOnlyNote);
} else {
this.connection.cacheNote(note);
this.send('note', note);
}
}
@bindThis

View File

@ -616,6 +616,81 @@ export class ClientServerService {
}
});
fastify.get<{ Params: { note: string; } }>('/notes/:note.json', async (request, reply) => {
const note = await this.notesRepository.findOneBy({
id: request.params.note,
visibility: In(['public', 'home']),
});
if (note) {
try {
const _note = await this.noteEntityService.pack(note, null);
reply.header('Content-Type', 'application/json; charset=utf-8');
reply.header('Cache-Control', 'public, max-age=600');
return reply.send(_note);
} catch (err) {
reply.header('Cache-Control', 'max-age=10, must-revalidate');
if (err instanceof IdentifiableError) {
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${err.message}`, {
path: request.routeOptions.url,
params: request.params,
query: request.query,
id: err.id,
error: {
message: err.message,
code: 'INTERNAL_ERROR',
stack: err.stack,
},
});
const httpStatusCode = err.id === '85ab9bd7-3a41-4530-959d-f07073900109' ? 403 : 500;
reply.code(httpStatusCode);
return reply.send({
message: err.message,
code: 'INTERNAL_ERROR',
id: err.id,
kind: 'server',
httpStatusCode,
info: {
message: err.message,
code: err.name,
id: err.id,
},
});
} else {
const error = err as Error;
const errId = randomUUID();
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, {
path: request.routeOptions.url,
params: request.params,
query: request.query,
id: errId,
error: {
message: error.message,
code: error.name,
stack: error.stack,
},
});
reply.code(500);
return reply.send({
message: 'Internal error occurred. Please contact us if the error persists.',
code: 'INTERNAL_ERROR',
id: 'b9f2a7f9-fe64-434b-9484-cb1f804d1a80',
kind: 'server',
httpStatusCode: 500,
info: {
message: error.message,
code: error.name,
id: errId,
},
});
}
}
} else {
reply.code(404);
return;
}
});
// Page
fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => {
const { username, host } = Acct.parse(request.params.user);

View File

@ -32,25 +32,10 @@
renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.')
}
const metaRes = await window.fetch('/api/meta', {
method: 'POST',
body: JSON.stringify({}),
credentials: 'omit',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
});
if (metaRes.status !== 200) {
renderError('META_FETCH');
return;
}
const meta = await metaRes.json();
const v = meta.version;
if (v == null) {
renderError('META_FETCH_V');
return;
}
if (localStorage.getItem('id') === null) {
localStorage.setItem('id', crypto.randomUUID().replaceAll('-', ''));
}
let id = localStorage.getItem('id');
//#region Detect language & fetch translations
if (!localStorage.hasOwn('locale')) {
@ -73,6 +58,25 @@
lang = 'ko-KR';
}
const metaRes = await window.fetch('/api/meta', {
method: 'GET',
credentials: 'omit',
headers: {
'Content-Type': 'application/json',
'X-Client-Transaction-Id': `${id}-misskey-${crypto.randomUUID().replaceAll('-', '')}`
},
});
if (metaRes.status !== 200) {
renderError('META_FETCH');
return;
}
const meta = await metaRes.json();
const v = meta.version;
if (v == null) {
renderError('META_FETCH_V');
return;
}
const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
if (localRes.status === 200) {
localStorage.setItem('lang', lang);

View File

@ -400,7 +400,7 @@ export const waitFire = async <C extends keyof misskey.Channels>(user: UserToken
if (timer) clearTimeout(timer);
res(true);
}
}, params);
}, { ...params, idOnly: false });
} catch (e) {
rej(e);
}

View File

@ -31,7 +31,7 @@
"@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.13",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.9",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.13",
"astring": "1.9.0",
"broadcast-channel": "7.0.0",
"buraha": "0.0.1",
@ -59,10 +59,10 @@
"misskey-reversi": "workspace:*",
"photoswipe": "5.4.4",
"punycode.js": "2.3.1",
"rollup": "4.28.1",
"sanitize-html": "2.13.1",
"rollup": "4.29.1",
"sanitize-html": "2.14.0",
"sass": "1.83.0",
"shiki": "1.24.2",
"shiki": "1.24.3",
"strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0",
"three": "0.171.0",
@ -73,7 +73,7 @@
"typescript": "5.7.2",
"uuid": "11.0.3",
"v-code-diff": "1.13.1",
"vite": "6.0.3",
"vite": "6.0.5",
"vue": "3.5.13",
"vue-gtag": "2.0.1",
"vuedraggable": "next",
@ -81,7 +81,7 @@
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "1.0.0",
"@misskey-dev/summaly": "MisskeyIO/summaly#5.1.2",
"@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3",
"@storybook/addon-actions": "8.4.7",
"@storybook/addon-essentials": "8.4.7",
"@storybook/addon-interactions": "8.4.7",

View File

@ -5,16 +5,16 @@
import { defineAsyncComponent, reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuButton } from '@/types/menu.js';
import { set as gtagSet, time as gtagTime } from 'vue-gtag';
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
import { MenuButton } from '@/types/menu.js';
import { del, get, set } from '@/scripts/idb-proxy.js';
import { apiUrl } from '@/config.js';
import { waiting, popup, popupMenu, success, alert } from '@/os.js';
import { generateClientTransactionId, misskeyApi } from '@/scripts/misskey-api.js';
import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
import { set as gtagSet, time as gtagTime } from 'vue-gtag';
import { instance } from '@/instance.js';
// TODO: 他のタブと永続化されたstateを同期

View File

@ -63,10 +63,6 @@ export async function common(createVue: () => App<Element>) {
});
}
if (miLocalStorage.getItem('id') === null) {
miLocalStorage.setItem('id', crypto.randomUUID());
}
let isClientUpdated = false;
//#region クライアントが更新されたかチェック

View File

@ -6,24 +6,32 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
<div class="_gaps_m">
<div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div>
<div v-show="withAvatar && !loginWithEmailAddress" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div>
<MkInfo v-if="message">
{{ message }}
</MkInfo>
<div v-if="!totpLogin" class="normal-signin _gaps_m">
<MkInput v-model="username" :debounce="true" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
<MkInput v-model="username" :debounce="true" :placeholder="loginWithEmailAddress ? i18n.ts.emailAddress : i18n.ts.username" type="text" :pattern="loginWithEmailAddress ? '^[a-zA-Z0-9_@.]+$' : '^[a-zA-Z0-9_]+$'" :spellcheck="false" :autocomplete="loginWithEmailAddress ? 'email webauthn' : 'username webauthn'" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
<template #prefix>
<i v-if="loginWithEmailAddress" class="ti ti-mail"></i>
<span v-else>@</span>
</template>
<template v-if="!loginWithEmailAddress" #suffix>@{{ host }}</template>
<template #caption>
<button class="_textButton" type="button" tabindex="-1" @click="loginWithEmailAddress = !loginWithEmailAddress">{{ loginWithEmailAddress ? i18n.ts.usernameLogin : i18n.ts.emailAddressLogin }}</button>
</template>
</MkInput>
<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required data-cy-signin-password>
<template #prefix><i class="ti ti-lock"></i></template>
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
<template #caption>
<button class="_textButton" type="button" tabindex="-1" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button>
</template>
</MkInput>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
<MkCaptcha v-if="!user?.twoFactorEnabled && instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
<MkButton type="submit" large primary rounded :disabled="!user || captchaFailed || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
<MkButton type="submit" large primary rounded :disabled="(!loginWithEmailAddress && !user) || captchaFailed || signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
</div>
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
@ -70,6 +78,7 @@ import { instance } from '@/instance.js';
import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
const signing = ref(false);
const loginWithEmailAddress = ref(false);
const userAbortController = ref<AbortController>();
const user = ref<Misskey.entities.UserDetailed | null>(null);
const username = ref('');
@ -119,7 +128,9 @@ const props = defineProps({
},
});
function onUsernameChange(): void {
async function onUsernameChange(): Promise<void> {
if (loginWithEmailAddress.value) return;
if (userAbortController.value) {
userAbortController.value.abort();
}
@ -168,8 +179,15 @@ async function queryKey(): Promise<void> {
});
}
function onSubmit(): void {
async function onSubmit(): Promise<void> {
signing.value = true;
if (loginWithEmailAddress.value) {
user.value = await misskeyApi('users/get-security-info', {
email: username.value,
password: password.value,
});
}
if (!totpLogin.value && user.value?.twoFactorEnabled) {
if (webAuthnSupported() && user.value.securityKeys) {
misskeyApi('signin', {

View File

@ -27,7 +27,7 @@ import { i18n } from '@/i18n.js';
withDefaults(defineProps<{
autoSet?: boolean;
message?: string,
message?: string;
}>(), {
autoSet: false,
message: '',

View File

@ -18,15 +18,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, watch, onUnmounted, provide, shallowRef } from 'vue';
import { time as gtagTime } from 'vue-gtag';
import * as Misskey from 'misskey-js';
import MkNotes from '@/components/MkNotes.vue';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
import { useStream } from '@/stream.js';
import * as sound from '@/scripts/sound.js';
import { $i } from '@/account.js';
import { $i, iAmModerator } from '@/account.js';
import { instance } from '@/instance.js';
import { defaultStore } from '@/store.js';
import { Paging } from '@/components/MkPagination.vue';
import { generateClientTransactionId } from '@/scripts/misskey-api.js';
const props = withDefaults(defineProps<{
src: 'home' | 'local' | 'media' | 'social' | 'global' | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
@ -68,9 +70,36 @@ const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
let tlNotesCount = 0;
function prepend(note) {
async function prepend(data) {
if (tlComponent.value == null) return;
let note = data;
//
// idOnlyid
if (!data.visibility) {
const initiateTime = Date.now();
const res = await window.fetch(`/notes/${data.id}.json`, {
method: 'GET',
credentials: 'omit',
headers: {
'Authorization': 'anonymous',
'X-Client-Transaction-Id': generateClientTransactionId('misskey'),
},
}).then(res => {
if (instance.googleAnalyticsId) {
gtagTime({
name: 'api-get',
event_category: `/notes/${data.id}.json`,
value: Date.now() - initiateTime,
});
}
return res;
});
if (!res.ok) return;
note = await res.json();
}
tlNotesCount++;
if (instance.notesPerOneAd > 0 && tlNotesCount % instance.notesPerOneAd === 0) {
@ -89,6 +118,7 @@ function prepend(note) {
let connection: Misskey.ChannelConnection | null = null;
let connection2: Misskey.ChannelConnection | null = null;
let paginationQuery: Paging | null = null;
const idOnly = !iAmModerator;
const stream = useStream();
@ -97,11 +127,13 @@ function connectChannel() {
if (props.antenna == null) return;
connection = stream.useChannel('antenna', {
antennaId: props.antenna,
idOnly: idOnly,
});
} else if (props.src === 'home') {
connection = stream.useChannel('homeTimeline', {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
idOnly: idOnly,
});
connection2 = stream.useChannel('main');
} else if (props.src === 'local') {
@ -109,23 +141,27 @@ function connectChannel() {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
idOnly: idOnly,
});
} else if (props.src === 'media') {
connection = stream.useChannel('hybridTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: true,
idOnly: idOnly,
});
} else if (props.src === 'social') {
connection = stream.useChannel('hybridTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
idOnly: idOnly,
});
} else if (props.src === 'global') {
connection = stream.useChannel('globalTimeline', {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
idOnly: idOnly,
});
} else if (props.src === 'mentions') {
connection = stream.useChannel('main');
@ -144,16 +180,19 @@ function connectChannel() {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
listId: props.list,
idOnly: idOnly,
});
} else if (props.src === 'channel') {
if (props.channel == null) return;
connection = stream.useChannel('channel', {
channelId: props.channel,
idOnly: idOnly,
});
} else if (props.src === 'role') {
if (props.role == null) return;
connection = stream.useChannel('roleTimeline', {
roleId: props.role,
idOnly: idOnly,
});
}
if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);

View File

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onMounted, shallowRef, ref, nextTick } from 'vue';
import { Chart } from 'chart.js';
import tinycolor from 'tinycolor2';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { chartVLine } from '@/scripts/chart-vline.js';
@ -52,7 +52,7 @@ async function renderChart() {
}));
};
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
const raw = await misskeyApiGet('charts/active-users', { limit: chartLimit, span: 'day' });
fetching.value = false;

View File

@ -81,7 +81,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import MkInfo from '@/components/MkInfo.vue';
import { instanceName } from '@/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { miLocalStorage } from '@/local-storage.js';
@ -91,7 +91,7 @@ import { openInstanceMenu } from '@/ui/_common_/common.js';
const stats = ref<Misskey.entities.StatsResponse | null>(null);
misskeyApi('stats', {}).then((res) => {
misskeyApiGet('stats').then((res) => {
stats.value = res;
});

View File

@ -5,7 +5,7 @@
import { computed, reactive } from 'vue';
import * as Misskey from 'misskey-js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { miLocalStorage } from '@/local-storage.js';
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js';
@ -47,7 +47,7 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
}
}
const meta = await misskeyApi('meta', {
const meta = await misskeyApiGet('meta', {
detail: true,
});

View File

@ -196,7 +196,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkInstanceStats from '@/components/MkInstanceStats.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import number from '@/filters/number.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
@ -229,8 +229,7 @@ watch(tab, () => {
}
});
const initStats = () => misskeyApi('stats', {
}).then((res) => {
const initStats = () => misskeyApiGet('stats').then((res) => {
stats.value = res;
});

View File

@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue';
import MkNumber from '@/components/MkNumber.vue';
import { i18n } from '@/i18n.js';
@ -78,7 +78,7 @@ const fetching = ref(true);
onMounted(async () => {
const [_stats, _onlineUsersCount] = await Promise.all([
misskeyApi('stats', {}),
misskeyApiGet('stats'),
misskeyApiGet('get-online-users-count').then(res => res.count),
]);
stats.value = _stats;

View File

@ -15,11 +15,17 @@ export const pendingApiRequestsCount = ref(0);
let id: string | null = miLocalStorage.getItem('id');
export function generateClientTransactionId(initiator: string) {
if (id === null) {
id = crypto.randomUUID();
id = crypto.randomUUID().replaceAll('-', '');
miLocalStorage.setItem('id', id);
}
return `${id}-${initiator}-${crypto.randomUUID()}`;
// ハイフンが含まれている場合は除去
if (id.includes('-')) {
id = id.replaceAll('-', '');
miLocalStorage.setItem('id', id);
}
return `${id}-${initiator}-${crypto.randomUUID().replaceAll('-', '')}`;
}
function handleResponse<_ResT>(
@ -58,24 +64,22 @@ export function misskeyApi<
if (endpoint.includes('://')) throw new Error('invalid endpoint');
pendingApiRequestsCount.value++;
const credential = token ? token : $i ? $i.token : undefined;
const onFinally = () => {
pendingApiRequestsCount.value--;
};
const promise = new Promise<_ResT>((resolve, reject) => {
// Append a credential
if ($i) (data as any).i = $i.token;
if (token !== undefined) (data as any).i = token;
// Send request
const initiateTime = Date.now();
window.fetch(`${apiUrl}/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
'Authorization': credential ? `Bearer ${credential}` : 'anonymous',
'X-Client-Transaction-Id': generateClientTransactionId(initiator),
},
signal,
@ -121,8 +125,8 @@ export function misskeyApiGet<
window.fetch(`${apiUrl}/${endpoint}?${query}`, {
method: 'GET',
credentials: 'omit',
cache: 'default',
headers: {
'Authorization': 'anonymous',
'X-Client-Transaction-Id': generateClientTransactionId(initiator),
},
}).then(res => {

View File

@ -648,6 +648,7 @@ export type Channels = {
params: {
withRenotes?: boolean;
withFiles?: boolean;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -659,6 +660,7 @@ export type Channels = {
withRenotes?: boolean;
withReplies?: boolean;
withFiles?: boolean;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -670,6 +672,7 @@ export type Channels = {
withRenotes?: boolean;
withReplies?: boolean;
withFiles?: boolean;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -680,6 +683,7 @@ export type Channels = {
params: {
withRenotes?: boolean;
withFiles?: boolean;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -691,6 +695,7 @@ export type Channels = {
listId: string;
withFiles?: boolean;
withRenotes?: boolean;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -709,6 +714,7 @@ export type Channels = {
roleTimeline: {
params: {
roleId: string;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -718,6 +724,7 @@ export type Channels = {
antenna: {
params: {
antennaId: string;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -727,6 +734,7 @@ export type Channels = {
channel: {
params: {
channelId: string;
idOnly?: boolean;
};
events: {
note: (payload: Note) => void;
@ -1788,6 +1796,8 @@ declare namespace entities {
UsersSearchResponse,
UsersShowRequest,
UsersShowResponse,
UsersGetSecurityInfoRequest,
UsersGetSecurityInfoResponse,
UsersStatsRequest,
UsersStatsResponse,
UsersAchievementsRequest,
@ -3194,6 +3204,12 @@ type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-
// @public (undocumented)
type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];
// @public (undocumented)
type UsersGetSecurityInfoRequest = operations['users___get-security-info']['requestBody']['content']['application/json'];
// @public (undocumented)
type UsersGetSecurityInfoResponse = operations['users___get-security-info']['responses']['200']['content']['application/json'];
// @public (undocumented)
type UsersGetSkebStatusRequest = operations['users___get-skeb-status']['requestBody']['content']['application/json'];

View File

@ -4248,6 +4248,17 @@ declare module '../api.js' {
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *No*
*/
request<E extends 'users/get-security-info', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* Show statistics about a user.
*

View File

@ -568,6 +568,8 @@ import type {
UsersSearchResponse,
UsersShowRequest,
UsersShowResponse,
UsersGetSecurityInfoRequest,
UsersGetSecurityInfoResponse,
UsersStatsRequest,
UsersStatsResponse,
UsersAchievementsRequest,
@ -977,6 +979,7 @@ export type Endpoints = {
'users/search-by-username-and-host': { req: UsersSearchByUsernameAndHostRequest; res: UsersSearchByUsernameAndHostResponse };
'users/search': { req: UsersSearchRequest; res: UsersSearchResponse };
'users/show': { req: UsersShowRequest; res: UsersShowResponse };
'users/get-security-info': { req: UsersGetSecurityInfoRequest; res: UsersGetSecurityInfoResponse };
'users/stats': { req: UsersStatsRequest; res: UsersStatsResponse };
'users/achievements': { req: UsersAchievementsRequest; res: UsersAchievementsResponse };
'users/update-memo': { req: UsersUpdateMemoRequest; res: EmptyResponse };

View File

@ -571,6 +571,8 @@ export type UsersSearchRequest = operations['users___search']['requestBody']['co
export type UsersSearchResponse = operations['users___search']['responses']['200']['content']['application/json'];
export type UsersShowRequest = operations['users___show']['requestBody']['content']['application/json'];
export type UsersShowResponse = operations['users___show']['responses']['200']['content']['application/json'];
export type UsersGetSecurityInfoRequest = operations['users___get-security-info']['requestBody']['content']['application/json'];
export type UsersGetSecurityInfoResponse = operations['users___get-security-info']['responses']['200']['content']['application/json'];
export type UsersStatsRequest = operations['users___stats']['requestBody']['content']['application/json'];
export type UsersStatsResponse = operations['users___stats']['responses']['200']['content']['application/json'];
export type UsersAchievementsRequest = operations['users___achievements']['requestBody']['content']['application/json'];

View File

@ -2584,6 +2584,13 @@ export type paths = {
post: operations['invite___limit'];
};
'/meta': {
/**
* meta
* @description No description provided.
*
* **Credential required**: *No*
*/
get: operations['meta_get'];
/**
* meta
* @description No description provided.
@ -3287,6 +3294,13 @@ export type paths = {
post: operations['server-info'];
};
'/stats': {
/**
* stats
* @description No description provided.
*
* **Credential required**: *No*
*/
get: operations['stats_get'];
/**
* stats
* @description No description provided.
@ -3654,6 +3668,15 @@ export type paths = {
*/
post: operations['users___show'];
};
'/users/get-security-info': {
/**
* users/get-security-info
* @description No description provided.
*
* **Credential required**: *No*
*/
post: operations['users___get-security-info'];
};
'/users/stats': {
/**
* users/stats
@ -22607,6 +22630,60 @@ export type operations = {
};
};
};
/**
* meta
* @description No description provided.
*
* **Credential required**: *No*
*/
meta_get: {
requestBody: {
content: {
'application/json': {
/** @default true */
detail?: boolean;
};
};
};
responses: {
/** @description OK (with results) */
200: {
content: {
'application/json': components['schemas']['MetaLite'] | components['schemas']['MetaDetailed'];
};
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* meta
* @description No description provided.
@ -27167,6 +27244,60 @@ export type operations = {
};
};
};
/**
* stats
* @description No description provided.
*
* **Credential required**: *No*
*/
stats_get: {
responses: {
/** @description OK (with results) */
200: {
content: {
'application/json': {
notesCount: number;
originalNotesCount: number;
usersCount: number;
originalUsersCount: number;
instances: number;
driveUsageLocal: number;
driveUsageRemote: number;
};
};
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* stats
* @description No description provided.
@ -29620,6 +29751,70 @@ export type operations = {
};
};
};
/**
* users/get-security-info
* @description No description provided.
*
* **Credential required**: *No*
*/
'users___get-security-info': {
requestBody: {
content: {
'application/json': {
email: string;
password: string;
};
};
};
responses: {
/** @description OK (with results) */
200: {
content: {
'application/json': {
twoFactorEnabled: boolean;
usePasswordLessLogin: boolean;
securityKeys: boolean;
};
};
};
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description To many requests */
429: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* users/stats
* @description Show statistics about a user.

View File

@ -64,6 +64,7 @@ export type Channels = {
params: {
withRenotes?: boolean;
withFiles?: boolean;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;
@ -75,6 +76,7 @@ export type Channels = {
withRenotes?: boolean;
withReplies?: boolean;
withFiles?: boolean;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;
@ -86,6 +88,7 @@ export type Channels = {
withRenotes?: boolean;
withReplies?: boolean;
withFiles?: boolean;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;
@ -96,6 +99,7 @@ export type Channels = {
params: {
withRenotes?: boolean;
withFiles?: boolean;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;
@ -107,6 +111,7 @@ export type Channels = {
listId: string;
withFiles?: boolean;
withRenotes?: boolean;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;
@ -125,6 +130,7 @@ export type Channels = {
roleTimeline: {
params: {
roleId: string;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;
@ -134,6 +140,7 @@ export type Channels = {
antenna: {
params: {
antennaId: string;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;
@ -143,6 +150,7 @@ export type Channels = {
channel: {
params: {
channelId: string;
idOnly?: boolean,
};
events: {
note: (payload: Note) => void;

1111
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff