diff --git a/CALCKEY.md b/CALCKEY.md index 00e1d7f73..4eb418b20 100644 --- a/CALCKEY.md +++ b/CALCKEY.md @@ -1,6 +1,6 @@ # All the changes to Calckey from stock Misskey -### Planned +## Planned - MFM button - Better Messaging UI @@ -8,36 +8,65 @@ - Like/star button - Option to publicize instance blocks - Better intro/onboarding -- Fully revamp welcome.a (non-logged in screen) -- Tabler icons instead of FontAwesome +- Fully revamp non-logged-in screen - Personal notes for all accounts -- Admin custom CSS -- Improve notifications (content is too verbose) - Non-nyaify cat mode - Timeline filters -- Mark as read from notifications widget +- "Bubble" timeline +- Filter notifications by user +- Remove NSFW/AI stuff +- [Rat mode?](https://stop.voring.me/notes/933fx97bmd) +- Improve accesibility score +
Current Misskey score is 57/100 -### Implemented +![](https://pool.jortage.com/voringme/misskey/8ff18aae-4dc6-4b08-9e05-a4c9d051a9e3.png) + +
+ +## Work in progress + +- Less cluttered notification summary +- Better timeline top bar +- Admin custom CSS + +## Implemented - Yarn 3 - Saner defaults - Star as default reaction -- Rosé Pine by default +- Rosé Pine by default (+ non-themable elements made Rosé Pine) - Better sidebar/navbar +- [Profile background as banner](https://codeberg.org/Freeplay/Misskey-Tweaks/src/branch/main/snippets/profile-background.styl) +- Mark as read from notifications widget - Better welcome screen (not logged in) - Ability to turn off "Connection lost" message -- Annoying Orange search +- Raw instance info only for moderators +- Spinner instead of "Loading..." +- SearchX instead of Google +- Spacing on group items - MOTD -- Reply limit bug fixed +- Reply limit bug fixed (somewhat) - Custom assets -- https://github.com/misskey-dev/misskey/pull/8983 -- https://github.com/misskey-dev/misskey/pull/8956 -- https://github.com/misskey-dev/misskey/pull/8954 -- https://github.com/misskey-dev/misskey/pull/8997 -- https://github.com/misskey-dev/misskey/pull/8996 -- https://github.com/misskey-dev/misskey/pull/8955 -- https://github.com/JakeMBauer/Misskey-Extras/blob/master/patches/star-is-like.patch -- https://github.com/misskey-dev/misskey/pull/8671 -- https://github.com/misskey-dev/misskey/pull/8927 -- https://github.com/misskey-dev/misskey/pull/8927 -- https://github.com/misskey-dev/misskey/pull/8549 +- [OAuth bearer token authentication](https://github.com/misskey-dev/misskey/pull/9021) +- [Styled Repair Tools](https://github.com/misskey-dev/misskey/pull/8956) +- [Option to make enter send message](https://github.com/misskey-dev/misskey/pull/8954) +- [Make showing ads optional](https://github.com/misskey-dev/misskey/pull/8996) +- [Autocomplete in messaging](https://github.com/misskey-dev/misskey/pull/8955) +- [Star is like](https://github.com/JakeMBauer/Misskey-Extras/blob/master/patches/star-is-like.patch) +- [Add additional background for acrylic popups if backdrop-filter is unsupported](https://github.com/misskey-dev/misskey/pull/8671) +- [Timeline page for non-login users](https://github.com/misskey-dev/misskey/pull/8927) +- [Add parameters to MFM rotate](https://github.com/misskey-dev/misskey/pull/8549) +- Many changes from [Foundkey](https://akkoma.dev/FoundKeyGang/Foundkey) + - 0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API + - 0ece67b04c3f0365057624c1068808276ccab981: refactor pages/auth.form.vue to composition API + - 4bc9610d8bf5af736b5e89e4782395705de45d7d: remove unnecessary joins + - 9ee609d70082f7a6dc119a5d83c0e7c5e1208676: enhance privacy of notes + - 0fec6e10477b1c1b95d9469fbaf4e249a3722f12: remove ms dependency + - 46fff77accbe8bf0fd3cc88170d67b997bf2bdc3: client uses new API for child notes depth + - c35372a20d22cddb75e93a0b407f2b652cd7faf0: pack children without detail + - aca724e0bfff3e58b4d273f3ee744e3f3aa9c39b: enable to fetch replies recursively + - 2fe64c11502fd8d89c126558cd715e095c83754e: Refactor components/page/page.textarea.vue to composition API + - 6d3181f9835955e5b79bde5484c74bd70e7f9535: Refactor components/page/page.text.vue to composition API + - b630cd7eacd695bb705e6748c87f38425ec4ed45: refactor: add NoteReactions.packMany + - 3fe351df6d4e21f7748c46adfa6ca165abd030c0: fix: catch errors from packing with detail + - 63591da33e233b2ed0ab331ae6bb3c9eff5020ae: refactor: colours in queue chart diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a6e6951..a61102996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,13 @@ You should also include the user name that made the change. ## 12.x.x (unreleased) ### Improvements +- Client: Add vi-VN language support ### Bugfixes +- Server: リモートユーザーを正しくブロックできるように修正する @xianonn - Client: 一度作ったwebhookの設定画面を開こうとするとページがフリーズする @syuilo +- Client: MiAuth認証ページが機能していない @syuilo +- Client: 一部のアプリからファイルを投稿フォームへドロップできない場合がある問題を修正 @m-hayabusa ## 12.117.1 (2022/07/19) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd475d985..d599d39cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -140,6 +140,34 @@ Misskey uses Vue(v3) as its front-end framework. - **When creating a new component, please use the Composition API (with [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html) and [ref sugar](https://github.com/vuejs/rfcs/discussions/369)) instead of the Options API.** - Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. +## nirax +niraxは、Misskeyで使用しているオリジナルのフロントエンドルーティングシステムです。 +**vue-routerから影響を多大に受けているので、まずはvue-routerについて学ぶことをお勧めします。** + +### ルート定義 +ルート定義は、以下の形式のオブジェクトの配列です。 + +``` ts +{ + name?: string; + path: string; + component: Component; + query?: Record; + loginRequired?: boolean; + hash?: string; + globalCacheKey?: string; + children?: RouteDef[]; +} +``` + +> **Warning** +> 現状、ルートは定義された順に評価されます。 +> たとえば、`/foo/:id`ルート定義の次に`/foo/bar`ルート定義がされていた場合、後者がマッチすることはありません。 + +### 複数のルーター +vue-routerとの最大の違いは、niraxは複数のルーターが存在することを許可している点です。 +これにより、アプリ内ウィンドウでブラウザとは個別にルーティングすることなどが可能になります。 + ## Notes ### How to resolve conflictions occurred at yarn.lock? diff --git a/README.md b/README.md index c2e7b20d9..e44eacf8a 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Read [this](./CALCKEY.md) for current and future differences. You need at least 🐢 NodeJS v16.10.0 (>v18.0.0 \ 0 + ) AS recursive WHERE nth_child < max_breadth + $$ + LANGUAGE SQL + `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP FUNCTION note_replies`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index cf460bf13..72e5af707 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -14,7 +14,7 @@ "test": "yarn mocha" }, "optionalDependencies": { - "@tensorflow/tfjs-node": "3.18.0" + "@tensorflow/tfjs-node": "3.19.0" }, "dependencies": { "@bull-board/api": "4.0.0", @@ -28,7 +28,6 @@ "@peertube/http-signature": "1.6.0", "@sinonjs/fake-timers": "9.1.2", "@syuilo/aiscript": "0.11.1", - "abort-controller": "3.0.0", "ajv": "8.11.0", "archiver": "5.3.1", "autobind-decorator": "2.4.0", @@ -45,13 +44,13 @@ "cli-highlight": "2.1.11", "color-convert": "2.0.1", "content-disposition": "0.5.4", - "date-fns": "2.28.0", + "date-fns": "2.29.1", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", "feed": "4.2.2", - "file-type": "17.1.2", + "file-type": "17.1.3", "fluent-ffmpeg": "2.1.2", - "got": "12.1.0", + "got": "12.2.0", "hpagent": "0.1.2", "ioredis": "4.28.5", "ip-cidr": "3.0.10", @@ -61,7 +60,7 @@ "json5": "2.2.1", "json5-loader": "4.0.1", "jsonld": "6.0.0", - "jsrsasign": "10.5.25", + "jsrsasign": "10.5.26", "koa": "2.13.4", "koa-bodyparser": "4.3.0", "koa-favicon": "2.1.0", @@ -71,14 +70,13 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", - "mfm-js": "0.23.0-canary.1", + "mfm-js": "0.23.0", "mime-types": "2.1.35", "misskey-js": "0.0.14", "mocha": "10.0.0", - "ms": "3.0.0-canary.1", "multer": "1.4.4", "nested-property": "4.0.0", - "node-fetch": "3.2.8", + "node-fetch": "3.2.9", "nodemailer": "6.7.7", "nsfwjs": "2.4.1", "oauth": "^0.9.15", @@ -91,32 +89,30 @@ "pug": "3.0.2", "punycode": "2.1.1", "pureimage": "0.3.14", - "qrcode": "1.5.0", + "qrcode": "1.5.1", "random-seed": "0.3.0", "ratelimiter": "3.4.1", "re2": "1.17.7", "redis-lock": "0.1.4", "reflect-metadata": "0.1.13", "rename": "1.0.4", - "require-all": "3.0.0", "rndstr": "1.0.0", "rss-parser": "3.12.0", "s-age": "1.1.2", - "sanitize-html": "2.7.0", + "sanitize-html": "2.7.1", "semver": "7.3.7", "sharp": "0.30.6", "speakeasy": "2.0.0", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "style-loader": "3.3.1", "summaly": "2.7.0", "syslog-pro": "1.0.0", - "systeminformation": "5.12.0", + "systeminformation": "5.12.1", "tinycolor2": "1.4.2", "tmp": "0.2.1", "ts-loader": "9.3.1", - "ts-node": "10.8.2", - "tsc-alias": "1.6.11", + "ts-node": "10.9.1", + "tsc-alias": "1.7.0", "tsconfig-paths": "4.0.0", "twemoji-parser": "14.0.0", "typeorm": "0.3.7", @@ -125,21 +121,20 @@ "uuid": "8.3.2", "web-push": "3.5.0", "websocket": "1.0.34", - "ws": "8.8.0", + "ws": "8.8.1", "xev": "3.0.2" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.100", + "@redocly/openapi-core": "1.0.0-beta.104", "@types/bcryptjs": "2.4.2", "@types/bull": "3.15.8", "@types/cbor": "6.0.0", "@types/escape-regexp": "0.0.1", "@types/fluent-ffmpeg": "2.1.20", - "@types/is-url": "1.2.30", "@types/js-yaml": "4.0.5", "@types/jsdom": "16.2.14", "@types/jsonld": "1.5.6", - "@types/jsrsasign": "10.5.1", + "@types/jsrsasign": "10.5.2", "@types/koa": "2.13.5", "@types/koa-bodyparser": "4.3.7", "@types/koa-cors": "0.0.2", @@ -152,7 +147,7 @@ "@types/koa__multer": "2.0.4", "@types/koa__router": "8.0.11", "@types/mocha": "9.1.1", - "@types/node": "18.0.3", + "@types/node": "18.6.1", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.4", "@types/oauth": "0.9.1", @@ -174,10 +169,10 @@ "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", "@types/ws": "8.5.3", - "@typescript-eslint/eslint-plugin": "5.30.6", - "@typescript-eslint/parser": "5.30.6", + "@typescript-eslint/eslint-plugin": "5.30.7", + "@typescript-eslint/parser": "5.30.7", "cross-env": "7.0.3", - "eslint": "8.19.0", + "eslint": "8.20.0", "eslint-plugin-import": "2.26.0", "execa": "6.1.0", "form-data": "^4.0.0", diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 6d3b9559e..17fdb45ec 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -1,5 +1,12 @@ export const MAX_NOTE_TEXT_LENGTH = 3000; +export const SECOND = 1000; +export const SEC = 1000; +export const MINUTE = 60 * SEC; +export const MIN = 60 * SEC; +export const HOUR = 60 * MIN; +export const DAY = 24 * HOUR; + export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts index 3f35ccee8..96267400a 100644 --- a/packages/backend/src/misc/get-note-summary.ts +++ b/packages/backend/src/misc/get-note-summary.ts @@ -9,10 +9,6 @@ export const getNoteSummary = (note: Packed<'Note'>): string => { return `(❌⛔)`; } - if (note.isHidden) { - return `(⛔)`; - } - let summary = ''; // 本文 @@ -32,6 +28,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => { summary += ` (📊)`; } + /* // 返信のとき if (note.replyId) { if (note.reply) { @@ -49,6 +46,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => { summary += '\n\nRN: ...'; } } + */ return summary.trim(); }; diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts index 9bd97f988..1d5702053 100644 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ b/packages/backend/src/models/repositories/note-favorite.ts @@ -14,6 +14,7 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ id: favorite.id, createdAt: favorite.createdAt.toISOString(), noteId: favorite.noteId, + // may throw error note: await Notes.pack(favorite.note || favorite.noteId, me), }; }, @@ -22,6 +23,7 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({ favorites: any[], me: { id: User['id'] } ) { - return Promise.all(favorites.map(x => this.pack(x, me))); + return Promise.allSettled(favorites.map(x => this.pack(x, me))) + .then(promises => promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : [])); }, }); diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts index 4deae51c9..46084a9a1 100644 --- a/packages/backend/src/models/repositories/note-reaction.ts +++ b/packages/backend/src/models/repositories/note-reaction.ts @@ -25,8 +25,22 @@ export const NoteReactionRepository = db.getRepository(NoteReaction).extend({ user: await Users.pack(reaction.user ?? reaction.userId, me), type: convertLegacyReaction(reaction.reaction), ...(opts.withNote ? { + // may throw error note: await Notes.pack(reaction.note ?? reaction.noteId, me), } : {}), }; }, + + async packMany( + src: NoteReaction[], + me?: { id: User['id'] } | null | undefined, + options?: { + withNote: booleam; + }, + ): Promise[]> { + const reactions = await Promise.allSettled(src.map(reaction => this.pack(reaction, me, options))); + + // filter out rejected promises, only keep fulfilled values + return reactions.flatMap(result => result.status === 'fulfilled' ? [result.value] : []); + } }); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 3fefab031..e697b4cea 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -10,66 +10,7 @@ import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@ import { NoteReaction } from '@/models/entities/note-reaction.js'; import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js'; import { db } from '@/db/postgre.js'; - -async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) { - // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) - let hide = false; - - // visibility が specified かつ自分が指定されていなかったら非表示 - if (packedNote.visibility === 'specified') { - if (meId == null) { - hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else { - // 指定されているかどうか - const specified = packedNote.visibleUserIds!.some((id: any) => meId === id); - - if (specified) { - hide = false; - } else { - hide = true; - } - } - } - - // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 - if (packedNote.visibility === 'followers') { - if (meId == null) { - hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else if (packedNote.reply && (meId === packedNote.reply.userId)) { - // 自分の投稿に対するリプライ - hide = false; - } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { - // 自分へのメンション - hide = false; - } else { - // フォロワーかどうか - const following = await Followings.findOneBy({ - followeeId: packedNote.userId, - followerId: meId, - }); - - if (following == null) { - hide = true; - } else { - hide = false; - } - } - } - - if (hide) { - packedNote.visibleUserIds = undefined; - packedNote.fileIds = []; - packedNote.files = []; - packedNote.text = null; - packedNote.poll = undefined; - packedNote.cw = null; - packedNote.isHidden = true; - } -} +import { IdentifiableError } from '@/misc/identifiable-error.js'; async function populatePoll(note: Note, meId: User['id'] | null) { const poll = await Polls.findOneByOrFail({ noteId: note.id }); @@ -193,7 +134,6 @@ export const NoteRepository = db.getRepository(Note).extend({ me?: { id: User['id'] } | null | undefined, options?: { detail?: boolean; - skipHide?: boolean; _hint_?: { myReactions: Map; }; @@ -201,13 +141,16 @@ export const NoteRepository = db.getRepository(Note).extend({ ): Promise> { const opts = Object.assign({ detail: true, - skipHide: false, }, options); const meId = me ? me.id : null; const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); const host = note.userHost; + if (!await this.isVisibleForMe(note, meId)) { + throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + } + let text = note.text; if (note.name && (note.url ?? note.uri)) { @@ -282,10 +225,6 @@ export const NoteRepository = db.getRepository(Note).extend({ packed.text = mfm.toString(tokens); } - if (!opts.skipHide) { - await hideNote(packed, meId); - } - return packed; }, @@ -294,7 +233,6 @@ export const NoteRepository = db.getRepository(Note).extend({ me?: { id: User['id'] } | null | undefined, options?: { detail?: boolean; - skipHide?: boolean; } ) { if (notes.length === 0) return []; @@ -316,11 +254,14 @@ export const NoteRepository = db.getRepository(Note).extend({ await prefetchEmojis(aggregateNoteEmojis(notes)); - return await Promise.all(notes.map(n => this.pack(n, me, { + const promises = await Promise.allSettled(notes.map(n => this.pack(n, me, { ...options, _hint_: { myReactions: myReactionsMap, }, }))); + + // filter out rejected promises, only keep fulfilled values + return promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : []); }, }); diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index cdf4b9a54..292bbb82f 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -52,10 +52,6 @@ export const packedNoteSchema = { optional: true, nullable: true, ref: 'Note', }, - isHidden: { - type: 'boolean', - optional: true, nullable: false, - }, visibility: { type: 'string', optional: false, nullable: false, diff --git a/packages/backend/src/remote/activitypub/renderer/block.ts b/packages/backend/src/remote/activitypub/renderer/block.ts index 13815fb76..802d7280b 100644 --- a/packages/backend/src/remote/activitypub/renderer/block.ts +++ b/packages/backend/src/remote/activitypub/renderer/block.ts @@ -7,7 +7,7 @@ import { Blocking } from '@/models/entities/blocking.js'; * @param block The block to be rendered. The blockee relation must be loaded. */ export function renderBlock(block: Blocking) { - if (block.blockee?.url == null) { + if (block.blockee?.uri == null) { throw new Error('renderBlock: missing blockee uri'); } diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts index ec71ddd2c..3cb94f10f 100644 --- a/packages/backend/src/server/api/api-handler.ts +++ b/packages/backend/src/server/api/api-handler.ts @@ -43,7 +43,8 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res }; // Authentication - authenticate(body['i']).then(([user, app]) => { + // for GET requests, do not even pass on the body parameter as it is considered unsafe + authenticate(ctx.headers.authorization, ctx.method === 'GET' ? null : body['i']).then(([user, app]) => { // API invoking call(endpoint.name, user, app, body, ctx).then((res: any) => { if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { @@ -80,11 +81,15 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res } }).catch(e => { if (e instanceof AuthenticationError) { - reply(403, new ApiError({ - message: 'Authentication failed. Please ensure your token is correct.', + ctx.response.status = 403; + ctx.response.set('WWW-Authenticate', 'Bearer'); + ctx.response.body = { + message: 'Authentication failed: ' + e.message, code: 'AUTHENTICATION_FAILED', id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', - })); + kind: 'client', + }; + res(); } else { reply(500, new ApiError()); } diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index 65ccfcf55..39be06c29 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -15,8 +15,25 @@ export class AuthenticationError extends Error { } } -export default async (token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { - if (token == null) { +export default async (authorization: string | null | undefined, bodyToken: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => { + let token: string | null = null; + + // check if there is an authorization header set + if (authorization != null) { + if (bodyToken != null) { + throw new AuthenticationError('using multiple authorization schemes'); + } + + // check if OAuth 2.0 Bearer tokens are being used + // Authorization schemes are case insensitive + if (authorization.substring(0, 7).toLowerCase() === 'bearer ') { + token = authorization.substring(7); + } else { + throw new AuthenticationError('unsupported authentication scheme'); + } + } else if (bodyToken != null) { + token = bodyToken; + } else { return [null, null]; } @@ -25,7 +42,7 @@ export default async (token: string | null): Promise<[CacheableLocalUser | null () => Users.findOneBy({ token }) as Promise); if (user == null) { - throw new AuthenticationError('user not found'); + throw new AuthenticationError('unknown token'); } return [user, null]; @@ -39,7 +56,7 @@ export default async (token: string | null): Promise<[CacheableLocalUser | null }); if (accessToken == null) { - throw new AuthenticationError('invalid signature'); + throw new AuthenticationError('unknown token'); } AccessTokens.update(accessToken.id, { diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts index 783ea9ef7..c5a1e765e 100644 --- a/packages/backend/src/server/api/common/getters.ts +++ b/packages/backend/src/server/api/common/getters.ts @@ -2,12 +2,20 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import { User } from '@/models/entities/user.js'; import { Note } from '@/models/entities/note.js'; import { Notes, Users } from '@/models/index.js'; +import { generateVisibilityQuery } from './generate-visibility-query.js'; /** - * Get note for API processing + * Get note for API processing, taking into account visibility. */ -export async function getNote(noteId: Note['id']) { - const note = await Notes.findOneBy({ id: noteId }); +export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null) { + const query = Notes.createQueryBuilder('note') + .where("note.id = :id", { + id: noteId, + }); + + generateVisibilityQuery(query, me); + + const note = await query.getOne(); if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 68a17867b..b5142fcf0 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -35,9 +35,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const exist = await PromoNotes.findOneBy({ noteId: note.id }); diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 0cbe7ebc6..559bc277f 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -1,7 +1,6 @@ import define from '../../define.js'; import Resolver from '@/remote/activitypub/resolver.js'; -import { ApiError } from '../../error.js'; -import ms from 'ms'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['federation'], @@ -9,7 +8,7 @@ export const meta = { requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 30, }, diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 6442a1412..7426daeec 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -11,8 +11,8 @@ import { Note } from '@/models/entities/note.js'; import { CacheableLocalUser, User } from '@/models/entities/user.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { isActor, isPost, getApId } from '@/remote/activitypub/type.js'; -import ms from 'ms'; import { SchemaType } from '@/misc/schema.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['federation'], @@ -20,7 +20,7 @@ export const meta = { requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 30, }, diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 0540e6ab0..4e88f32fb 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -1,15 +1,15 @@ -import ms from 'ms'; import create from '@/services/blocking/create.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getUser } from '../../common/getters.js'; import { Blockings, NoteWatchings, Users } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['account'], limit: { - duration: ms('1hour'), + duration: HOUR, max: 100, }, diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 77e17b3ba..37215bbd6 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -1,15 +1,15 @@ -import ms from 'ms'; import deleteBlocking from '@/services/blocking/delete.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getUser } from '../../common/getters.js'; import { Blockings, Users } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['account'], limit: { - duration: ms('1hour'), + duration: HOUR, max: 100, }, diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 5d72f5c1b..91baa8eb7 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -52,9 +52,9 @@ export default define(meta, paramDef, async (ps, user) => { throw new ApiError(meta.errors.noSuchClip); } - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const exist = await ClipNotes.findOneBy({ diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index ddcbd6288..a37e6cf0c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,4 +1,3 @@ -import ms from 'ms'; import { addFile } from '@/services/drive/add-file.js'; import { DriveFiles } from '@/models/index.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; @@ -7,6 +6,9 @@ import { fetchMeta } from '@/misc/fetch-meta.js'; import define from '../../../define.js'; import { apiLogger } from '../../../logger.js'; import { ApiError } from '../../../error.js'; +import { DriveFiles } from '@/models/index.js'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['drive'], @@ -14,7 +16,7 @@ export const meta = { requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 120, }, diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index eb8071c3c..88a448f21 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,15 +1,14 @@ -import ms from 'ms'; import { uploadFromUrl } from '@/services/drive/upload-from-url.js'; +import define from '../../../define.js'; import { DriveFiles } from '@/models/index.js'; import { publishMainStream } from '@/services/stream.js'; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js'; -import define from '../../../define.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['drive'], limit: { - duration: ms('1hour'), + duration: HOUR, max: 60, }, @@ -34,8 +33,8 @@ export const paramDef = { } as const; // eslint-disable-next-line import/no-default-export -export default define(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => { - uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => { +export default define(meta, paramDef, async (ps, user) => { + uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => { DriveFiles.pack(file, { self: true }).then(packedFile => { publishMainStream(user.id, 'urlUploadFinished', { marker: ps.marker, diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index 5fe622932..3dc9d4e9f 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -1,12 +1,12 @@ -import ms from 'ms'; import { createExportCustomEmojisJob } from '@/queue/index.js'; import define from '../define.js'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 1, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 02a030cd5..3a12a55b8 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -1,16 +1,16 @@ -import ms from 'ms'; import create from '@/services/following/create.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getUser } from '../../common/getters.js'; import { Followings, Users } from '@/models/index.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['following', 'users'], limit: { - duration: ms('1hour'), + duration: HOUR, max: 100, }, diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 2f41b16e9..a454f2d72 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -1,15 +1,15 @@ -import ms from 'ms'; import deleteFollowing from '@/services/following/delete.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getUser } from '../../common/getters.js'; import { Followings, Users } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['following', 'users'], limit: { - duration: ms('1hour'), + duration: HOUR, max: 100, }, diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 18ec5affe..cf3a21406 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -1,15 +1,15 @@ -import ms from 'ms'; import deleteFollowing from '@/services/following/delete.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getUser } from '../../common/getters.js'; import { Followings, Users } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['following', 'users'], limit: { - duration: ms('1hour'), + duration: HOUR, max: 100, }, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 8074a3b34..eec4d2ba1 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,10 +1,10 @@ -import ms from 'ms'; import define from '../../../define.js'; import { DriveFiles, GalleryPosts } from '@/models/index.js'; import { genId } from '../../../../../misc/gen-id.js'; import { GalleryPost } from '@/models/entities/gallery-post.js'; import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['gallery'], @@ -14,7 +14,7 @@ export const meta = { kind: 'write:gallery', limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, }, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 82fe38078..b333d947d 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -1,9 +1,9 @@ -import ms from 'ms'; import define from '../../../define.js'; import { DriveFiles, GalleryPosts } from '@/models/index.js'; import { GalleryPost } from '@/models/entities/gallery-post.js'; import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['gallery'], @@ -13,7 +13,7 @@ export const meta = { kind: 'write:gallery', limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, }, diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index aed4c2e0a..682d39552 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -1,12 +1,12 @@ import define from '../../define.js'; import { createExportBlockingJob } from '@/queue/index.js'; -import ms from 'ms'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 1, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index 058d77b3c..3d56ab7ee 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -1,12 +1,12 @@ import define from '../../define.js'; import { createExportFollowingJob } from '@/queue/index.js'; -import ms from 'ms'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 1, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index c0216fac0..b6cc1eea4 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -1,12 +1,12 @@ import define from '../../define.js'; import { createExportMuteJob } from '@/queue/index.js'; -import ms from 'ms'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 1, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index 4b85a4555..4856b84ab 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -1,12 +1,12 @@ import define from '../../define.js'; import { createExportNotesJob } from '@/queue/index.js'; -import ms from 'ms'; +import { DAY } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1day'), + duration: DAY, max: 1, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index fa5c1f5e5..1aa02707d 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -1,12 +1,12 @@ import define from '../../define.js'; import { createExportUserListsJob } from '@/queue/index.js'; -import ms from 'ms'; +import { MINUTE } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1min'), + duration: MINUTE, max: 1, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 0bcbf37dd..5e5bcba7a 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -1,15 +1,15 @@ import define from '../../define.js'; import { createImportBlockingJob } from '@/queue/index.js'; -import ms from 'ms'; import { ApiError } from '../../error.js'; import { DriveFiles } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 1, }, diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index ee2abbea1..28d9d38fa 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -1,14 +1,14 @@ import define from '../../define.js'; import { createImportFollowingJob } from '@/queue/index.js'; -import ms from 'ms'; import { ApiError } from '../../error.js'; import { DriveFiles } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duratition: HOUR, max: 1, }, diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index b3b3b3923..4165da020 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -1,15 +1,15 @@ import define from '../../define.js'; import { createImportMutingJob } from '@/queue/index.js'; -import ms from 'ms'; import { ApiError } from '../../error.js'; import { DriveFiles } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 1, }, diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 64f5ec05f..6b3949c99 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -1,14 +1,14 @@ import define from '../../define.js'; import { createImportUserListsJob } from '@/queue/index.js'; -import ms from 'ms'; import { ApiError } from '../../error.js'; import { DriveFiles } from '@/models/index.js'; +import { HOUR } from '@/const.js'; export const meta = { secure: true, requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 1, }, diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index a2249803e..2b343dabd 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -13,7 +13,7 @@ export const meta = { limit: { duration: 60000, - max: 10, + max: 15, }, kind: 'read:notifications', diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 331807852..7cfb88978 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -2,12 +2,12 @@ import { publishMainStream } from '@/services/stream.js'; import define from '../../define.js'; import rndstr from 'rndstr'; import config from '@/config/index.js'; -import ms from 'ms'; import bcrypt from 'bcryptjs'; import { Users, UserProfiles } from '@/models/index.js'; import { sendEmail } from '@/services/send-email.js'; import { ApiError } from '../../error.js'; import { validateEmailForAccount } from '@/services/validate-email-for-account.js'; +import { HOUR } from '@/const.js'; export const meta = { requireCredential: true, @@ -15,7 +15,7 @@ export const meta = { secure: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 3, }, diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index f66d75873..17d295700 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,8 +1,8 @@ import define from '../../../define.js'; -import ms from 'ms'; import { ApiError } from '../../../error.js'; import { MessagingMessages } from '@/models/index.js'; import { deleteMessage } from '@/services/messages/delete.js'; +import { SECOND, HOUR } from '@/const.js'; export const meta = { tags: ['messaging'], @@ -12,9 +12,9 @@ export const meta = { kind: 'write:messaging', limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, - minInterval: ms('1sec'), + minInterval: SECOND, }, errors: { diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index d27bbaefa..feaf94dcf 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -12,6 +12,8 @@ export const meta = { requireCredential: false, requireCredentialPrivateMode: true, + description: 'Get a list of children of a notes. Children includes replies as well as quote renotes that quote the respective post. A post will not be duplicated if it is a reply and a quote of a note in this thread. For depths larger than 1 the threading has to be computed by the client.', + res: { type: 'array', optional: false, nullable: false, @@ -27,7 +29,20 @@ export const paramDef = { type: 'object', properties: { noteId: { type: 'string', format: 'misskey:id' }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + limit: { + description: 'The maximum number of replies/quotes to show per parent note, i.e. the maximum number of children each note may have.', + type: 'integer', + minimum: 1, + maximum: 100, + default: 10, + }, + depth: { + description: 'The number of layers of replies to fetch at once. Defaults to 1 for backward compatibility.', + type: 'integer', + minimum: 1, + maximum: 100, + default: 1, + }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, }, @@ -37,28 +52,10 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(new Brackets(qb => { qb - .where('note.replyId = :noteId', { noteId: ps.noteId }) - .orWhere(new Brackets(qb => { qb - .where('note.renoteId = :noteId', { noteId: ps.noteId }) - .andWhere(new Brackets(qb => { qb - .where('note.text IS NOT NULL') - .orWhere('note.fileIds != \'{}\'') - .orWhere('note.hasPoll = TRUE'); - })); - })); - })) + .andWhere('note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))', { noteId: ps.noteId, depth: ps.depth, limit: ps.limit }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') - .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') - .leftJoinAndSelect('renote.user', 'renoteUser') - .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') - .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateVisibilityQuery(query, user); if (user) { @@ -66,7 +63,7 @@ export default define(meta, paramDef, async (ps, user) => { generateBlockedUserQuery(query, user); } - const notes = await query.take(ps.limit).getMany(); + const notes = await query.getMany(); - return await Notes.packMany(notes, user); + return await Notes.packMany(notes, user, { detail: false }); }); diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 5a4420a68..514386d73 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -39,9 +39,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, me).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const clipNotes = await ClipNotes.findBy({ diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 28613962a..fa9b58848 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -41,9 +41,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const conversation: Note[] = []; @@ -51,7 +51,11 @@ export default define(meta, paramDef, async (ps, user) => { async function get(id: any) { i++; - const p = await Notes.findOneBy({ id }); + const p = await getNote(id, user).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') return null; + throw e; + }); + if (p == null) return; if (i > ps.offset!) { diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index a13329416..82540f96b 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -1,4 +1,3 @@ -import ms from 'ms'; import { In } from 'typeorm'; import create from '@/services/note/create.js'; import { User } from '@/models/entities/user.js'; @@ -10,6 +9,8 @@ import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { noteVisibilities } from '../../../../types.js'; import { ApiError } from '../../error.js'; import define from '../../define.js'; +import { HOUR } from '@/const.js'; +import { getNote } from '../../common/getters.js'; export const meta = { tags: ['notes'], @@ -17,7 +18,7 @@ export const meta = { requireCredential: true, limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, }, @@ -83,7 +84,7 @@ export const meta = { export const paramDef = { type: 'object', properties: { - visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' }, + visibility: { type: 'string', enum: noteVisibilities, default: 'public' }, visibleUserIds: { type: 'array', uniqueItems: true, items: { type: 'string', format: 'misskey:id', } }, @@ -185,11 +186,12 @@ export default define(meta, paramDef, async (ps, user) => { let renote: Note | null = null; if (ps.renoteId != null) { // Fetch renote to note - renote = await Notes.findOneBy({ id: ps.renoteId }); + renote = await getNote(ps.renoteId, user).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchRenoteTarget); + throw e; + }); - if (renote == null) { - throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { + if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { throw new ApiError(meta.errors.cannotReRenote); } @@ -208,11 +210,12 @@ export default define(meta, paramDef, async (ps, user) => { let reply: Note | null = null; if (ps.replyId != null) { // Fetch reply - reply = await Notes.findOneBy({ id: ps.replyId }); + reply = await getNote(ps.replyId, user).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchReplyTarget); + throw e; + }); - if (reply == null) { - throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { + if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { throw new ApiError(meta.errors.cannotReplyToPureRenote); } diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index c23ceeb5b..34d23448e 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -1,9 +1,9 @@ -import ms from 'ms'; import deleteNote from '@/services/note/delete.js'; import { Users } from '@/models/index.js'; import define from '../../define.js'; import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; +import { SECOND, HOUR } from '@/const.js'; export const meta = { tags: ['notes'], @@ -13,9 +13,9 @@ export const meta = { kind: 'write:notes', limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, - minInterval: ms('1sec'), + minInterval: SECOND, }, errors: { @@ -43,9 +43,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) { diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 097371a42..b5dd88a4e 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -37,9 +37,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Get favoritee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); // if already favorited diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 82ef4fa19..3f4d39254 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -36,9 +36,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { // Get favoritee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); // if already favorited diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 45a832cbd..6dd5ddf9e 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -72,9 +72,9 @@ export default define(meta, paramDef, async (ps, user) => { const createdAt = new Date(); // Get votee - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); if (!note.hasPoll) { diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index be2846d25..00a89b3f2 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -3,6 +3,7 @@ import { NoteReactions } from '@/models/index.js'; import { NoteReaction } from '@/models/entities/note-reaction.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; +import { getNote } from '../../common/getters.js'; export const meta = { tags: ['notes', 'reactions'], @@ -47,6 +48,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { + // check note visibility + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; + }); + const query = { noteId: ps.noteId, } as FindOptionsWhere; @@ -69,5 +76,5 @@ export default define(meta, paramDef, async (ps, user) => { relations: ['user', 'user.avatar', 'user.banner', 'note'], }); - return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user))); + return await NoteReactions.packMany(reactions, user); }); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 07e52a926..b5c0c9d17 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -42,9 +42,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); await createReaction(user, note, ps.reaction).catch(e => { if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index c13cafa21..c25d88d1b 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,8 +1,8 @@ -import ms from 'ms'; import deleteReaction from '@/services/note/reaction/delete.js'; import define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; +import { SECOND, HOUR } from '@/const.js'; export const meta = { tags: ['reactions', 'notes'], @@ -12,9 +12,9 @@ export const meta = { kind: 'write:reactions', limit: { - duration: ms('1hour'), + duration: HOUR, max: 60, - minInterval: ms('3sec'), + minInterval: 3 * SECOND, }, errors: { @@ -42,9 +42,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); await deleteReaction(user, note).catch(e => { if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 4d0cd8fc6..2f662f355 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -45,9 +45,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 470791b1b..83a39a855 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -34,12 +34,16 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); return await Notes.pack(note, user, { + // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774) detail: true, + }).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); }); diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 01afa5add..67579b2a6 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,4 +1,5 @@ import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js'; +import { getNote } from '../../common/getters.js'; import define from '../../define.js'; export const meta = { @@ -36,7 +37,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await Notes.findOneByOrFail({ id: ps.noteId }); + const note = await getNote(ps.noteId, user); const [favorite, watching, threadMuting] = await Promise.all([ NoteFavorites.count({ diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index cf360526d..4154b5dc5 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -31,9 +31,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const mutedNotes = await Notes.find({ diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index ac310d0fe..cbc0e5ce5 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -29,9 +29,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); await NoteThreadMutings.delete({ diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index ba6e262d6..a01dcfa48 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -39,15 +39,11 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); - if (!(await Notes.isVisibleForMe(note, user ? user.id : null))) { - return 204; // TODO: 良い感じのエラー返す - } - if (note.text == null) { return 204; } diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 3fba0efe0..1089a9e37 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -1,9 +1,9 @@ -import ms from 'ms'; import deleteNote from '@/services/note/delete.js'; import { Notes, Users } from '@/models/index.js'; import define from '../../define.js'; import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; +import { SECOND, HOUR } from '@/const.js'; export const meta = { tags: ['notes'], @@ -13,9 +13,9 @@ export const meta = { kind: 'write:notes', limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, - minInterval: ms('1sec'), + minInterval: SECOND, }, errors: { @@ -37,9 +37,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const renotes = await Notes.findBy({ diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index 7d482b073..6025799fa 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -29,9 +29,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); await watch(user.id, note); diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index 2c1a2e5fb..7021c7970 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -29,9 +29,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); await unwatch(user.id, note); diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index b008cde84..e7b78fb38 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,9 +1,9 @@ -import ms from 'ms'; import { Pages, DriveFiles } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { Page } from '@/models/entities/page.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['pages'], @@ -13,7 +13,7 @@ export const meta = { kind: 'write:pages', limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, }, diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index d241f585a..8230ea09b 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,8 +1,8 @@ -import ms from 'ms'; import { Not } from 'typeorm'; import { Pages, DriveFiles } from '@/models/index.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['pages'], @@ -12,7 +12,7 @@ export const meta = { kind: 'write:pages', limit: { - duration: ms('1hour'), + duration: HOUR, max: 300, }, diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index c6a940c65..7c37fcbf7 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -28,9 +28,9 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { - if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); - throw e; + const note = await getNote(ps.noteId, user).catch(err => { + if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw err; }); const exist = await PromoReads.findOneBy({ diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 511a6bbb5..ddf193903 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,5 +1,4 @@ import rndstr from 'rndstr'; -import ms from 'ms'; import { IsNull } from 'typeorm'; import { publishMainStream } from '@/services/stream.js'; import config from '@/config/index.js'; @@ -8,6 +7,7 @@ import { sendEmail } from '@/services/send-email.js'; import { genId } from '@/misc/gen-id.js'; import { ApiError } from '../error.js'; import define from '../define.js'; +import { HOUR } from '@/const.js'; export const meta = { tags: ['reset password'], @@ -17,7 +17,7 @@ export const meta = { description: 'Request a users password to be reset.', limit: { - duration: ms('1hour'), + duration: HOUR, max: 3, }, diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 79cf58a41..144326958 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -63,5 +63,5 @@ export default define(meta, paramDef, async (ps, me) => { .take(ps.limit) .getMany(); - return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true }))); + return await NoteReactions.packMany(reactions, me, { withNote: true }); }); diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index e7654e171..d4dc524d9 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,8 +1,8 @@ -import ms from 'ms'; import { Users, Followings } from '@/models/index.js'; import define from '../../define.js'; import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query.js'; import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../common/generate-block-query.js'; +import { DAY } from '@/const.js'; export const meta = { tags: ['users'], @@ -39,7 +39,7 @@ export default define(meta, paramDef, async (ps, me) => { .where('user.isLocked = FALSE') .andWhere('user.isExplorable = TRUE') .andWhere('user.host IS NULL') - .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) + .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - (7 * DAY)) }) .andWhere('user.id != :meId', { meId: me.id }) .orderBy('user.followersCount', 'DESC'); diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 68fa81404..86f2f4228 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -33,6 +33,11 @@ export function genOpenapiSpec() { in: 'body', name: 'i', }, + // TODO: change this to oauth2 when the remaining oauth stuff is set up + Bearer: { + type: 'http', + scheme: 'bearer', + } }, }, }; @@ -71,6 +76,19 @@ export function genOpenapiSpec() { schema.required.push('file'); } + const security = [ + { + ApiKeyAuth: [], + }, + { + Bearer: [], + }, + ]; + if (!endpoint.meta.requireCredential) { + // add this to make authentication optional + security.push({}); + } + const info = { operationId: endpoint.name, summary: endpoint.name, @@ -79,14 +97,8 @@ export function genOpenapiSpec() { description: 'Source code', url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`, }, - ...(endpoint.meta.tags ? { - tags: [endpoint.meta.tags[0]], - } : {}), - ...(endpoint.meta.requireCredential ? { - security: [{ - ApiKeyAuth: [], - }], - } : {}), + tags: endpoint.meta.tags || undefined, + security, requestBody: { required: true, content: { @@ -181,9 +193,16 @@ export function genOpenapiSpec() { }, }; - spec.paths['/' + endpoint.name] = { + const path = { post: info, }; + if (endpoint.meta.allowGet) { + path.get = { ...info }; + // API Key authentication is not permitted for GET requests + path.get.security = path.get.security.filter(elem => !Object.prototype.hasOwnProperty.call(elem, 'ApiKeyAuth')); + } + + spec.paths['/' + endpoint.name] = path; } return spec; diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index d2cc5122d..c9cffd2d3 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -1,4 +1,8 @@ import Connection from '.'; +import { Note } from '@/models/entities/note.js'; +import { Notes } from '@/models/index.js'; +import { Packed } from '@/misc/schema.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; /** * Stream channel @@ -54,6 +58,32 @@ export default abstract class Channel { }); } + protected withPackedNote(callback: (note: Packed<'Note'>) => void): (Note) => void { + return async (note: Note) => { + try { + // because `note` was previously JSON.stringify'ed, the fields that + // were objects before are now strings and have to be restored or + // removed from the object + note.createdAt = new Date(note.createdAt); + delete note.reply; + delete note.renote; + delete note.user; + delete note.channel; + + const packed = await Notes.pack(note, this.user, { detail: true }); + + callback(packed); + } catch (err) { + if (err instanceof IdentifiableError && err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') { + // skip: note not visible to user + return; + } else { + throw err; + } + } + }; + } + public abstract init(params: any): void; public dispose?(): void; public onMessage?(type: string, body: any): void; diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index d28320d92..a9a98e904 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -2,6 +2,7 @@ import Channel from '../channel.js'; import { Notes } from '@/models/index.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { StreamMessages } from '../types.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export default class extends Channel { public readonly chName = 'antenna'; @@ -23,16 +24,25 @@ export default class extends Channel { private async onEvent(data: StreamMessages['antenna']['payload']) { if (data.type === 'note') { - const note = await Notes.pack(data.body.id, this.user, { detail: true }); + try { + const note = await Notes.pack(data.body.id, this.user, { detail: true }); - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.muting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.blocking)) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (isUserRelated(note, this.muting)) return; + // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する + if (isUserRelated(note, this.blocking)) return; - this.connection.cacheNote(note); + this.connection.cacheNote(note); - this.send('note', note); + this.send('note', note); + } catch (e) { + if (e instanceof IdentifiableError && e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') { + // skip: note not visible to user + return; + } else { + throw e; + } + } } else { this.send(data.type, data.body); } diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 3cdd89a8b..7ed47c389 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -1,5 +1,5 @@ import Channel from '../channel.js'; -import { Notes, Users } from '@/models/index.js'; +import { Users } from '@/models/index.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { User } from '@/models/entities/user.js'; import { StreamMessages } from '../types.js'; @@ -31,19 +31,6 @@ export default class extends Channel { private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.muting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 5b4ae850e..391851ecd 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -1,6 +1,5 @@ import Channel from '../channel.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import { isUserRelated } from '@/misc/is-user-related.js'; @@ -13,7 +12,7 @@ export default class extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onNote = this.onNote.bind(this); + this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { @@ -30,19 +29,6 @@ export default class extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 741db447e..f9f7ae410 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -1,5 +1,4 @@ import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { Packed } from '@/misc/schema.js'; @@ -12,7 +11,7 @@ export default class extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onNote = this.onNote.bind(this); + this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { @@ -29,13 +28,6 @@ export default class extends Channel { const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag)))); if (!matched) return; - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.muting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 075a242ef..9f5188547 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -1,5 +1,4 @@ import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; @@ -12,7 +11,7 @@ export default class extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onNote = this.onNote.bind(this); + this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { @@ -31,29 +30,6 @@ export default class extends Channel { // Ignore notes from instances the user has muted if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user!, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user!, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user!, { - detail: true, - }); - } - } - // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index f5dedf77c..e73136b8e 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -1,6 +1,5 @@ import Channel from '../channel.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { isInstanceMuted } from '@/misc/is-instance-muted.js'; @@ -13,7 +12,7 @@ export default class extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onNote = this.onNote.bind(this); + this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { @@ -36,29 +35,6 @@ export default class extends Channel { (note.channelId != null && this.followingChannels.has(note.channelId)) )) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user!, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user!, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user!, { - detail: true, - }); - } - } - // Ignore notes from instances the user has muted if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index f01f47723..729de6d4a 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -1,6 +1,5 @@ import Channel from '../channel.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; -import { Notes } from '@/models/index.js'; import { checkWordMute } from '@/misc/check-word-mute.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { Packed } from '@/misc/schema.js'; @@ -12,7 +11,7 @@ export default class extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); - this.onNote = this.onNote.bind(this); + this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { @@ -30,19 +29,6 @@ export default class extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null && !this.followingChannels.has(note.channelId)) return; - // リプライなら再pack - if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { - detail: true, - }); - } - // 関係ない返信は除外 if (note.reply && !this.user!.showTimelineReplies) { const reply = note.reply; diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts index 9cfea0bfc..7f42263db 100644 --- a/packages/backend/src/server/api/stream/channels/main.ts +++ b/packages/backend/src/server/api/stream/channels/main.ts @@ -1,5 +1,4 @@ import Channel from '../channel.js'; -import { Notes } from '@/models/index.js'; import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js'; export default class extends Channel { @@ -16,26 +15,12 @@ export default class extends Channel { if (isUserFromMutedInstance(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return; if (data.body.userId && this.muting.has(data.body.userId)) return; - if (data.body.note && data.body.note.isHidden) { - const note = await Notes.pack(data.body.note.id, this.user, { - detail: true, - }); - this.connection.cacheNote(note); - data.body.note = note; - } break; } case 'mention': { if (isInstanceMuted(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return; if (this.muting.has(data.body.userId)) return; - if (data.body.isHidden) { - const note = await Notes.pack(data.body.id, this.user, { - detail: true, - }); - this.connection.cacheNote(note); - data.body = note; - } break; } } diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 97ad2983c..9b2476148 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -1,5 +1,5 @@ import Channel from '../channel.js'; -import { Notes, UserListJoinings, UserLists } from '@/models/index.js'; +import { UserListJoinings, UserLists } from '@/models/index.js'; import { User } from '@/models/entities/user.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { Packed } from '@/misc/schema.js'; @@ -15,7 +15,7 @@ export default class extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); this.updateListUsers = this.updateListUsers.bind(this); - this.onNote = this.onNote.bind(this); + this.onNote = this.withPackedNote(this.onNote.bind(this)); } public async init(params: any) { @@ -51,29 +51,6 @@ export default class extends Channel { private async onNote(note: Packed<'Note'>) { if (!this.listUsers.includes(note.userId)) return; - if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user, { - detail: true, - }); - - if (note.isHidden) { - return; - } - } else { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { - detail: true, - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { - detail: true, - }); - } - } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.muting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 3b0a75d79..8050d8a1d 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -243,7 +243,7 @@ export type StreamMessages = { }; notes: { name: 'notesStream'; - payload: Packed<'Note'>; + payload: Note; }; }; diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index f8e42d27f..cfe209d09 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -17,10 +17,14 @@ export const initializeStreamingServer = (server: http.Server) => { ws.on('request', async (request) => { const q = request.resourceURL.query as ParsedUrlQuery; - // TODO: トークンが間違ってるなどしてauthenticateに失敗したら - // コネクション切断するなりエラーメッセージ返すなりする - // (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので) - const [user, app] = await authenticate(q.i as string); + const [user, app] = await authenticate(request.httpRequest.headers.authorization, q.i) + .catch(err => { + request.reject(403, err.message); + return []; + }); + if (typeof user === 'undefined') { + return; + } if (user?.isSuspended) { request.reject(400); diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index ec5616550..1a7a20d86 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -78,7 +78,7 @@ const nodeinfo2 = async () => { enableEmail: meta.enableEmail, enableServiceWorker: meta.enableServiceWorker, proxyAccountName: proxyAccount ? proxyAccount.username : null, - themeColor: meta.themeColor || '#86b300', + themeColor: meta.themeColor || '#31748f', }, }; }; diff --git a/packages/backend/src/server/web/bios.css b/packages/backend/src/server/web/bios.css index 318fc3e28..24d294560 100644 --- a/packages/backend/src/server/web/bios.css +++ b/packages/backend/src/server/web/bios.css @@ -1,11 +1,11 @@ main > .tabs { padding: 16px; - border-bottom: 4px solid #c3c3c3; + border-bottom: 4px solid #908caa; } #lsEditor > .adder { margin: 16px; padding: 16px; - border: 2px solid #c3c3c3; + border: 2px solid #908caa; } #lsEditor > .adder > textarea { display: block; @@ -15,7 +15,7 @@ main > .tabs { } #lsEditor > .record { padding: 16px; - border-bottom: 1px solid #c3c3c3; + border-bottom: 1px solid #908caa; } #lsEditor > .record > header { font-weight: 700; @@ -28,15 +28,15 @@ main > .tabs { } html { - background: #222; + background: #191724; } main { - background: #333; + background: #1f1d2e; border-radius: 10px; } #tl > div { padding: 16px; - border-bottom: 1px solid #c3c3c3; + border-bottom: 1px solid #908caa; } #tl > div > header { font-weight: 700; @@ -50,8 +50,8 @@ main { } body, html { - background-color: #222; - color: #dfddcc; + background-color: #191724; + color: #e0def4; justify-content: center; margin: auto; padding: 10px; @@ -63,9 +63,9 @@ button { border: none; cursor: pointer; margin-bottom: 12px; - background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0)); + background: linear-gradient(90deg, rgb(156, 207, 216), rgb(49, 116, 143)); line-height: 50px; - color: #222; + color: #191724; font-weight: bold; font-size: 20px; padding: 12px; @@ -80,29 +80,28 @@ button { button { background: #444; line-height: 40px; - color: rgb(153, 204, 0); + color: rgb(156, 207, 216); font-size: 16px; padding: 0 20px; margin-right: 5px; margin-left: 5px; - } button:hover { background: #555; } #ls { - background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0)); + background: linear-gradient(90deg, rgb(156, 207, 216), rgb(49, 116, 143)); line-height: 30px; - color: #222; + color: #191724; font-weight: bold; font-size: 18px; padding: 12px; } #ls:hover { - background: rgb(153, 204, 0); + background: rgb(156, 207, 216); } a { - color: rgb(134, 179, 0); + color: rgb(156, 207, 216); text-decoration: none; } p, @@ -120,7 +119,7 @@ textarea { background-color: #444; border: solid #aaa; border-radius: 10px; - color: #dfddcc; + color: #e0def4; margin-top: 1rem; margin-bottom: 1rem; width: 20rem; @@ -135,7 +134,7 @@ input { background-color: #666; border: solid #aaa; border-radius: 10px; - color: #dfddcc; + color: #e0def4; margin-top: 1rem; margin-bottom: 1rem; width: 10rem; diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 957011542..aeb13793e 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -137,6 +137,7 @@ Refresh

Don't worry, it's (probably) not your fault.

+

Please make sure your browser is up-to-date and any AdBlockers are off.

If the problem persists after refreshing, please contact your instance's administrator.
You may also try the following options:

+ @@ -14,6 +14,7 @@ import { } from 'vue'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import * as os from '@/os'; +import { i18n } from '@/i18n'; const props = withDefaults(defineProps<{ copy?: string | null; diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index 6228d420c..ce6d15cd1 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -14,7 +14,7 @@
- +