Merge pull request #74 from kokonect-link/develop

Release: 13.13.2-cp-4.1.0
This commit is contained in:
NoriDev 2023-06-20 15:30:30 +09:00 committed by GitHub
commit 28f0522186
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
384 changed files with 5873 additions and 1902 deletions

View file

@ -147,6 +147,12 @@ id: 'aid'
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Cloud Logging
#cloudLogging:
# projectId: example-project-id
# saKeyPath: /path/to/service-account-key.json
# logName: cherrypick
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128

View file

@ -151,6 +151,12 @@ id: 'aid'
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Cloud Logging
#cloudLogging:
# projectId: example-project-id
# saKeyPath: /path/to/service-account-key.json
# logName: cherrypick
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128

View file

@ -147,6 +147,12 @@ id: 'aid'
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Cloud Logging
#cloudLogging:
# projectId: example-project-id
# saKeyPath: /path/to/service-account-key.json
# logName: cherrypick
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128

View file

@ -35,7 +35,7 @@ services:
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: misskey
POSTGRES_DB: cherrypick
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:

View file

@ -46,8 +46,10 @@ Please include errors from the developer console and/or server log files if you
<!-- Example: Chrome 113.0.5672.126 -->
* Server URL:
<!-- Example: kokonect.link -->
* CherryPick:
13.x.x-cp-4.x.x
### 🛰 Backend (for instance admin)
### 🛰 Backend (for server admin)
<!-- If you are using a managed service, put that after the version. -->
* Installation Method or Hosting Service: <!-- Example: docker compose, k8s/docker, systemd, "CherryPick install shell script", development environment -->

10
.github/labeler.yml vendored
View file

@ -13,9 +13,9 @@
'packages/sw':
- packages/sw/**/*
'packages/misskey-js':
- packages/misskey-js/**/*
'packages/cherrypick-js':
- packages/cherrypick-js/**/*
'packages/misskey-js:test':
- packages/misskey-js/test/**/*
- packages/misskey-js/test-d/**/*
'packages/cherrypick-js:test':
- packages/cherrypick-js/test/**/*
- packages/cherrypick-js/test-d/**/*

View file

@ -1,4 +1,4 @@
name: API report (misskey.js)
name: API report (cherrypick.js)
on: [push, pull_request]
@ -23,14 +23,14 @@ jobs:
run: pnpm i --frozen-lockfile
- name: Build
run: pnpm --filter misskey-js build
run: pnpm --filter cherrypick-js build
- name: Check files
run: ls packages/misskey-js/built
run: ls packages/cherrypick-js/built
- name: API report
run: pnpm --filter misskey-js api-prod
run: pnpm --filter cherrypick-js api-prod
- name: Show report
if: always()
run: cat packages/misskey-js/temp/misskey-js.api.md
run: cat packages/cherrypick-js/temp/cherrypick-js.api.md

View file

@ -36,7 +36,7 @@ jobs:
- backend
- frontend
- sw
- misskey-js
- cherrypick-js
steps:
- uses: actions/checkout@v3.3.0
with:
@ -62,7 +62,7 @@ jobs:
matrix:
workspace:
- backend
- misskey-js
- cherrypick-js
steps:
- uses: actions/checkout@v3.3.0
with:

View file

@ -46,8 +46,8 @@ jobs:
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Build misskey-js
run: pnpm --filter misskey-js build
- name: Build cherrypick-js
run: pnpm --filter cherrypick-js build
- name: Build storybook
run: pnpm --filter frontend build-storybook
- name: Publish to Chromatic

View file

@ -1,7 +1,7 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Test (misskey.js)
name: Test (cherrypick.js)
on:
push:
@ -38,10 +38,10 @@ jobs:
run: git diff --exit-code pnpm-lock.yaml
- name: Build
run: pnpm --filter misskey-js build
run: pnpm --filter cherrypick-js build
- name: Test
run: pnpm --filter misskey-js test
run: pnpm --filter cherrypick-js test
env:
CI: true
@ -49,4 +49,4 @@ jobs:
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./packages/misskey-js/coverage/coverage-final.json
files: ./packages/cherrypick-js/coverage/coverage-final.json

View file

@ -12,6 +12,24 @@
-->
## 13.x.x (unreleased)
### Client
- Fix: サーバーメトリクスが90度傾いている
## 13.13.2
### General
- エラー時や項目が存在しないときなどのアイコン画像をサーバー管理者が設定できるように
- ロールが付与されているユーザーリストを非公開にできるように
- サーバーの負荷が非常に高いため、ユーザー統計表示機能を削除しました
### Client
- Fix: タブがバックグラウンドでもstreamが切断されないように
### Server
- Fix: キャッシュが溜まり続けないように
## 13.13.1
### Client
@ -96,11 +114,12 @@ Meilisearchの設定に`index`が必要になりました。値はMisskeyサー
## 13.12.0
### NOTE
- Node.js 18.6.0以上が必要になりました
- Node.js 18.16.0以上が必要になりました
### General
- アカウントの引っ越し(フォロワー引き継ぎ)に対応
- Meilisearchを全文検索に使用できるようになりました
* 「フォロワーのみ」の投稿は検索結果に表示されません。
- 新規登録前に簡潔なルールをユーザーに表示できる、サーバールール機能を追加
- ユーザーへの自分用メモ機能
* ユーザーに対して、自分だけが見られるメモを追加できるようになりました。

View file

@ -1,14 +1,15 @@
<!--
## 13.x.x-cp-4.x.x
출시일: unreleased
출시일: unreleased<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md#13xx](CHANGELOG.md#13xx) 문서를 참고하십시오.
## NOTE
### General
-
-
### Client
-
-
### Server
-
@ -19,16 +20,99 @@
# 릴리즈 노트
이 문서는 CherryPick의 변경 사항만 포함합니다.<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md](CHANGELOG.md) 문서를 참고하십시오.
이 문서는 CherryPick의 변경 사항만 포함합니다.
## 13.13.1-cp-4.0.0
출시일: 2023/06/06
## 13.13.2-cp-4.1.0
출시일: 2023/06/20<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md#13132](CHANGELOG.md#13132) 문서를 참고하십시오.
### General
- 채팅 및 그룹 기능 유지 (revert: [misskey-dev/misskey#9919](https://github.com/misskey-dev/misskey/pull/9919), [misskey-dev/misskey#9942](https://github.com/misskey-dev/misskey/pull/9942))
- 노트 수식 삽입 기능 복원 (MathML 호환을 위해 기존에 제거된 KaTex를 Temml로 대체 ([misskey-dev/misskey#9754](https://github.com/misskey-dev/misskey/issues/9754)))
- Cloud Translation - Advanced(v3) 지원 추가 ([@mk-castella](https://github.com/libnare/mk-castella/commit/3c582dd850d00f5b8faea027fd054118efb97856))
- 타임라인에 노트의 답글을 표시하는 옵션의 기본값을 켜짐으로 설정
- 네비게이션 바의 배치를 수정
- 프로필 아이콘의 기본값을 사각형으로 설정
- 미디어 타임라인 추가 ([kiyo4act/misskey.designc01be0d](https://github.com/kiyo4act/misskey.design/commit/c01be0dc7674cdf0bcac6081c63baab52c4c9abe))
- ruby 표기 지원 ([yuriha-chan/misskey@0a109d4](https://github.com/yuriha-chan/misskey/commit/0a109d4f7442f8eedc48693b09ad2fd0b61e67a5), [yuriha-chan/misskey@446f0c2](https://github.com/yuriha-chan/misskey/commit/446f0c2ea5cc9a9f7f48a75935bce550bd0b3095), [yuriha-chan/misskey@4cfd28a](https://github.com/yuriha-chan/misskey/commit/4cfd28a452be0cdcc1328d77ab0db6dca627ca23), [yuriha-chan/misskey@dc6a6dc](https://github.com/yuriha-chan/misskey/commit/dc6a6dcdc3c2ac5b836570defb14ef4d441725e8))
- 노트 검색을 전체/로컬/리모트로 나누도록 변경 ([kiyo4act/misskey.design@4adad07](https://github.com/kiyo4act/misskey.design/commit/4adad0768ce02bd49207a94678cf3c9130ed9e10))
- 노트/유저 검색 페이지에서 Enter 키를 누르면 검색하도록
- 프로필 번역 기능 추가
- 네비게이션 메뉴에 배너 표시 옵션 추가
- 노트에서 프로필 아이콘을 숨기는 옵션 추가
- 닉네임 기능 ([shrimpia/misskey@126f145](https://github.com/shrimpia/misskey/commit/126f145560caa0cc34fe8d2c9ee22f3be922ea10), [shrimpia/misskey@58f70be](https://github.com/shrimpia/misskey/commit/58f70beb9aff2287a64d903b43583184340294aa))
- 유저 페이지에서 사용자의 이름을 클릭 또는 탭하여 원하는 이름으로 변경할 수 있습니다.
- 「노트 액션 버튼을 마우스를 올렸을 때에만 표시」 옵션을 켰을 때, 자세히 버튼을 표시하도록 변경 ([shrimpia/misskey@4802191](https://github.com/shrimpia/misskey/commit/48021913bb9b6b2a314e8d88e3816f6f66a52888))
- 「이미 팔로우한 경우 알림 필드에 팔로우 버튼을 표시하지 않음」을 선택 사항으로 설정 ([shrimpia/misskey@9345149](https://github.com/shrimpia/misskey/commit/9345149f5d0447058a6ed1524708925a84744bd7))
- 노트 작성 폼에서 본문 미리보기 상태 기억 ([shrimpia/misskey](https://github.com/shrimpia/misskey))
- 리모트에 존재하는 커스텀 이모지도 자신의 서버 내에 같은 이름의 이모지가 있으면 리액션 할 수 있도록 ([shrimpia/misskey@e91295f](https://github.com/shrimpia/misskey/commit/e91295ff9c6f8ac90f61c8de7a891a6836e48e95), [shrimpia/misskey@010378f](https://github.com/shrimpia/misskey/commit/010378fae659ad3015bfade4346209e01bb2a902), [shrimpia/misskey@acf2a30](https://github.com/shrimpia/misskey/commit/acf2a30e8a8c57525dfbab499dbb0b6c7d8e43c2))
- 「이미 본 리노트를 간략화하기」 옵션의 기본값을 꺼짐으로 설정
- 이벤트 기능 (misskey-dev/misskey#10628)
- Play에 API Token을 요청할 수 있는 기능 추가 (misskey-dev/misskey#10949)
- 절대 시간 표기 (sakura-tel/milkey#69)
- 초대 코드 해지 기능 ([atsu1125/groundpolis@2da90e7](https://github.com/atsu1125/groundpolis/commit/2da90e7241d2f88390a7713e76a86f26e158248e))
### Client
- (Friendly) 일부 페이지를 제외하고 플로팅 버튼을 표시하지 않음
- 모바일에서 UI 흐림 효과를 비활성화 했을 때 가독성 향상
- 토스트 알림의 배경이 불투명하게 표시되도록
- 헤더의 배경이 불투명하게 표시되도록
- MFM 도움말에 검색 섹션 추가
- 노트 디자인 개선
- 채팅을 읽지 않았을 떄 표시되는 인디케이터의 위치 조정
- 이미 읽은 채팅은 가독성 개선을 위해 배경을 연하게
- 답글 노트의 디자인 개선
- 업데이트 팝업의 버튼 디자인 변경
- 팔로우/팔로워를 비공개로 하고 있는 경우 표시는 '0'이 아닌 키 아이콘을 표시하도록 (misskey-dev/misskey#10934)
- 신고의 초기 댓글에 사용자 ID 추가 ([kiyo4act/misskey.design@fded63c](https://github.com/kiyo4act/misskey.design/commit/fded63c7317721daeb8babcdf901dc00ab475231), [kiyo4act/misskey.design@8b6e303](https://github.com/kiyo4act/misskey.design/commit/8b6e303f184888193f4ce1daaa1629fedb46c7a9))
- OGP 미리보기 추가 ([kiyo4act/misskey.design@4eb0a6d](https://github.com/kiyo4act/misskey.design/commit/4eb0a6d8467c0c601e6fe37b0170c6c36f4bc8f2))
- 더 보기! 메뉴에 도움말 추가
- 노트를 자세히 볼 때 역할 배지를 표시하도록
- 일부 제어판 페이지의 헤더 개선
- 스크롤이 최상단일 때 헤더를 누르면 새로고침 메뉴를 표시하도록
- MkImgWithBlurhash에서 blurhash 그리기에 사용하는 canvas는 재사용하도록(misskey-dev/misskey#10966)
- CherryPick 고유 기능 및 개선된 기능은 「CherryPick」 배지 추가
- 네비게이션 메뉴 편집 페이지 UI 개선 ([shrimpia/misskey@bf8c84d](https://github.com/shrimpia/misskey/commit/bf8c84d299bd06cb21e18a9fe68ff9abc11fd4a0))
- 「노트 본문에 번역 버튼 표시」를 선택 사항으로 설정
- 답글도 번역할 수 있도록 개선
- 리노트 했을 때 뜨는 토스트 알림에 아이콘 추가
- 노트의 서버 정보 옵션의 순서를 변경
- 글로벌 타임라인의 아이콘 변경
- 노트 헤더에 리액션 수신 상태 표시
- 노트를 게시했을 때 토스트 알림 표시
- 노트 액션 버튼 추가 및 편의성 향상
- 데이터 세이버를 활성화하면 설정을 반영하기 위해 페이지를 새로 고치도록
- 그룹 페이지의 전반적인 디자인 개선
- 파일 및 투표의 details 디자인 개선
- Fix: (Friendly) 플로팅 메뉴를 길게 눌렀을 때 프로필 이미지를 드래그 할 수 있는 문제
- Fix: (Friendly) 타임라인이 변경되었을 때 네비게이션 바의 인디케이터가 사라지지 않는 문제
- Fix: (Friendly) 모바일에서 헤더가 사라졌을 때 프로필 아이콘의 높이가 잘못 설정되는 문제
- Fix: (Friendly) 화면이 넓은 일부 모바일 기기에서 프로필 아이콘의 높이가 잘못 설정되는 문제
- Fix: (Friendly) 스크롤 계산 방식 변경
- Fix: (Friendly) 모바일에서 네비게이션 메뉴의 인디케이터 크기를 작게 조정
- Fix: (Friendly) 유저 이름이 너무 긴 경우 디자인에 문제가 발생할 수 있음
- Fix: 네트워크 트래픽이 10MB/s를 초과하면 네트워크 통계 위젯의 그래프가 잘못 출력되는 문제
- Fix: iOS 기기에서 채팅 입력란이 채팅을 가리는 문제
- Fix: 특정 MFM 구문이 포함된 노트 간략화 옵션 비활성화 시, MFM 구문에 대해서는 더 보기 버튼을 표시하지 않도록
- Fix: 프로필 아이콘을 사각형으로 설정했을 때 유저 팝업의 디자인 개선
- Fix: 노트 헤더에서 유저 이름이 너무 긴 경우 디자인에 문제가 발생할 수 있음
- Fix: 그룹 페이지에서 기능이 제대로 작동하지 않음
- Fix: 채팅 내용이 길이가 너무 길어도 그만큼 공간을 차지하는 문제
### Server
- mfm-js를 cherrypick-mfm-js로 변경
- misskey-js를 cherrypick-js로 변경
- Add: Cloud Logging 기능 ([libnare/mk-castella@0a883ef](https://github.com/libnare/mk-castella/commit/0a883efe08921d5e5b7914a1075c3931122a623b), [libnare/mk-castella@7987d57](https://github.com/libnare/mk-castella/commit/7987d57789316084b38665e539e41a463ddaa110))
- Fix: 알림에서 UserGroupInvitation 관련 문제 해결 (kokonect-link/cherrypick#75)
- Fix: 정의되지 않은 OffscreenCanvas (misskey-dev/misskey#11017)
---
## 13.13.1-cp-4.0.0
출시일: 2023/06/06<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md#13131](CHANGELOG.md#13131) 문서를 참고하십시오.
### General
- 채팅 및 그룹 기능 유지 (revert: misskey-dev/misskey#9919, misskey-dev/misskey#9942)
- 노트 수식 삽입 기능 복원 (MathML 호환을 위해 기존에 제거된 KaTex를 Temml로 대체 (misskey-dev/misskey#9754))
- Cloud Translation - Advanced(v3) 지원 추가 ([libnare/mk-castella@3c582dd](https://github.com/libnare/mk-castella/commit/3c582dd850d00f5b8faea027fd054118efb97856))
- mfm-cheat-sheet 복원
### Client
@ -39,7 +123,7 @@
- (Friendly) 데스크톱 모드에서 타임라인 옆에 알림 영역을 배치하도록
- (Friendly) 오조작 방지를 위해 플로팅 버튼을 길게 눌러 새로고침 기능 제거
- (Friendly) 채팅 탭에 플로팅 버튼 추가
- 클라이언트에서 사용되는 폰트의 확장자를 ttf에서 woff2 및 woff로 변경 ([@mk-castella](https://github.com/libnare/mk-castella/commit/f439b3e007618c02da7a352016b3d0f397311f54))
- 클라이언트에서 사용되는 폰트의 확장자를 ttf에서 woff2 및 woff로 변경 ([libnare/mk-castella@f439b3e](https://github.com/libnare/mk-castella/commit/f439b3e007618c02da7a352016b3d0f397311f54))
- about-misskey 페이지에 CherryPick 관련 정보 추가 및 Misskey 문단 구분명 추가
- 계정 초기 설정 마법사 개선
- 뒤로 가기 버튼 추가
@ -62,7 +146,7 @@
- 알림 기본 정렬을 수직으로 변경
- 채팅방 목록 페이지 디자인 개선
- 리노트 문구를 노트 최상단으로 배치
- 특정 MFM 구문이 포함된 노트를 간략화 할지 선택할 수 있음(enhance: [aba0755](https://github.com/kokonect-link/cherrypick/commit/aba0755880d6797f49d34c8b7fe2c602d153e367))
- 특정 MFM 구문이 포함된 노트를 간략화 할지 선택할 수 있음(enhance: [@aba0755](https://github.com/kokonect-link/cherrypick/commit/aba0755880d6797f49d34c8b7fe2c602d153e367))
- 노트 사이를 띄우는 옵션 활성화 시 알림 페이지의 노트도 띄우도록
- 안테나, 그룹, 리스트, 클립 페이지의 생성 버튼을 헤더로 이동
- 채팅 디자인 일부 개선
@ -83,7 +167,8 @@
---
## 13.5.6-cp-3.1.0
출시일: 2023/02/10
출시일: 2023/02/10<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md](CHANGELOG.md) 문서를 참고하십시오.
## NOTE
변경 사항이 없습니다.
@ -91,7 +176,8 @@
---
## 13.5.5-cp-3.1.0
출시일: 2023/02/10
출시일: 2023/02/10<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md](CHANGELOG.md) 문서를 참고하십시오.
## NOTE
이 버전부터 Misskey v13을 대응합니다.
@ -120,7 +206,8 @@
---
## 12.119.0-cp-3.0.0
출시일: 2022/09/16
출시일: 2022/09/16<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md](CHANGELOG.md) 문서를 참고하십시오.
## NOTE
이 버전부터는 기존 버전과 연결되지 않고, 새로 포크하여 작업되었습니다. 따라서 기존 버전에 있던 기능들이 다시 명시될 수 있습니다.
@ -154,7 +241,7 @@
### Bugfixes
- 클라이언트: 채팅방에서 메시지를 입력하고 있을 때 움직이지 않는 온점(.)이 표시되는 문제
- 클라이언트: 환영 페이지에서 GitHub 바로가기와 더보기 버튼이 겹쳐있는 경우 더보기 버튼을 누를 수 없었던 문제
- 클라이언트: 환영 페이지에서 GitHub 바로가기와 더 보기 버튼이 겹쳐있는 경우 더 보기 버튼을 누를 수 없었던 문제
<!--
## 12.x.x-cp-2.x.x (unreleased)_legacy
@ -440,8 +527,8 @@ CherryPick은 다른 클라이언트의 유용한 기능들을 **이식**하고
- 노트와 유저를 동시에 검색할 수 있게 변경했어요.
- 사이드 바와 내비게이션 바의 배치를 개선했어요.
- 더 작은 폰트(verySmail) 크기를 추가했어요!
- 프로필 페이지에 프로필 수정 버튼을 추가했어요! 이제 수정을 위해서 더보기 버튼을 누르지 않아도 돼요.
- 프로필 페이지에서 더보기 버튼을 하나로 줄였어요. (저도 왜 이게 2개나 있는지 모르겠어요...)
- 프로필 페이지에 프로필 수정 버튼을 추가했어요! 이제 수정을 위해서 더 보기 버튼을 누르지 않아도 돼요.
- 프로필 페이지에서 더 보기 버튼을 하나로 줄였어요. (저도 왜 이게 2개나 있는지 모르겠어요...)
### UI Improvements
- Friendly UI: 새 노트 알림의 아이콘이 변경됐어요.
@ -467,7 +554,7 @@ CherryPick은 다른 클라이언트의 유용한 기능들을 **이식**하고
- 리모트 유저의 프로필을 불러올 때 문제를 야기할 수 있는 부분이 수정됐어요.
- 환영 페이지에서 배너 이미지가 뜨지 않는 문제를 수정했어요!
- 비로그인 상태에서 유저 프로필의 노트를 열람하지 못하는 문제를 수정했어요.
- 환영 페이지에서 GitHub 바로가기와 더보기 버튼이 겹쳐있는 경우 더보기 버튼을 누를 수 없었던 문제를 수정했어요.
- 환영 페이지에서 GitHub 바로가기와 더 보기 버튼이 겹쳐있는 경우 더 보기 버튼을 누를 수 없었던 문제를 수정했어요.
- 이미지가 노트 영역을 뚫고 나오는 문제를 수정했어요.
- 일본어 및 한국어를 제외한 언어에서 도움말의 API 문서의 목차가 작동하지 않는 문제를 수정했어요.
- 알림 토스트의 텍스트가 eclipse 되지 않는 문제를 수정했어요.

View file

@ -167,8 +167,8 @@ TODO
## Environment Variable
- `MISSKEY_CONFIG_YML`: Specify the file path of config.yml instead of default.yml (e.g. `2nd.yml`).
- `MISSKEY_WEBFINGER_USE_HTTP`: If it's set true, WebFinger requests will be http instead of https, useful for testing federation between servers in localhost. NEVER USE IN PRODUCTION.
- `CHERRYPICK_CONFIG_YML`: Specify the file path of config.yml instead of default.yml (e.g. `2nd.yml`).
- `CHERRYPICK_WEBFINGER_USE_HTTP`: If it's set true, WebFinger requests will be http instead of https, useful for testing federation between servers in localhost. NEVER USE IN PRODUCTION.
## Continuous integration
CherryPick uses GitHub Actions for executing automated tests.
@ -219,7 +219,7 @@ CherryPick uses [Storybook](https://storybook.js.org/) for UI development.
##### Setup
```bash
pnpm --filter misskey-js build
pnpm --filter cherrypick-js build
pnpm --filter frontend tsc -p .storybook && (node packages/frontend/.storybook/preload-locale.js & node packages/frontend/.storybook/preload-theme.js)
```
@ -234,7 +234,7 @@ node packages/frontend/.storybook/generate.js && pnpm --filter frontend storyboo
##### Setup
```bash
pnpm --filter misskey-js build
pnpm --filter cherrypick-js build
```
##### Run
@ -435,7 +435,7 @@ export const paramDef = {
```
### コネクションには`markRaw`せよ
**Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。
**Vueのコンポーネントのdataオプションとして**cherrypick.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。
### JSONのimportに気を付けよう
TypeScriptでjsonをimportすると、tscでコンパイルするときにそのjsonファイルも一緒にdistディレクトリに吐き出されてしまう。この挙動により、意図せずファイルの書き換えが発生することがあるので、jsonをimportするときは書き換えられても良いものかどうか確認すること。書き換えされて欲しくない場合は、importで読み込むのではなく、`fs.readFileSync`などの関数を使って読み込むようにすればよい。

View file

@ -23,7 +23,7 @@ COPY --link ["scripts", "./scripts"]
COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
COPY --link ["packages/sw/package.json", "./packages/sw/"]
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
COPY --link ["packages/cherrypick-js/package.json", "./packages/cherrypick-js/"]
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output

View file

@ -1,6 +1,6 @@
<div align="center">
<a href="https://misskey-hub.net">
<img src="./assets/title_float.svg" alt="Misskey logo" style="border-radius:50%" width="400"/>
<img src="./assets/title_float_cherrypick.svg" alt="CherryPick logo" width="400"/>
</a>
**🌎 **[CherryPick](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀**

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg10"
version="1.1"
viewBox="0 0 872.642 124.261"
height="205.08"
width="414.71">
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<style>
#g8 {
animation-name: floating;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
@keyframes floating {
0% { transform: translate(0, 0px); }
50% { transform: translate(0, -25px); }
100% { transform: translate(0, 0px); }
}
</style>
<linearGradient id="myGradient" gradientTransform="rotate(90)">
<stop offset="5%" stop-color="#A1CA03" />
<stop offset="95%" stop-color="#91BA03" />
</linearGradient>
<defs
id="defs14" />
<g
id="g8"
fill="url('#myGradient')"
word-spacing="0"
letter-spacing="0"
font-family="OTADESIGN Rounded"
font-weight="400">
<g><path style="opacity:0.938" fill="#fefefe" d="M 562.5,-0.5 C 570.833,-0.5 579.167,-0.5 587.5,-0.5C 597.026,0.211835 606.693,1.04517 616.5,2C 633.704,7.51005 643.538,19.3434 646,37.5C 647.96,18.205 658.46,10.3717 677.5,14C 690.528,21.4164 694.695,32.2497 690,46.5C 688.357,48.9768 686.524,51.3102 684.5,53.5C 687.4,57.3614 689.233,61.6948 690,66.5C 690.667,81.1667 690.667,95.8333 690,110.5C 686.364,128.4 675.864,134.567 658.5,129C 650.611,123.71 646.778,116.21 647,106.5C 646.333,94.1667 646.333,81.8333 647,69.5C 647.747,63.1742 650.247,57.6742 654.5,53C 650.647,50.1477 648.147,46.3144 647,41.5C 642.462,78.4843 621.628,93.4843 584.5,86.5C 584.666,95.8393 584.5,105.173 584,114.5C 582.167,124.333 576.333,130.167 566.5,132C 553.843,133.093 545.676,127.593 542,115.5C 541.33,98.416 540.997,81.416 541,64.5C 539.131,69.9025 536.465,74.9025 533,79.5C 519.717,97.784 506.051,115.784 492,133.5C 486.498,139.809 479.998,144.476 472.5,147.5C 470.5,147.5 468.5,147.5 466.5,147.5C 451.684,142.905 446.85,133.238 452,118.5C 455.053,111.556 459.219,105.389 464.5,100C 457.609,90.7744 451.109,81.2744 445,71.5C 442.108,74.0574 438.941,76.224 435.5,78C 426.285,79.6892 418.452,83.8559 412,90.5C 410.725,92.7173 409.725,95.0506 409,97.5C 408.072,105.334 406.405,113.001 404,120.5C 397.785,128.442 389.619,131.275 379.5,129C 374.794,127.895 371.627,125.062 370,120.5C 368.35,113.933 367.35,107.267 367,100.5C 366.667,89.5 366.333,78.5 366,67.5C 364.539,71.2597 362.039,74.0931 358.5,76C 351.086,78.8046 343.753,81.8046 336.5,85C 332.072,88.0925 329.239,92.2591 328,97.5C 327.072,105.334 325.405,113.001 323,120.5C 319.955,124.942 315.789,127.776 310.5,129C 300.955,131.606 293.789,128.773 289,120.5C 287.35,113.933 286.35,107.267 286,100.5C 285.713,86.324 285.213,72.1573 284.5,58C 284.987,37.4995 294.987,30.1661 314.5,36C 318.759,38.4257 321.759,41.9257 323.5,46.5C 335.081,38.7435 347.081,37.9102 359.5,44C 362.04,46.2047 364.207,48.7047 366,51.5C 369.961,35.4344 379.794,30.2677 395.5,36C 399.759,38.4257 402.759,41.9257 404.5,46.5C 414.506,39.9966 425.172,38.4966 436.5,42C 439.102,42.8834 441.269,44.3834 443,46.5C 452.419,35.0514 463.252,33.5514 475.5,42C 481.611,49.056 487.111,56.556 492,64.5C 497.051,55.45 503.551,47.6167 511.5,41C 525.545,35.4544 535.378,39.621 541,53.5C 541.075,40.1113 541.742,26.778 543,13.5C 547.028,5.24868 553.528,0.582017 562.5,-0.5 Z M 585.5,40.5 C 591.833,40.5 598.167,40.5 604.5,40.5C 604.714,42.1439 604.38,43.6439 603.5,45C 597.509,45.4995 591.509,45.6662 585.5,45.5C 585.5,43.8333 585.5,42.1667 585.5,40.5 Z"/></g>
<g><path style="opacity:0.933" fill="#fefdfe" d="M -0.5,79.5 C -0.5,71.5 -0.5,63.5 -0.5,55.5C 7.18255,18.6518 29.8492,0.485143 67.5,1C 80.2553,2.35617 91.422,7.1895 101,15.5C 109.346,25.1479 110.013,35.1479 103,45.5C 97.5097,50.7729 91.0097,52.6063 83.5,51C 78.1667,49 72.8333,47 67.5,45C 48.526,44.9929 40.6926,54.4929 44,73.5C 48.1625,83.7544 55.8291,88.4211 67,87.5C 75.1059,85.9277 83.2725,85.4277 91.5,86C 103.046,91.2585 107.546,100.092 105,112.5C 102.585,118.583 98.4185,123.083 92.5,126C 72.3468,132.291 52.3468,131.791 32.5,124.5C 14.9182,114.34 3.91818,99.3401 -0.5,79.5 Z"/></g>
<g><path style="opacity:0.931" fill="#fefdfe" d="M 123.5,1.5 C 137.73,-0.309975 146.563,5.69003 150,19.5C 150.595,27.1652 151.095,34.8318 151.5,42.5C 179.819,42.5525 195.653,56.5525 199,84.5C 199.667,93.8333 199.667,103.167 199,112.5C 197.305,123.613 190.805,129.613 179.5,130.5C 169.843,129.44 163.343,124.44 160,115.5C 158.922,105.865 158.255,96.1986 158,86.5C 156.477,84.3799 154.644,84.0465 152.5,85.5C 151.809,94.52 150.976,103.52 150,112.5C 147.647,123.849 140.647,129.849 129,130.5C 118.779,129.278 112.779,123.611 111,113.5C 109.413,82.1897 109.08,50.8564 110,19.5C 110.452,10.1912 114.952,4.19124 123.5,1.5 Z"/></g>
<g><path style="opacity:0.936" fill="#fdfefe" d="M 860.5,50.5 C 860.5,52.1667 860.5,53.8333 860.5,55.5C 859.322,59.1759 857.822,62.8426 856,66.5C 850.684,70.6486 845.851,75.3152 841.5,80.5C 845.445,85.9476 849.945,90.9476 855,95.5C 860.929,104.817 860.929,114.15 855,123.5C 841.511,132.705 829.511,131.038 819,118.5C 810.001,131.57 798.835,133.903 785.5,125.5C 782.941,123.048 781.441,120.048 781,116.5C 779.986,84.1703 779.653,51.837 780,19.5C 781.162,8.67094 787.162,3.00427 798,2.5C 809.985,3.48677 817.318,9.82011 820,21.5C 820.333,28.5 820.667,35.5 821,42.5C 829.631,33.2582 839.798,31.0915 851.5,36C 856.449,39.7517 859.449,44.585 860.5,50.5 Z"/></g>
<g><path style="opacity:1" fill="#a4c2ea" d="M 624.5,14.5 C 624.44,15.0431 624.107,15.3764 623.5,15.5C 605.237,13.5142 586.904,12.6808 568.5,13C 561.518,13.9861 557.018,17.8194 555,24.5C 554.333,55.1667 554.333,85.8333 555,116.5C 555.505,118.518 556.005,120.518 556.5,122.5C 553.441,120.603 551.274,117.936 550,114.5C 549.333,82.5 549.333,50.5 550,18.5C 551.833,14 555,10.8333 559.5,9C 577.853,8.25048 596.186,8.58382 614.5,10C 617.978,11.2401 621.311,12.7401 624.5,14.5 Z"/></g>
<g><path style="opacity:1" fill="#eaaeca" d="M 94.5,21.5 C 77.4278,12.2621 59.7612,11.0955 41.5,18C 22.4906,29.6853 12.8239,46.6853 12.5,69C 12.4987,77.3284 13.6654,85.4951 16,93.5C 19.3635,98.2301 22.1968,103.23 24.5,108.5C 8.49291,91.3948 3.65958,71.3948 10,48.5C 19.6274,19.8696 39.4608,7.03626 69.5,10C 79.1609,11.2613 87.4942,15.0947 94.5,21.5 Z"/></g>
<g><path style="opacity:1" fill="#eaadca" d="M 137.5,13.5 C 128.618,11.8876 124.118,15.5543 124,24.5C 123.196,56.523 123.363,88.523 124.5,120.5C 121.088,118.148 119.254,114.815 119,110.5C 118.333,79.1667 118.333,47.8333 119,16.5C 119.786,13.4173 121.619,11.2506 124.5,10C 128.236,9.39012 131.903,9.72345 135.5,11C 136.381,11.7083 137.047,12.5416 137.5,13.5 Z"/></g>
<g><path style="opacity:1" fill="#a4c2ea" d="M 808.5,16.5 C 801.445,14.4574 796.612,16.7907 794,23.5C 793.333,54.8333 793.333,86.1667 794,117.5C 795.298,118.946 795.465,120.279 794.5,121.5C 791.444,119.737 789.611,117.07 789,113.5C 788.333,81.1667 788.333,48.8333 789,16.5C 790.8,12.765 793.8,11.0983 798,11.5C 802.559,11.175 806.059,12.8417 808.5,16.5 Z"/></g>
<g><path style="opacity:1" fill="#b1d3fe" d="M 624.5,14.5 C 638.166,27.0441 641.333,42.0441 634,59.5C 630.74,68.4275 624.573,74.2608 615.5,77C 602.542,78.3529 589.542,78.8529 576.5,78.5C 576.667,90.1714 576.5,101.838 576,113.5C 572.355,122.571 565.855,125.571 556.5,122.5C 556.005,120.518 555.505,118.518 555,116.5C 554.333,85.8333 554.333,55.1667 555,24.5C 557.018,17.8194 561.518,13.9861 568.5,13C 586.904,12.6808 605.237,13.5142 623.5,15.5C 624.107,15.3764 624.44,15.0431 624.5,14.5 Z M 585.5,40.5 C 585.5,42.1667 585.5,43.8333 585.5,45.5C 591.509,45.6662 597.509,45.4995 603.5,45C 604.38,43.6439 604.714,42.1439 604.5,40.5C 598.167,40.5 591.833,40.5 585.5,40.5 Z"/></g>
<g><path style="opacity:1" fill="#febcdb" d="M 94.5,21.5 C 99.9885,26.7734 100.822,32.7734 97,39.5C 93.51,43.0473 89.3434,44.214 84.5,43C 72.1088,35.2117 59.4421,34.545 46.5,41C 33.0505,53.692 30.8838,68.192 40,84.5C 43.1033,88.114 46.6033,91.114 50.5,93.5C 54.5446,96.5773 59.2113,98.4106 64.5,99C 74.1899,99.0965 83.8565,98.5965 93.5,97.5C 99.6325,105.525 98.2992,112.358 89.5,118C 86.2265,119.202 82.8931,120.202 79.5,121C 66.7139,122.123 54.0472,121.456 41.5,119C 35.168,116.504 29.5013,113.004 24.5,108.5C 22.1968,103.23 19.3635,98.2301 16,93.5C 13.6654,85.4951 12.4987,77.3284 12.5,69C 12.8239,46.6853 22.4906,29.6853 41.5,18C 59.7612,11.0955 77.4278,12.2621 94.5,21.5 Z"/></g>
<g><path style="opacity:1" fill="#febcdc" d="M 137.5,13.5 C 139.984,15.1198 141.484,17.4531 142,20.5C 142.185,31.2254 143.018,41.8921 144.5,52.5C 145.736,51.7542 147.07,51.4208 148.5,51.5C 148.5,52.8333 148.5,54.1667 148.5,55.5C 158.895,54.6255 168.895,55.9588 178.5,59.5C 183.7,63.5269 187.2,68.8603 189,75.5C 192.196,89.191 192.196,102.858 189,116.5C 183.643,122.196 177.81,122.863 171.5,118.5C 172.657,109.676 172.824,100.676 172,91.5C 171.676,87.5084 169.843,84.5084 166.5,82.5C 160.861,76.0304 154.194,74.8638 146.5,79C 145.333,80.8333 144.167,82.6667 143,84.5C 142.667,92.5 142.333,100.5 142,108.5C 140.504,119.148 134.671,123.148 124.5,120.5C 123.363,88.523 123.196,56.523 124,24.5C 124.118,15.5543 128.618,11.8876 137.5,13.5 Z"/></g>
<g><path style="opacity:1" fill="#b1d2fe" d="M 808.5,16.5 C 810.539,17.911 811.539,19.911 811.5,22.5C 811.168,34.8513 811.501,47.1847 812.5,59.5C 813.107,59.3764 813.44,59.0431 813.5,58.5C 814.833,57.5 816.167,56.5 817.5,55.5C 817.192,57.924 817.526,60.2573 818.5,62.5C 824.986,57.0915 831.653,51.9249 838.5,47C 841.726,45.8011 845.059,45.3011 848.5,45.5C 850.828,47.1661 851.828,49.4995 851.5,52.5C 851.378,55.6979 850.545,58.6979 849,61.5C 842.167,67.6667 835.333,73.8333 828.5,80C 834.968,87.4695 841.802,94.6362 849,101.5C 854.006,114.941 849.506,121.441 835.5,121C 834.167,120.333 832.833,119.667 831.5,119C 825.319,112.652 818.986,106.485 812.5,100.5C 811.844,105.879 811.011,111.213 810,116.5C 805.732,121.536 800.565,123.203 794.5,121.5C 795.465,120.279 795.298,118.946 794,117.5C 793.333,86.1667 793.333,54.8333 794,23.5C 796.612,16.7907 801.445,14.4574 808.5,16.5 Z"/></g>
<g><path style="opacity:1" fill="#a4c3ea" d="M 678.5,25.5 C 674.818,25.3347 671.152,25.5014 667.5,26C 666.042,28.46 664.209,30.6266 662,32.5C 661.137,35.2686 660.637,38.102 660.5,41C 660.305,43.3523 660.972,45.3523 662.5,47C 662.272,47.3988 661.938,47.5654 661.5,47.5C 654.347,42.1886 652.847,35.5219 657,27.5C 661.618,21.9473 667.451,20.114 674.5,22C 676.324,22.66 677.657,23.8267 678.5,25.5 Z"/></g>
<g><path style="opacity:1" fill="#b1d2fe" d="M 678.5,25.5 C 680.708,26.8936 682.208,28.8936 683,31.5C 684.16,45.6685 677.66,51.5019 663.5,49C 662.584,48.7216 661.918,48.2216 661.5,47.5C 661.938,47.5654 662.272,47.3988 662.5,47C 660.972,45.3523 660.305,43.3523 660.5,41C 660.637,38.102 661.137,35.2686 662,32.5C 664.209,30.6266 666.042,28.46 667.5,26C 671.152,25.5014 674.818,25.3347 678.5,25.5 Z"/></g>
<g><path style="opacity:0.951" fill="#fcfdfe" d="M 612.5,36.5 C 613.198,42.0606 612.198,47.2273 609.5,52C 607.913,52.8624 606.246,53.5291 604.5,54C 597.174,54.4997 589.841,54.6663 582.5,54.5C 580.856,54.7135 579.356,54.3802 578,53.5C 577.188,46.2529 577.354,39.0862 578.5,32C 588.167,31.3333 597.833,31.3333 607.5,32C 609.687,33.025 611.354,34.525 612.5,36.5 Z M 585.5,40.5 C 585.5,42.1667 585.5,43.8333 585.5,45.5C 591.509,45.6662 597.509,45.4995 603.5,45C 604.38,43.6439 604.714,42.1439 604.5,40.5C 598.167,40.5 591.833,40.5 585.5,40.5 Z"/></g>
<g><path style="opacity:0.93" fill="#fdfefe" d="M 736.5,34.5 C 750.144,33.6146 762.31,37.2813 773,45.5C 780.675,56.6326 779.842,67.1326 770.5,77C 761.636,81.32 752.636,81.4867 743.5,77.5C 741.618,78.0933 739.785,78.7599 738,79.5C 737.082,82.0631 737.582,84.2298 739.5,86C 741.8,86.6583 744.133,87.1583 746.5,87.5C 755.169,85.5317 763.503,86.3651 771.5,90C 780.218,99.0098 781.385,108.843 775,119.5C 769.119,125.524 761.952,129.024 753.5,130C 737.599,131.755 722.933,128.422 709.5,120C 693.459,104.001 688.959,85.1678 696,63.5C 704.081,46.2508 717.581,36.5842 736.5,34.5 Z"/></g>
<g><path style="opacity:1" fill="#e4efff" d="M 811.5,22.5 C 812.167,34.5 812.833,46.5 813.5,58.5C 813.44,59.0431 813.107,59.3764 812.5,59.5C 811.501,47.1847 811.168,34.8513 811.5,22.5 Z"/></g>
<g><path style="opacity:0.932" fill="#fefdfe" d="M 235.5,36.5 C 262.59,34.0188 278.59,46.0188 283.5,72.5C 283.102,81.4645 279.436,88.6312 272.5,94C 279.452,100.081 281.286,107.581 278,116.5C 271.113,126.2 261.613,130.867 249.5,130.5C 217.777,129.115 201.777,112.449 201.5,80.5C 202.358,57.3437 213.691,42.6771 235.5,36.5 Z"/></g>
<g><path style="opacity:1" fill="#a4c2ea" d="M 612.5,36.5 C 614.017,36.5106 615.184,37.1772 616,38.5C 618.645,45.0848 617.812,51.0848 613.5,56.5C 612.624,57.2508 611.624,57.7508 610.5,58C 601.173,58.4998 591.839,58.6665 582.5,58.5C 582.5,57.1667 582.5,55.8333 582.5,54.5C 589.841,54.6663 597.174,54.4997 604.5,54C 606.246,53.5291 607.913,52.8624 609.5,52C 612.198,47.2273 613.198,42.0606 612.5,36.5 Z"/></g>
<g><path style="opacity:1" fill="#e9aeca" d="M 313.5,46.5 C 310.482,46.3354 307.482,46.502 304.5,47C 301.923,49.2367 299.756,51.7367 298,54.5C 298.142,69.4869 298.476,84.4869 299,99.5C 299.355,106.07 300.188,112.57 301.5,119C 301.43,119.765 301.097,120.265 300.5,120.5C 299.206,119.71 298.039,118.71 297,117.5C 293.877,95.2816 292.877,72.9483 294,50.5C 295.936,42.8714 300.769,40.3714 308.5,43C 310.584,43.6972 312.251,44.8639 313.5,46.5 Z"/></g>
<g><path style="opacity:1" fill="#e9aeca" d="M 394.5,46.5 C 391.482,46.3354 388.482,46.502 385.5,47C 382.923,49.2367 380.756,51.7367 379,54.5C 379.142,69.4869 379.476,84.4869 380,99.5C 380.355,106.07 381.188,112.57 382.5,119C 382.43,119.765 382.097,120.265 381.5,120.5C 380.206,119.71 379.039,118.71 378,117.5C 374.877,95.2816 373.877,72.9483 375,50.5C 376.936,42.8714 381.769,40.3714 389.5,43C 391.584,43.6972 393.251,44.8639 394.5,46.5 Z"/></g>
<g><path style="opacity:1" fill="#a4c2e9" d="M 848.5,45.5 C 845.059,45.3011 841.726,45.8011 838.5,47C 831.653,51.9249 824.986,57.0915 818.5,62.5C 817.526,60.2573 817.192,57.924 817.5,55.5C 823.453,50.5183 829.786,46.0183 836.5,42C 841.401,40.593 845.401,41.7597 848.5,45.5 Z"/></g>
<g><path style="opacity:1" fill="#a4c3ea" d="M 764.5,49.5 C 764.44,50.0431 764.107,50.3764 763.5,50.5C 752.604,46.7979 741.604,46.6312 730.5,50C 727.874,52.1457 724.874,53.479 721.5,54C 715.753,58.3385 711.586,63.8385 709,70.5C 707.88,76.963 707.046,83.463 706.5,90C 707.21,94.9283 708.377,99.7616 710,104.5C 712.358,107.222 714.191,110.222 715.5,113.5C 703.547,102.474 699.047,88.8071 702,72.5C 706.232,57.2684 716.066,47.7684 731.5,44C 743.214,42.0205 754.214,43.8539 764.5,49.5 Z"/></g>
<g><path style="opacity:1" fill="#e9aeca" d="M 261.5,50.5 C 232.885,44.3062 217.552,55.8062 215.5,85C 215.128,91.7242 216.128,98.2242 218.5,104.5C 219.453,106.908 220.787,109.075 222.5,111C 221.944,111.383 221.611,111.883 221.5,112.5C 215.338,106.169 211.838,98.5024 211,89.5C 208.144,73.7326 212.644,60.5659 224.5,50C 226.888,48.1395 229.554,46.8061 232.5,46C 241.326,44.9201 249.993,45.5867 258.5,48C 259.942,48.3768 260.942,49.2101 261.5,50.5 Z"/></g>
<g><path style="opacity:1" fill="#eaaeca" d="M 468.5,48.5 C 457.096,48.7334 452.596,54.4001 455,65.5C 457.231,70.7311 459.897,75.7311 463,80.5C 469.12,88.1122 474.62,96.1122 479.5,104.5C 473.005,110.84 467.838,118.173 464,126.5C 463.502,129.817 463.335,133.15 463.5,136.5C 458.881,133.366 457.381,129.032 459,123.5C 463.022,115.156 468.189,107.656 474.5,101C 468.044,90.5914 461.211,80.4247 454,70.5C 450.372,64.6242 449.039,58.2909 450,51.5C 455.429,44.847 461.595,43.847 468.5,48.5 Z"/></g>
<g><path style="opacity:1" fill="#febbdb" d="M 313.5,46.5 C 314.931,47.7804 315.598,49.4471 315.5,51.5C 315.177,55.226 315.511,58.8927 316.5,62.5C 317.107,62.3764 317.44,62.0431 317.5,61.5C 318.605,60.3995 319.938,59.7329 321.5,59.5C 321.5,61.1667 321.5,62.8333 321.5,64.5C 322.552,64.6495 323.552,64.4828 324.5,64C 332.696,54.5746 343.03,50.7412 355.5,52.5C 357.894,54.3318 358.894,56.8318 358.5,60C 358.66,62.4697 357.993,64.6364 356.5,66.5C 347.825,70.5633 339.158,74.7299 330.5,79C 326.241,82.3584 323.074,86.5251 321,91.5C 318.999,100.189 316.999,108.855 315,117.5C 310.592,121.229 305.759,122.229 300.5,120.5C 301.097,120.265 301.43,119.765 301.5,119C 300.188,112.57 299.355,106.07 299,99.5C 298.476,84.4869 298.142,69.4869 298,54.5C 299.756,51.7367 301.923,49.2367 304.5,47C 307.482,46.502 310.482,46.3354 313.5,46.5 Z"/></g>
<g><path style="opacity:1" fill="#febbdb" d="M 394.5,46.5 C 395.931,47.7804 396.598,49.4471 396.5,51.5C 396.177,55.226 396.511,58.8927 397.5,62.5C 398.107,62.3764 398.44,62.0431 398.5,61.5C 399.605,60.3995 400.938,59.7329 402.5,59.5C 402.5,61.1667 402.5,62.8333 402.5,64.5C 403.552,64.6495 404.552,64.4828 405.5,64C 413.696,54.5746 424.03,50.7412 436.5,52.5C 438.894,54.3318 439.894,56.8318 439.5,60C 439.66,62.4697 438.993,64.6364 437.5,66.5C 428.825,70.5633 420.158,74.7299 411.5,79C 407.241,82.3584 404.074,86.5251 402,91.5C 399.999,100.189 397.999,108.855 396,117.5C 391.592,121.229 386.759,122.229 381.5,120.5C 382.097,120.265 382.43,119.765 382.5,119C 381.188,112.57 380.355,106.07 380,99.5C 379.476,84.4869 379.142,69.4869 379,54.5C 380.756,51.7367 382.923,49.2367 385.5,47C 388.482,46.502 391.482,46.3354 394.5,46.5 Z"/></g>
<g><path style="opacity:1" fill="#e9adc9" d="M 528.5,49.5 C 525.109,50.141 521.776,51.3077 518.5,53C 511.308,62.0244 504.308,71.1911 497.5,80.5C 497,80.8333 496.5,81.1667 496,81.5C 494.588,80.3144 493.421,78.981 492.5,77.5C 499.462,67.7593 506.795,58.2593 514.5,49C 519.366,45.5402 524.033,45.7069 528.5,49.5 Z"/></g>
<g><path style="opacity:1" fill="#e9adca" d="M 355.5,52.5 C 343.03,50.7412 332.696,54.5746 324.5,64C 323.552,64.4828 322.552,64.6495 321.5,64.5C 321.5,62.8333 321.5,61.1667 321.5,59.5C 328.774,50.7035 338.107,47.2035 349.5,49C 352.025,49.4238 354.025,50.5905 355.5,52.5 Z"/></g>
<g><path style="opacity:1" fill="#e9adca" d="M 436.5,52.5 C 424.03,50.7412 413.696,54.5746 405.5,64C 404.552,64.4828 403.552,64.6495 402.5,64.5C 402.5,62.8333 402.5,61.1667 402.5,59.5C 409.774,50.7035 419.107,47.2035 430.5,49C 433.025,49.4238 435.025,50.5905 436.5,52.5 Z"/></g>
<g><path style="opacity:1" fill="#b1d3fe" d="M 764.5,49.5 C 769.751,54.124 770.918,59.7907 768,66.5C 765.917,70.1294 762.75,72.1294 758.5,72.5C 751.719,69.8515 744.719,69.0182 737.5,70C 728.323,74.5372 726.156,81.3706 731,90.5C 733.015,91.6966 734.849,93.0299 736.5,94.5C 738.474,95.9059 740.474,97.4059 742.5,99C 750.843,99.4579 759.176,99.2912 767.5,98.5C 773.245,109.068 770.245,116.235 758.5,120C 743.081,124.479 728.747,122.313 715.5,113.5C 714.191,110.222 712.358,107.222 710,104.5C 708.377,99.7616 707.21,94.9283 706.5,90C 707.046,83.463 707.88,76.963 709,70.5C 711.586,63.8385 715.753,58.3385 721.5,54C 724.874,53.479 727.874,52.1457 730.5,50C 741.604,46.6312 752.604,46.7979 763.5,50.5C 764.107,50.3764 764.44,50.0431 764.5,49.5 Z"/></g>
<g><path style="opacity:1" fill="#febbdb" d="M 261.5,50.5 C 272.828,57.5073 276.661,67.5073 273,80.5C 267.788,89.3125 259.955,93.3125 249.5,92.5C 245.774,92.1774 242.107,92.5108 238.5,93.5C 238.624,94.1067 238.957,94.44 239.5,94.5C 241.131,96.6433 243.131,98.31 245.5,99.5C 248.851,101.893 252.684,102.893 257,102.5C 260.782,102.621 264.282,101.954 267.5,100.5C 273.076,106.846 272.409,112.679 265.5,118C 249.725,124.032 235.058,122.199 221.5,112.5C 221.611,111.883 221.944,111.383 222.5,111C 220.787,109.075 219.453,106.908 218.5,104.5C 216.128,98.2242 215.128,91.7242 215.5,85C 217.552,55.8062 232.885,44.3062 261.5,50.5 Z"/></g>
<g><path style="opacity:1" fill="#febbdb" d="M 468.5,48.5 C 470.371,48.8588 471.871,49.8588 473,51.5C 478.667,60.1667 484.333,68.8333 490,77.5C 490.77,78.5981 491.603,78.5981 492.5,77.5C 493.421,78.981 494.588,80.3144 496,81.5C 496.5,81.1667 497,80.8333 497.5,80.5C 504.308,71.1911 511.308,62.0244 518.5,53C 521.776,51.3077 525.109,50.141 528.5,49.5C 534.19,54.655 535.023,60.655 531,67.5C 514.646,90.8584 497.146,113.358 478.5,135C 473.698,138.895 468.698,139.395 463.5,136.5C 463.335,133.15 463.502,129.817 464,126.5C 467.838,118.173 473.005,110.84 479.5,104.5C 474.62,96.1122 469.12,88.1122 463,80.5C 459.897,75.7311 457.231,70.7311 455,65.5C 452.596,54.4001 457.096,48.7334 468.5,48.5 Z"/></g>
<g><path style="opacity:1" fill="#ebafcc" d="M 148.5,51.5 C 157.62,50.5168 166.287,52.0168 174.5,56C 176.324,56.66 177.657,57.8267 178.5,59.5C 168.895,55.9588 158.895,54.6255 148.5,55.5C 148.5,54.1667 148.5,52.8333 148.5,51.5 Z"/></g>
<g><path style="opacity:1" fill="#ffdeed" d="M 315.5,51.5 C 316.167,54.8333 316.833,58.1667 317.5,61.5C 317.44,62.0431 317.107,62.3764 316.5,62.5C 315.511,58.8927 315.177,55.226 315.5,51.5 Z"/></g>
<g><path style="opacity:1" fill="#ffdeed" d="M 396.5,51.5 C 397.167,54.8333 397.833,58.1667 398.5,61.5C 398.44,62.0431 398.107,62.3764 397.5,62.5C 396.511,58.8927 396.177,55.226 396.5,51.5 Z"/></g>
<g><path style="opacity:1" fill="#a6c3e9" d="M 677.5,59.5 C 669.338,58.5038 663.838,61.8372 661,69.5C 659.419,81.4422 659.085,93.4422 660,105.5C 660.217,110.292 661.05,114.958 662.5,119.5C 662.376,120.107 662.043,120.44 661.5,120.5C 658.259,117.656 656.426,113.99 656,109.5C 654.136,95.141 654.136,80.8076 656,66.5C 658.537,58.3162 664.037,54.8162 672.5,56C 674.881,56.2233 676.547,57.39 677.5,59.5 Z"/></g>
<g><path style="opacity:1" fill="#b1d2fe" d="M 677.5,59.5 C 679.746,61.6489 681.246,64.3156 682,67.5C 682.667,81.1667 682.667,94.8333 682,108.5C 679.971,121.254 673.138,125.254 661.5,120.5C 662.043,120.44 662.376,120.107 662.5,119.5C 661.05,114.958 660.217,110.292 660,105.5C 659.085,93.4422 659.419,81.4422 661,69.5C 663.838,61.8372 669.338,58.5038 677.5,59.5 Z"/></g>
<g><path style="opacity:1" fill="#fefbfd" d="M 253.5,68.5 C 253.822,70.7224 253.155,72.5557 251.5,74C 248.321,75.4613 244.988,76.2946 241.5,76.5C 238.57,76.2051 236.57,74.7051 235.5,72C 239.943,62.0944 245.943,60.9277 253.5,68.5 Z"/></g>
<g><path style="opacity:1" fill="#eaadca" d="M 253.5,68.5 C 255.024,69.1738 256.358,70.1738 257.5,71.5C 258.76,73.3529 258.76,75.1862 257.5,77C 252.768,79.4013 247.768,80.068 242.5,79C 241.748,78.3292 241.414,77.4959 241.5,76.5C 244.988,76.2946 248.321,75.4613 251.5,74C 253.155,72.5557 253.822,70.7224 253.5,68.5 Z"/></g>
<g><path style="opacity:1" fill="#e9adc9" d="M 166.5,82.5 C 169.843,84.5084 171.676,87.5084 172,91.5C 172.824,100.676 172.657,109.676 171.5,118.5C 169.59,117.025 168.424,115.025 168,112.5C 167.758,102.467 167.258,92.4672 166.5,82.5 Z"/></g>
<g><path style="opacity:1" fill="#ffdfee" d="M 249.5,92.5 C 246.167,93.1667 242.833,93.8333 239.5,94.5C 238.957,94.44 238.624,94.1067 238.5,93.5C 242.107,92.5108 245.774,92.1774 249.5,92.5 Z"/></g>
<g><path style="opacity:1" fill="#e9aeca" d="M 50.5,93.5 C 55.8792,95.0756 61.3792,96.0756 67,96.5C 72.729,95.8173 78.3956,94.8173 84,93.5C 87.9796,93.2066 91.1463,94.54 93.5,97.5C 83.8565,98.5965 74.1899,99.0965 64.5,99C 59.2113,98.4106 54.5446,96.5773 50.5,93.5 Z"/></g>
<g><path style="opacity:1" fill="#a7c4ea" d="M 736.5,94.5 C 745.143,95.3106 753.81,95.4772 762.5,95C 764.584,95.6972 766.251,96.8639 767.5,98.5C 759.176,99.2912 750.843,99.4579 742.5,99C 740.474,97.4059 738.474,95.9059 736.5,94.5 Z"/></g>
<g><path style="opacity:1" fill="#e9acc9" d="M 267.5,100.5 C 264.282,101.954 260.782,102.621 257,102.5C 252.684,102.893 248.851,101.893 245.5,99.5C 250.526,99.5885 255.526,99.2552 260.5,98.5C 263.078,98.4419 265.411,99.1086 267.5,100.5 Z"/></g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -167,6 +167,12 @@ id: "aid"
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Cloud Logging
#cloudLogging:
# projectId: example-project-id
# saKeyPath: /path/to/service-account-key.json
# logName: cherrypick
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128

View file

@ -64,7 +64,6 @@ describe('After setup instance', () => {
cy.get('[data-cy-signup-submit]').should('be.disabled');
cy.get('[data-cy-signup-password-retype] input').type('alice1234');
cy.get('[data-cy-signup-submit]').should('not.be.disabled');
cy.get('[data-cy-signup-back]').click();
cy.get('[data-cy-signup-submit]').click();
cy.wait('@signup');

View file

@ -1005,7 +1005,7 @@ postToTheChannel: "In Kanal senden"
cannotBeChangedLater: "Kann später nicht mehr geändert werden."
reactionAcceptance: "Reaktionsannahme"
likeOnly: "Nur \"Gefällt mir\""
likeOnlyForRemote: "Nur \"Gefällt mir\" für fremde Instanzen"
likeOnlyForRemote: "Alle (Nur \"Gefällt mir\" für fremde Instanzen)"
nonSensitiveOnly: "Keine Sensitiven"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Keine Sensitiven (Nur \"Gefällt mir\" von fremden Instanzen)"
rolesAssignedToMe: "Mir zugewiesene Rollen"
@ -1076,6 +1076,7 @@ later: "Später"
goToMisskey: "Zu CherryPick"
additionalEmojiDictionary: "Zusätzliche Emoji-Wörterbücher"
installed: "Installiert"
branding: "Branding"
_initialAccountSetting:
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
@ -1107,7 +1108,7 @@ _accountMigration:
migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden."
movedAndCannotBeUndone: "\nDieses Konto wurde migriert.\nDiese Aktion ist unwiderruflich."
postMigrationNote: "Dieses Konto wird 24 Stunden nach Abschluss der Migration allen Konten, denen es derzeit folgt, nicht mehr folgen.\n\nSowohl die Anzahl der Follower als auch die der Konten, denen dieses Konto folgt, wird dann auf Null gesetzt. Um zu vermeiden, dass Follower dieses Kontos dessen Beiträge, welche nur für Follower bestimmt sind, nicht mehr sehen können, werden sie diesem Konto jedoch weiterhin folgen."
movedTo: "Umzugsziel:"
movedTo: "Neues Konto:"
_achievements:
earnedAt: "Freigeschaltet am"
_types:
@ -1361,7 +1362,7 @@ _role:
condition: "Bedingung"
isConditionalRole: "Dies ist eine konditionale Rolle."
isPublic: "Öffentliche Rolle"
descriptionOfIsPublic: "Ist dies aktiviert, so kann jeder die Liste der Benutzer, die dieser Rolle zugewiesen sind, einsehen. Zusätzlich wird diese Rolle im Profil zugewiesener Benutzer angezeigt."
descriptionOfIsPublic: "Diese Rolle wird im Profil zugewiesener Benutzer angezeigt."
options: "Optionen"
policies: "Richtlinien"
baseRole: "Rollenvorlage"
@ -1370,8 +1371,8 @@ _role:
iconUrl: "Icon-URL"
asBadge: "Als Abzeichen anzeigen"
descriptionOfAsBadge: "Ist dies aktiviert, so wird das Icon dieser Rolle an der Seite der Namen von Benutzern mit dieser Rolle angezeigt."
isExplorable: "Rollenchronik veröffentlichen"
descriptionOfIsExplorable: "Ist dies aktiviert, so ist die Rollenchronik dieser Rolle frei zugänglich. Die Chronik von Rollen, welche nicht öffentlich sind, wird auch bei Aktivierung nicht veröffentlicht."
isExplorable: "Benutzerliste veröffentlichen"
descriptionOfIsExplorable: "Ist dies aktiviert, so ist die Chronik dieser Rolle, sowie eine Liste der Benutzer mit dieser Rolle, frei zugänglich."
displayOrder: "Position"
descriptionOfDisplayOrder: "Je höher die Nummer, desto höher die UI-Position."
canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen"
@ -1927,6 +1928,7 @@ _instanceCharts:
_timelines:
home: "Startseite"
local: "Lokal"
media: "Medien"
social: "Sozial"
global: "Global"
_play:

View file

@ -1,5 +1,11 @@
---
_lang_: "English"
posted: "Note are now published."
translateNote: "Translate Note"
showTranslateButtonInNote: "Display translate button in note body"
editNickName: "Edit nickname"
hideAvatarsInNote: "Hide avatars in notes"
displayBanner: "Display Banner Image"
requireRefresh: "When the page needs to refresh"
performanceWarning: "High resource usage can result in higher device temperatures and faster battery consumption"
photosensitiveSeizuresWarning: "Can cause photosensitive seizures"
@ -1029,7 +1035,7 @@ postToTheChannel: "Post to channel"
cannotBeChangedLater: "This cannot be changed later."
reactionAcceptance: "Reaction Acceptance"
likeOnly: "Only likes"
likeOnlyForRemote: "Only likes for remote instances"
likeOnlyForRemote: "All (Only likes for remote instances)"
nonSensitiveOnly: "Non-sensitive only"
nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non-sensitive only (Only likes from remote)"
rolesAssignedToMe: "Roles assigned to me"
@ -1058,6 +1064,9 @@ accountMoved: "This user has moved to a new account:"
accountMovedShort: "This account has been migrated."
operationForbidden: "Operation forbidden"
forceShowAds: "Always show ads"
event: "Event"
events: "Events"
reverseChronological: "flashback"
addMemo: "Add memo"
editMemo: "Edit memo"
reactionsList: "Reactions"
@ -1100,6 +1109,37 @@ later: "Later"
goToMisskey: "To CherryPick"
additionalEmojiDictionary: "Additional emoji dictionaries"
installed: "Installed"
branding: "Branding"
additionalPermissionsForFlash: "Allow to add permission to Play"
thisFlashRequiresTheFollowingPermissions: "This Play requires the following permissions"
doYouWantToAllowThisPlayToAccessYourAccount: "Do you want to allow this Play to access your account?"
translateProfile: "Translate profile"
enableAbsoluteTime: "Enable Absolute Time"
inviteRevoke: "Revoke All Invitation Codes"
inviteRevokeConfirm: "Are you sure that you want to revoke all invitation codes?"
_group:
leader: "Group owner"
banish: "Banish"
banishConfirm: "Do you really want to banish \"{name}\" from \"{group}\"?"
_cherrypick:
function: "Proprietary Functions"
functionDescription: "Enables or disables proprietary features that CherryPick adds."
nickname: "Nickname function"
nicknameDescription: "On the user page, you can change the user's name to anything you like by clicking or tapping on it. The change will be reflected only to you.\nThis can be used to identify users who change their names frequently."
patch: "Patch"
patchDescription: "Make changes to Misskey's functionality"
infoButtonForNoteActions: "Show detail view button in the note"
infoButtonForNoteActionsDescription: "Applies when the option「Only show note actions on hover」is turned on."
rememberPostFormToggleState: "Remember to turn preview on/off in the note post form"
reactableRemoteReaction: "Allow remote custom emoji reactions to react if there is an emoji with the same name on this server."
showFollowingMessageInsteadOfButton: "Do not show the follow button in the notification field if you are already following someone"
_bannerDisplay:
all: "All"
topBottom: "Top and Bottom"
top: "Top (Server banner)"
bottom: "Bottom (Profile banner)"
bg: "Background"
hide: "Hide"
_requireRefreshBehavior:
dialog: "Show warning dialog"
quiet: "Show unobtrusive alert"
@ -1123,6 +1163,34 @@ _initialAccountSetting:
laterAreYouSure: "Really do profile setup later?"
_serverRules:
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
_event:
title: "Title"
startDateTime: "Start date time"
endDateTime: "End date time"
startDate: "Start date"
endDate: "End date"
startTime: "Start time"
endTime: "End time"
detailName: "Details"
detailValue: "value"
location: "Location"
url: "URL"
doorTime: "Door Time"
organizer: "Organizer"
organizerLink: "Organizer Link"
audience: "Audience"
language: "Language"
ageRange: "Age Range"
ticketsUrl: "Tickets"
isFree: "Free"
price: "Price"
availability: "Availability"
from: "From"
until: "Until"
availabilityStart: "Availability Start"
availabilityEnd: "Availability End"
keywords: "Keywords"
performers: "Performers"
_accountMigration:
moveFrom: "Migrate another account to this one"
moveFromSub: "Create alias to another account"
@ -1137,7 +1205,7 @@ _accountMigration:
migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore."
movedAndCannotBeUndone: "\nThis account has been migrated.\nMigration cannot be reversed."
postMigrationNote: "This account will unfollow all accounts it is currently following 24 hours after migration finishes.\nBoth the number of follows and followers will then become zero. To avoid your followers from being unable to see followers only posts of this account, they will however continue following this account."
movedTo: "Account to move to:"
movedTo: "New account:"
_achievements:
earnedAt: "Unlocked at"
_types:
@ -1391,7 +1459,7 @@ _role:
condition: "Condition"
isConditionalRole: "This is a conditional role."
isPublic: "Public role"
descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
descriptionOfIsPublic: "This role will be displayed in the profiles of assigned users."
options: "Options"
policies: "Policies"
baseRole: "Role template"
@ -1400,8 +1468,8 @@ _role:
iconUrl: "Icon URL"
asBadge: "Show as badge"
descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on."
isExplorable: "Role timeline is public"
descriptionOfIsExplorable: "This role's timeline will become publicly accessible if enabled. Timelines of non-public roles will not be made public even if set."
isExplorable: "Make role explorable"
descriptionOfIsExplorable: "This role's timeline and the list of users with this will be made public if enabled."
displayOrder: "Position"
descriptionOfDisplayOrder: "The higher the number, the higher its UI position."
canEditMembersByModerator: "Allow moderators to edit the list of members for this role"
@ -1620,6 +1688,8 @@ _mfm:
bgDescription: "Set the background color to the specified value."
plain: "Plain"
plainDescription: "Deactivates the effects of all MFM contained within this MFM effect."
ruby: "Ruby"
rubyDescription: "Display ruby characters over the text."
_instanceTicker:
none: "Never show"
remote: "Show for remote users"
@ -1979,6 +2049,7 @@ _instanceCharts:
_timelines:
home: "Home"
local: "Local"
media: "Media"
social: "Social"
global: "Global"
_play:

View file

@ -1831,6 +1831,7 @@ _instanceCharts:
_timelines:
home: "Inicio"
local: "Local"
media: "Media"
social: "Social"
global: "Global"
_play:

View file

@ -1491,6 +1491,7 @@ _instanceCharts:
_timelines:
home: "Principal"
local: "Local"
media: "Média"
social: "Social"
global: "Global"
_play:

View file

@ -1750,6 +1750,7 @@ _instanceCharts:
_timelines:
home: "Beranda"
local: "Lokal"
media: "Media"
social: "Sosial"
global: "Global"
_play:

75
locales/index.d.ts vendored
View file

@ -3,6 +3,12 @@
// Do not edit this file directly.
export interface Locale {
"_lang_": string;
"posted": string;
"translateNote": string;
"showTranslateButtonInNote": string;
"editNickName": string;
"hideAvatarsInNote": string;
"displayBanner": string;
"requireRefresh": string;
"performanceWarning": string;
"photosensitiveSeizuresWarning": string;
@ -1061,6 +1067,9 @@ export interface Locale {
"accountMovedShort": string;
"operationForbidden": string;
"forceShowAds": string;
"event": string;
"events": string;
"reverseChronological": string;
"addMemo": string;
"editMemo": string;
"reactionsList": string;
@ -1103,6 +1112,40 @@ export interface Locale {
"goToMisskey": string;
"additionalEmojiDictionary": string;
"installed": string;
"branding": string;
"additionalPermissionsForFlash": string;
"thisFlashRequiresTheFollowingPermissions": string;
"doYouWantToAllowThisPlayToAccessYourAccount": string;
"translateProfile": string;
"enableAbsoluteTime": string;
"inviteRevoke": string;
"inviteRevokeConfirm": string;
"_group": {
"leader": string;
"banish": string;
"banishConfirm": string;
};
"_cherrypick": {
"function": string;
"functionDescription": string;
"nickname": string;
"nicknameDescription": string;
"patch": string;
"patchDescription": string;
"infoButtonForNoteActions": string;
"infoButtonForNoteActionsDescription": string;
"rememberPostFormToggleState": string;
"reactableRemoteReaction": string;
"showFollowingMessageInsteadOfButton": string;
};
"_bannerDisplay": {
"all": string;
"topBottom": string;
"top": string;
"bottom": string;
"bg": string;
"hide": string;
};
"_requireRefreshBehavior": {
"dialog": string;
"quiet": string;
@ -1129,6 +1172,35 @@ export interface Locale {
"_serverRules": {
"description": string;
};
"_event": {
"title": string;
"startDateTime": string;
"endDateTime": string;
"startDate": string;
"endDate": string;
"startTime": string;
"endTime": string;
"detailName": string;
"detailValue": string;
"location": string;
"url": string;
"doorTime": string;
"organizer": string;
"organizerLink": string;
"audience": string;
"language": string;
"ageRange": string;
"ticketsUrl": string;
"isFree": string;
"price": string;
"availability": string;
"from": string;
"until": string;
"availabilityStart": string;
"availabilityEnd": string;
"keywords": string;
"performers": string;
};
"_accountMigration": {
"moveFrom": string;
"moveFromSub": string;
@ -1726,6 +1798,8 @@ export interface Locale {
"bgDescription": string;
"plain": string;
"plainDescription": string;
"ruby": string;
"rubyDescription": string;
};
"_instanceTicker": {
"none": string;
@ -2114,6 +2188,7 @@ export interface Locale {
"_timelines": {
"home": string;
"local": string;
"media": string;
"social": string;
"global": string;
};

View file

@ -1854,6 +1854,7 @@ _instanceCharts:
_timelines:
home: "Home"
local: "Locale"
media: "Contenuti multimediali"
social: "Sociale"
global: "Federata"
_play:

View file

@ -1,6 +1,12 @@
_lang_: "日本語"
requireRefresh: "페이지 새로 고침이 필요할 때"
posted: "ノートを公開しました。"
translateNote: "ノートを翻訳する"
showTranslateButtonInNote: "ノート本文に翻訳ボタンを表示"
editNickName: "ニックネームを編集"
hideAvatarsInNote: "ノートでアイコンを隠す"
displayBanner: "バナー画像の表示"
requireRefresh: "ページの更新が必要なとき"
performanceWarning: "リソースを多く使用するため、デバイスの温度が高くなり、バッテリーの消耗が速くなる可能性があります"
photosensitiveSeizuresWarning: "光敏感性発作を起こす可能性があります"
friendlyEnableNotification: "通知領域を有効化/無効化"
@ -1058,6 +1064,9 @@ accountMoved: "このユーザーは新しいアカウントに移行しまし
accountMovedShort: "このアカウントは移行されています"
operationForbidden: "この操作はできません"
forceShowAds: "常に広告を表示する"
event: "イベント"
events: "イベント"
reverseChronological: "倒叙"
addMemo: "メモを追加"
editMemo: "メモを編集"
reactionsList: "リアクション一覧"
@ -1100,6 +1109,40 @@ later: "あとで"
goToMisskey: "CherryPickへ"
additionalEmojiDictionary: "絵文字の追加辞書"
installed: "インストール済み"
branding: "ブランディング"
additionalPermissionsForFlash: "Playへの追加許可"
thisFlashRequiresTheFollowingPermissions: "このPlayは以下の権限を要求しています"
doYouWantToAllowThisPlayToAccessYourAccount: "このPlayによるアカウントへのアクセスを許可しますか"
translateProfile: "プロフィールを翻訳する"
enableAbsoluteTime: "絶対時刻表記を使用する"
inviteRevoke: "全ての招待コードを失効する"
inviteRevokeConfirm: "本当に全ての招待コードを失効させますか?"
_group:
leader: "グループオーナー"
banish: "追放"
banishConfirm: "本当に\"{name}\"さんを\"{group}\"から追放しますか?"
_cherrypick:
function: "独自機能"
functionDescription: "CherryPickが追加する独自機能を有効・無効にします。"
nickname: "ニックネーム機能"
nicknameDescription: "ユーザーページにて、ユーザーの名前をクリック/タップすることで好きなものに変更できるようになります。変更は自分にのみ反映されます。\n頻繁に名前を変更するユーザーを識別するときなどに使えます。"
patch: "パッチ"
patchDescription: "Misskeyの機能に変更を加えます。"
infoButtonForNoteActions: "ノートに詳細表示ボタンを表示する"
infoButtonForNoteActionsDescription: "オプション「ノートの操作部をホバー時のみ表示する」をオンにしたときに適用されます。"
rememberPostFormToggleState: "投稿フォームにて、プレビューのオン・オフを記憶する"
reactableRemoteReaction: "リモートのカスタム絵文字リアクションでも、このサーバーに同じ名前の絵文字があればリアクションできるようにする"
showFollowingMessageInsteadOfButton: "既にフォローしている場合、通知欄にフォローボタンを表示しない"
_bannerDisplay:
all: "全て"
topBottom: "上部と下部"
top: "上部(サーバーバナー)"
bottom: "下部(プロフィールバナー)"
bg: "バックグラウンド"
hide: "隠す"
_requireRefreshBehavior:
dialog: "ダイアログで通知"
@ -1127,6 +1170,35 @@ _initialAccountSetting:
_serverRules:
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
_event:
title: "題名"
startDateTime: "開始日時"
endDateTime: "終了日時"
startDate: "開始日"
endDate: "終了日"
startTime: "開始時刻"
endTime: "終了時刻"
detailName: "属性"
detailValue: "値"
location: "所在地"
url: "URL"
doorTime: "ドアタイム"
organizer: "主催者"
organizerLink: "主催者リンク"
audience: "オーディエンス"
language: "言語"
ageRange: "年齢層"
ticketsUrl: "チケット"
isFree: "無料"
price: "価格"
availability: "可用性"
from: "から"
until: "まで"
availabilityStart: "アベイラビリティ開始"
availabilityEnd: "アベイラビリティ終了"
keywords: "キーワード"
performers: "出演者"
_accountMigration:
moveFrom: "別のアカウントからこのアカウントに移行"
moveFromSub: "別のアカウントへエイリアスを作成"
@ -1396,8 +1468,8 @@ _role:
conditional: "コンディショナル"
condition: "条件"
isConditionalRole: "これはコンディショナルロールです。"
isPublic: "ロールを公開"
descriptionOfIsPublic: "ロールにアサインされたユーザーを誰でも見ることができます。また、ユーザーのプロフィールでこのロールが表示されます。"
isPublic: "公開ロール"
descriptionOfIsPublic: "ユーザーのプロフィールでこのロールが表示されます。"
options: "オプション"
policies: "ポリシー"
baseRole: "ベースロール"
@ -1406,8 +1478,8 @@ _role:
iconUrl: "アイコン画像のURL"
asBadge: "バッジとして表示"
descriptionOfAsBadge: "オンにすると、ユーザー名の横にロールのアイコンが表示されます。"
isExplorable: "ロールタイムラインを公開"
descriptionOfIsExplorable: "オンにすると、ロールのタイムラインを公開します。ロールの公開がオフの場合、タイムラインの公開はされません。"
isExplorable: "ユーザーを見つけやすくする"
descriptionOfIsExplorable: "オンにすると、「みつける」でメンバー一覧が公開されるほか、ロールのタイムラインが利用可能になります。"
displayOrder: "表示順"
descriptionOfDisplayOrder: "数値が大きいほどUI上で先頭に表示されます。"
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
@ -1642,6 +1714,8 @@ _mfm:
bgDescription: "指定した値で背景色を指定します。"
plain: "プレーン"
plainDescription: "内側の構文を全て無効にします。"
ruby: "ルビ"
rubyDescription: "文字の上にルビを表示します。"
_instanceTicker:
none: "表示しない"
@ -2028,6 +2102,7 @@ _instanceCharts:
_timelines:
home: "ホーム"
local: "ローカル"
media: "メディア"
social: "ソーシャル"
global: "グローバル"

View file

@ -1927,6 +1927,7 @@ _instanceCharts:
_timelines:
home: "ホーム"
local: "ローカル"
media: "メディア"
social: "ソーシャル"
global: "グローバル"
_play:

View file

@ -1,5 +1,11 @@
---
_lang_: "한국어"
posted: "노트를 게시했어요!"
translateNote: "노트 번역하기"
showTranslateButtonInNote: "노트 본문에 번역 버튼 표시"
editNickName: "닉네임 편집"
hideAvatarsInNote: "노트에서 프로필 아이콘 숨김"
displayBanner: "배너 이미지 표시"
requireRefresh: "페이지 새로 고침이 필요할 때"
performanceWarning: "리소스를 많이 사용하므로, 디바이스의 온도가 높아지고 배터리의 소모가 빨라질 수 있어요"
photosensitiveSeizuresWarning: "광과민성 발작을 일으킬 수 있어요"
@ -1059,6 +1065,9 @@ accountMoved: "이 사용자는 다음 계정으로 이사했어요:"
accountMovedShort: "이사한 계정이에요"
operationForbidden: "사용할 수 없어요"
forceShowAds: "광고를 항상 표시"
event: "이벤트"
events: "이벤트"
reverseChronological: "미래순"
addMemo: "메모 추가"
editMemo: "메모 편집"
reactionsList: "리액션 목록"
@ -1078,7 +1087,7 @@ pleaseAgreeAllToContinue: "계속하시려면 모든 항목에 동의해야 해
continue: "계속"
preservedUsernames: "예약된 사용자명"
preservedUsernamesDescription: "예약할 사용자명을 한 줄에 하나씩 입력해 주세요. 여기에서 지정한 사용자명으로는 계정을 생성할 수 없게 돼요. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않아요."
createNoteFromTheFile: "이 파일로 노트 작성"
createNoteFromTheFile: "이 파일로 노트 작성"
archive: "아카이브"
channelArchiveConfirmTitle: "{name} 을(를) 아카이브할까요?"
channelArchiveConfirmDescription: "아카이브한 채널은 채널 목록과 검색 결과에 표시되지 않으며, 채널에 새로운 노트를 작성할 수 없게 돼요."
@ -1093,14 +1102,44 @@ specifyUser: "사용자 지정"
failedToPreviewUrl: "미리 볼 수 없음"
update: "업데이트"
rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할"
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "역할을 지정하지 않으면, 누구나 이 이모지를 리액션으로 사용할 수 있습니다."
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "역할은 공개로 설정되어 있어야 합니다."
rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "역할을 지정하지 않으면 누구나 이 이모지를 리액션으로 사용할 수 있어요."
rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "역할은 공개로 설정되어 있어야 해요."
cancelReactionConfirm: "리액션을 취소하시겠습니까?"
changeReactionConfirm: "리액션을 변경하시겠습니까?"
later: "나중에"
goToMisskey: "CherryPick으로"
additionalEmojiDictionary: "이모지 추가 사전"
installed: "설치됨"
additionalPermissionsForFlash: "Play에 대한 추가 권한"
thisFlashRequiresTheFollowingPermissions: "이 Play는 다음 권한을 요구해요"
doYouWantToAllowThisPlayToAccessYourAccount: "이 Play가 계정에 접근하도록 허용할까요?"
translateProfile: "프로필 번역하기"
enableAbsoluteTime: "절대 시간 표기 사용"
inviteRevoke: "모든 초대 코드 무효화"
inviteRevokeConfirm: "정말로 모든 초대 코드를 무효화 할까요?"
_group:
leader: "그룹 주인"
banish: "추방"
banishConfirm: "정말로 \"{name}\" 님을 \"{group}\" 에서 추방할까요?"
_cherrypick:
function: "고유 기능"
functionDescription: "CherryPick이 추가하는 고유 기능을 활성화/비활성화할 수 있어요."
nickname: "닉네임 기능"
nicknameDescription: "유저 페이지에서 이름을 클릭 또는 탭하여 원하는 이름으로 변경할 수 있어요. 변경한 이름은 자신에게만 반영돼요.\n자주 이름을 바꾸는 사용자를 식별하는 데 사용할 수 있어요."
patch: "패치"
patchDescription: "Misskey의 기능을 변경해요."
infoButtonForNoteActions: "노트에 자세히 버튼 표시"
infoButtonForNoteActionsDescription: "「노트 액션 버튼을 마우스를 올렸을 때에만 표시」기능을 켰을 때만 적용돼요."
rememberPostFormToggleState: "노트 작성 화면에서 본문 미리보기 활성화 상태 기억"
reactableRemoteReaction: "서버에 리모트 이모지와 이름이 같은 이모지가 있으면 리모트 이모지에도 반응할 수 있음"
showFollowingMessageInsteadOfButton: "이미 팔로우한 경우 알림 필드에 팔로우 버튼을 표시하지 않음"
_bannerDisplay:
all: "전부"
topBottom: "상단 및 하단"
top: "상단 (서버 배너)"
bottom: "하단 (프로필 배너)"
bg: "백그라운드"
hide: "숨기기"
_requireRefreshBehavior:
dialog: "알림창 표시"
quiet: "조용히 알림"
@ -1124,6 +1163,34 @@ _initialAccountSetting:
laterAreYouSure: "초기 설정을 나중에 진행할까요?"
_serverRules:
description: "회원 가입 이전에 간단하게 표시할 서버 규칙이에요. 이용 약관의 요약으로 구성하는 것을 추천해요."
_event:
title: "제목"
startDateTime: "시작 일시"
endDateTime: "종료 일시"
startDate: "시작일"
endDate: "종료일"
startTime: "시작 시간"
endTime: "종료 시간"
detailName: "속성"
detailValue: "값"
location: "위치"
url: "URL"
doorTime: "출입 가능 시간"
organizer: "주최자"
organizerLink: "주최자 링크"
audience: "대상"
language: "언어"
ageRange: "연령층"
ticketsUrl: "티켓"
isFree: "무료"
price: "가격"
availability: "사용 가능 여부"
from: "부터"
until: "까지"
availabilityStart: "이용 가능 시간"
availabilityEnd: "이용 종료 시간"
keywords: "키워드"
performers: "출연자"
_accountMigration:
moveFrom: "다른 계정에서 이 계정으로 이사"
moveFromSub: "다른 계정에 대한 별칭을 생성"
@ -1621,6 +1688,8 @@ _mfm:
bgDescription: "지정한 값으로 배경색을 지정해요."
plain: "평문"
plainDescription: "안에 있는 MFM 구문을 모두 무시하고 평문으로 표시해요."
ruby: "루비"
rubyDescription: "글자 위에 루비를 표시해요."
_instanceTicker:
none: "보이지 않음"
remote: "리모트 유저에게만 보이기"
@ -1980,6 +2049,7 @@ _instanceCharts:
_timelines:
home: "홈"
local: "로컬"
media: "미디어"
social: "소셜"
global: "글로벌"
_play:

View file

@ -1835,6 +1835,7 @@ _instanceCharts:
_timelines:
home: "Персональная"
local: "Местная"
media: "Медиа"
social: "Социальная"
global: "Всеобщая"
_play:

View file

@ -1415,6 +1415,7 @@ _instanceCharts:
_timelines:
home: "Domov"
local: "Lokálne"
media: "Médiá"
social: "Sociálne"
global: "Globálne"
_play:

View file

@ -1880,6 +1880,7 @@ _instanceCharts:
_timelines:
home: "หน้าแรก"
local: "ในพื้นที่"
media: "สื่อ"
social: "โซเชี่ยล"
global: "ทั่วโลก"
_play:

View file

@ -1,6 +1,7 @@
---
_lang_: "Türkçe"
introMisskey: "Açık kaynaklı bir dağıtılmış mikroblog hizmeti olan CherryPick'e hoş geldiniz.\nCherryPick, neler olup bittiğini paylaşmak ve herkese sizden bahsetmek için \"notlar\" oluşturmanıza olanak tanıyan, açık kaynaklı, dağıtılmış bir mikroblog hizmetidir.\nHerkesin notlarına kendi tepkilerinizi hızlıca eklemek için \"Tepkiler\" özelliğini de kullanabilirsiniz👍.\nYeni bir dünyayı keşfedin🚀."
poweredByMisskeyDescription: "name}Açık kaynak bir platform\n<b>CherryPick</b>Dünya'nın en sunucularında biri。"
monthAndDay: "{month}Ay {day}Gün"
search: "Arama"
notifications: "Bildirim"
@ -13,7 +14,9 @@ cancel: "İptal"
enterUsername: "Kullanıcı adınızı giriniz"
noNotes: "Notlar mevcut değil."
noNotifications: "Bildirim bulunmuyor"
instance: "Sunucu"
settings: "Ayarlar"
notificationSettings: "Bildirim Ayarları"
basicSettings: "Temel Ayarlar"
otherSettings: "Diğer Ayarlar"
openInWindow: "Bir pencere ile aç"
@ -21,9 +24,11 @@ profile: "Profil"
timeline: "Zaman çizelgesi"
noAccountDescription: "Bu kullanıcı henüz biyografisini yazmadı"
login: "Giriş Yap "
loggingIn: "Oturum aç"
logout: ıkış Yap"
signup: "Kayıt Ol"
uploading: "Yükleniyor"
save: "Kaydet"
users: "Kullanıcı"
addUser: "Kullanıcı Ekle"
favorite: "Favoriler"
@ -31,6 +36,7 @@ favorites: "Favoriler"
unfavorite: "Favorilerden Kaldır"
favorited: "Favorilerime eklendi."
alreadyFavorited: "Zaten favorilerinizde kayıtlı."
cantFavorite: "Favorilere kayıt yapılamadı"
pin: "Sabitlenmiş"
unpin: "Sabitlemeyi kaldır"
copyContent: "İçeriği kopyala"
@ -40,23 +46,88 @@ deleteAndEdit: "Sil ve yeniden düzenle"
deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek istiyor musunuz? Bu nota ilişkin tüm Tepkiler, Yeniden Notlar ve Yanıtlar da silinecektir."
addToList: "Listeye ekle"
sendMessage: "Mesaj Gönder"
copyRSS: "RSSKopyala"
copyUsername: "Kullanıcı Adını Kopyala"
copyUserId: "KullanıcıyıKopyala"
copyNoteId: "Kimlik notunu kopyala"
searchUser: "Kullanıcıları ara"
reply: "yanıt"
loadMore: "Devamını yükle"
showMore: "Devamını yükle"
lists: "Listeler"
noLists: "Liste yok"
note: "not"
notes: "notlar"
following: "takipçi"
followers: "takipçi"
followsYou: "seni takip ediyor"
createList: "Liste oluştur"
manageLists: "Yönetici Listeleri"
error: "hata"
follow: "takipçi"
followRequest: "Takip isteği"
followRequests: "Takip istekleri"
unfollow: "takip etmeyi bırak"
followRequestPending: "Bekleyen Takip Etme Talebi"
enterEmoji: "Emoji Giriniz"
renote: "vazgeçme"
unrenote: "not alma"
renoted: "yeniden adlandırılmış"
cantRenote: "Ayrılamama"
cantReRenote: "not alabilirmiyim"
quote: "alıntı"
pinnedNote: "Sabitlenen"
pinned: "Sabitlenmiş"
you: "sen"
unmute: "sesi aç"
renoteMute: "sesi kapat"
renoteUnmute: "sesi açmayı iptal et"
block: "engelle"
unblock: "engellemeyi kaldır"
suspend: "askıya al"
unsuspend: "askıya alma"
blockConfirm: "Onayı engelle"
unblockConfirm: "engellemeyi kaldır onayla"
selectChannel: "Kanal seç"
flagAsBot: "Bot olarak işaretle"
instances: "Sunucu"
remove: "Sil"
pinnedNotes: "Sabitlenen"
userList: "Listeler"
smtpUser: "Kullanıcı Adı"
smtpPass: "Şifre"
user: "Kullanıcı"
searchByGoogle: "Arama"
_theme:
keys:
renote: "vazgeçme"
_sfx:
note: "notlar"
notification: "Bildirim"
_widgets:
profile: "Profil"
notifications: "Bildirim"
timeline: "Zaman çizelgesi"
_cw:
show: "Devamını yükle"
_visibility:
followers: "takipçi"
_profile:
username: "Kullanıcı Adı"
_exportOrImport:
followingList: "takipçi"
blockingList: "engelle"
userLists: "Listeler"
_notification:
_types:
follow: "takipçi"
renote: "vazgeçme"
quote: "alıntı"
_actions:
reply: "yanıt"
renote: "vazgeçme"
_deck:
_columns:
notifications: "Bildirim"
tl: "Zaman çizelgesi"
list: "Listeler"

View file

@ -1578,6 +1578,7 @@ _instanceCharts:
_timelines:
home: "Домівка"
local: "Локальна"
media: "Медіафайли"
social: "Соціальна"
global: "Глобальна"
_play:

View file

@ -1632,6 +1632,7 @@ _instanceCharts:
_timelines:
home: "Trang chính"
local: "Máy chủ này"
media: "Phương tiện"
social: "Xã hội"
global: "Liên hợp"
_play:

View file

@ -1074,6 +1074,7 @@ cancelReactionConfirm: "要取消回应吗?"
changeReactionConfirm: "要更改回应吗?"
later: "一会再说"
goToMisskey: "去往CherryPick"
additionalEmojiDictionary: "表情符号追加字典"
installed: "已安装"
_initialAccountSetting:
accountCreated: "账户创建完成了!"
@ -1926,6 +1927,7 @@ _instanceCharts:
_timelines:
home: "首页"
local: "本地"
media: "媒体"
social: "社交"
global: "全局"
_play:

View file

@ -1076,6 +1076,7 @@ later: "稍後再說"
goToMisskey: "往CherryPick"
additionalEmojiDictionary: "表情符號的附加辭典"
installed: "已安裝"
branding: "品牌宣傳"
_initialAccountSetting:
accountCreated: "帳戶已建立完成!"
letsStartAccountSetup: "來進行帳戶的初始設定吧。"
@ -1927,6 +1928,7 @@ _instanceCharts:
_timelines:
home: "首頁"
local: "本地"
media: "媒體"
social: "社交"
global: "公開"
_play:

View file

@ -1,6 +1,6 @@
{
"name": "cherrypick",
"version": "13.13.1-cp-4.0.0",
"version": "13.13.2-cp-4.1.0",
"codename": "nasubi",
"repository": {
"type": "git",
@ -51,8 +51,7 @@
"gulp-replace": "1.1.4",
"gulp-terser": "2.1.0",
"js-yaml": "4.1.0",
"typescript": "5.1.3",
"mitt": "3.0.0"
"typescript": "5.1.3"
},
"devDependencies": {
"@types/gulp": "4.0.10",

View file

@ -1,2 +1,2 @@
# Misskey Backend
# CherryPick Backend
![](../../assets/backend.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View file

@ -0,0 +1,91 @@
export class Event1681429921400 {
name = 'Event1681429921400'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_2cd3b2a6b4cf0b910b260afe08"`);
await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_createdAt"`);
await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muteeId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muterId"`);
await queryRunner.query(`CREATE TABLE "event" ("id" character varying(32) NOT NULL, "start" TIMESTAMP WITH TIME ZONE NOT NULL, "end" TIMESTAMP WITH TIME ZONE, "title" character varying(128) NOT NULL, "metadata" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_30c2f3bbaf6d34a55f8ae6e4614" PRIMARY KEY ("id")); COMMENT ON COLUMN "event"."start" IS 'The start time of the event'; COMMENT ON COLUMN "event"."end" IS 'The end of the event'; COMMENT ON COLUMN "event"."title" IS 'short name of event'; COMMENT ON COLUMN "event"."metadata" IS 'metadata mapping for event with more user configurable optional information'`);
await queryRunner.query(`CREATE INDEX "IDX_785ee5fc1ea38a1b9b38ff88e5" ON "event" ("start") `);
await queryRunner.query(`ALTER TABLE "note" ADD "eventId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "UQ_3af9380f266b7046cce9c992197" UNIQUE ("eventId")`);
await queryRunner.query(`COMMENT ON COLUMN "note"."eventId" IS 'The ID of child event'`);
await queryRunner.query(`COMMENT ON COLUMN "user"."isRoot" IS 'Whether the User is the root.'`);
await queryRunner.query(`COMMENT ON COLUMN "ad"."startsAt" IS 'The expired date of the Ad.'`);
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "lastUsedAt" DROP DEFAULT`);
await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS 'The created date of the Muting.'`);
await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS 'The mutee user ID.'`);
await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS 'The muter user ID.'`);
await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`);
await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b"`);
await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`);
await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c"`);
await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`);
await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6"`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9"`);
await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`);
await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "UQ_10c146e4b39b443ede016f6736d"`);
await queryRunner.query(`CREATE INDEX "IDX_3fcc2c589eaefc205e0714b99c" ON "ad" ("startsAt") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c71faf11f0a28a5c0bb506203c" ON "channel_favorite" ("userId", "channelId") `);
await queryRunner.query(`CREATE INDEX "IDX_3af9380f266b7046cce9c99219" ON "note" ("eventId") `);
await queryRunner.query(`CREATE INDEX "IDX_f7b9d338207e40e768e4a5265a" ON "instance" ("firstRetrievedAt") `);
await queryRunner.query(`CREATE INDEX "IDX_d1259a2c2b7bb413ff449e8711" ON "renote_muting" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_7eac97594bcac5ffcf2068089b" ON "renote_muting" ("muteeId") `);
await queryRunner.query(`CREATE INDEX "IDX_7aa72a5fe76019bfe8e5e0e8b7" ON "renote_muting" ("muterId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0d801c609cec4e9eb4b6b4490c" ON "renote_muting" ("muterId", "muteeId") `);
await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_3af9380f266b7046cce9c992197" FOREIGN KEY ("eventId") REFERENCES "event"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6" FOREIGN KEY ("muteeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d" FOREIGN KEY ("muterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "FK_10c146e4b39b443ede016f6736d"`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9"`);
await queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6"`);
await queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c"`);
await queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b"`);
await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d"`);
await queryRunner.query(`ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6"`);
await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_3af9380f266b7046cce9c992197"`);
await queryRunner.query(`DROP INDEX "public"."IDX_0d801c609cec4e9eb4b6b4490c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_7aa72a5fe76019bfe8e5e0e8b7"`);
await queryRunner.query(`DROP INDEX "public"."IDX_7eac97594bcac5ffcf2068089b"`);
await queryRunner.query(`DROP INDEX "public"."IDX_d1259a2c2b7bb413ff449e8711"`);
await queryRunner.query(`DROP INDEX "public"."IDX_f7b9d338207e40e768e4a5265a"`);
await queryRunner.query(`DROP INDEX "public"."IDX_3af9380f266b7046cce9c99219"`);
await queryRunner.query(`DROP INDEX "public"."IDX_c71faf11f0a28a5c0bb506203c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_3fcc2c589eaefc205e0714b99c"`);
await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`);
await queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "FK_10c146e4b39b443ede016f6736d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_51cb79b5555effaf7d69ba1cff9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`);
await queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "FK_f4853eb41ab722fe05f81cedeb6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`);
await queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "FK_e263909ca4fe5d57f8d4230dd5c" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`);
await queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "FK_da851e06d0dfe2ef397d8b1bf1b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muterId" IS NULL`);
await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."muteeId" IS NULL`);
await queryRunner.query(`COMMENT ON COLUMN "renote_muting"."createdAt" IS NULL`);
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "lastUsedAt" SET DEFAULT '2023-04-13 18:46:24.168209-04'`);
await queryRunner.query(`COMMENT ON COLUMN "ad"."startsAt" IS NULL`);
await queryRunner.query(`COMMENT ON COLUMN "user"."isRoot" IS 'Whether the User is the admin.'`);
await queryRunner.query(`COMMENT ON COLUMN "note"."eventId" IS 'The ID of child event'`);
await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "UQ_3af9380f266b7046cce9c992197"`);
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "eventId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_785ee5fc1ea38a1b9b38ff88e5"`);
await queryRunner.query(`DROP TABLE "event"`);
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `);
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `);
await queryRunner.query(`CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `);
await queryRunner.query(`CREATE INDEX "IDX_2cd3b2a6b4cf0b910b260afe08" ON "instance" ("firstRetrievedAt") `);
}
}

View file

@ -0,0 +1,29 @@
export class Event1681673280586 {
name = 'Event1681673280586'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "FK_3af9380f266b7046cce9c992197"`);
await queryRunner.query(`DROP INDEX "public"."IDX_3af9380f266b7046cce9c99219"`);
await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "eventId" TO "isEvent"`);
await queryRunner.query(`ALTER TABLE "note" RENAME CONSTRAINT "UQ_3af9380f266b7046cce9c992197" TO "UQ_16484b50d1ee91555d4b8821ac3"`);
await queryRunner.query(`ALTER TABLE "event" RENAME COLUMN "id" TO "noteId"`);
await queryRunner.query(`ALTER TABLE "event" RENAME CONSTRAINT "PK_30c2f3bbaf6d34a55f8ae6e4614" TO "PK_2b481f231cd035e84390072bf7b"`);
await queryRunner.query(`ALTER TABLE "note" DROP CONSTRAINT "UQ_16484b50d1ee91555d4b8821ac3"`);
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "isEvent"`);
await queryRunner.query(`ALTER TABLE "note" ADD "isEvent" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "event" ADD CONSTRAINT "FK_2b481f231cd035e84390072bf7b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "event" DROP CONSTRAINT "FK_2b481f231cd035e84390072bf7b"`);
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "isEvent"`);
await queryRunner.query(`ALTER TABLE "note" ADD "isEvent" character varying(32)`);
await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "UQ_16484b50d1ee91555d4b8821ac3" UNIQUE ("isEvent")`);
await queryRunner.query(`ALTER TABLE "event" RENAME CONSTRAINT "PK_2b481f231cd035e84390072bf7b" TO "PK_30c2f3bbaf6d34a55f8ae6e4614"`);
await queryRunner.query(`ALTER TABLE "event" RENAME COLUMN "noteId" TO "id"`);
await queryRunner.query(`ALTER TABLE "note" RENAME CONSTRAINT "UQ_16484b50d1ee91555d4b8821ac3" TO "UQ_3af9380f266b7046cce9c992197"`);
await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "isEvent" TO "eventId"`);
await queryRunner.query(`CREATE INDEX "IDX_3af9380f266b7046cce9c99219" ON "note" ("eventId") `);
await queryRunner.query(`ALTER TABLE "note" ADD CONSTRAINT "FK_3af9380f266b7046cce9c992197" FOREIGN KEY ("eventId") REFERENCES "event"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
}

View file

@ -0,0 +1,29 @@
export class Event1681675881633 {
name = 'Event1681675881633'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "isEvent" TO "hasEvent"`);
await queryRunner.query(`CREATE TYPE "public"."event_notevisibility_enum" AS ENUM('public', 'home', 'followers', 'specified')`);
await queryRunner.query(`ALTER TABLE "event" ADD "noteVisibility" "public"."event_notevisibility_enum" NOT NULL`);
await queryRunner.query(`COMMENT ON COLUMN "event"."noteVisibility" IS '[Denormalized]'`);
await queryRunner.query(`ALTER TABLE "event" ADD "userId" character varying(32) NOT NULL`);
await queryRunner.query(`COMMENT ON COLUMN "event"."userId" IS '[Denormalized]'`);
await queryRunner.query(`ALTER TABLE "event" ADD "userHost" character varying(128)`);
await queryRunner.query(`COMMENT ON COLUMN "event"."userHost" IS '[Denormalized]'`);
await queryRunner.query(`CREATE INDEX "IDX_01cd2b829e0263917bf570cb67" ON "event" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_f6ba57dff679ccbcfe004698ec" ON "event" ("userHost") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_f6ba57dff679ccbcfe004698ec"`);
await queryRunner.query(`DROP INDEX "public"."IDX_01cd2b829e0263917bf570cb67"`);
await queryRunner.query(`COMMENT ON COLUMN "event"."userHost" IS '[Denormalized]'`);
await queryRunner.query(`ALTER TABLE "event" DROP COLUMN "userHost"`);
await queryRunner.query(`COMMENT ON COLUMN "event"."userId" IS '[Denormalized]'`);
await queryRunner.query(`ALTER TABLE "event" DROP COLUMN "userId"`);
await queryRunner.query(`COMMENT ON COLUMN "event"."noteVisibility" IS '[Denormalized]'`);
await queryRunner.query(`ALTER TABLE "event" DROP COLUMN "noteVisibility"`);
await queryRunner.query(`DROP TYPE "public"."event_notevisibility_enum"`);
await queryRunner.query(`ALTER TABLE "note" RENAME COLUMN "hasEvent" TO "isEvent"`);
}
}

View file

@ -0,0 +1,17 @@
export class ErrorImageUrl1685973839966 {
name = 'ErrorImageUrl1685973839966'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "errorImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" ADD "serverErrorImageUrl" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "notFoundImageUrl" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "infoImageUrl" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "infoImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "notFoundImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "serverErrorImageUrl"`);
await queryRunner.query(`ALTER TABLE "meta" ADD "errorImageUrl" character varying(1024) DEFAULT 'https://xn--931a.moe/aiart/yubitun.png'`);
}
}

View file

@ -65,6 +65,7 @@
"@fastify/multipart": "7.6.0",
"@fastify/static": "6.10.2",
"@fastify/view": "7.4.1",
"@google-cloud/logging": "^10.5.0",
"@google-cloud/translate": "^7.2.1",
"@nestjs/common": "9.4.2",
"@nestjs/core": "9.4.2",
@ -85,6 +86,8 @@
"cbor": "9.0.0",
"chalk": "5.2.0",
"chalk-template": "0.4.0",
"cherrypick-js": "workspace:*",
"cherrypick-mfm-js": "github:kokonect-link/mfm.js",
"chokidar": "3.5.3",
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
@ -109,9 +112,7 @@
"jsonld": "8.2.0",
"jsrsasign": "10.8.6",
"meilisearch": "0.32.5",
"mfm-js": "0.23.3",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"ms": "3.0.0-canary.1",
"nested-property": "4.0.0",
"node-fetch": "3.3.1",
@ -147,6 +148,7 @@
"slacc": "0.0.9",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"strip-ansi": "^7.1.0",
"summaly": "github:misskey-dev/summaly",
"systeminformation": "5.17.16",
"tinycolor2": "1.6.0",

View file

@ -90,6 +90,12 @@ export type Source = {
deliverJobMaxAttempts?: number;
inboxJobMaxAttempts?: number;
cloudLogging?: {
projectId: string;
saKeyPath: string;
logName?: string;
}
mediaProxy?: string;
proxyRemoteFiles?: boolean;
videoThumbnailGenerator?: string;
@ -98,7 +104,7 @@ export type Source = {
};
/**
* Misskeyが自動的に()
* CherryPickが自動的に()
*/
export type Mixin = {
version: string;
@ -133,8 +139,8 @@ const dir = `${_dirname}/../../../.config`;
/**
* Path of configuration file
*/
const path = process.env.MISSKEY_CONFIG_YML
? resolve(dir, process.env.MISSKEY_CONFIG_YML)
const path = process.env.CHERRYPICK_CONFIG_YML
? resolve(dir, process.env.CHERRYPICK_CONFIG_YML)
: process.env.NODE_ENV === 'test'
? resolve(dir, 'test.yml')
: resolve(dir, 'default.yml');

View file

@ -7,6 +7,7 @@ import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { StreamMessages } from '@/server/api/stream/types.js';
import type { FlashToken } from '@/misc/flash-token';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
@ -16,6 +17,7 @@ export class CacheService implements OnApplicationShutdown {
public localUserByIdCache: MemoryKVCache<LocalUser>;
public uriPersonCache: MemoryKVCache<User | null>;
public userProfileCache: RedisKVCache<UserProfile>;
public flashAccessTokensCache: RedisKVCache<FlashToken | null>;
public userMutingsCache: RedisKVCache<Set<string>>;
public userBlockingCache: RedisKVCache<Set<string>>;
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
@ -116,6 +118,13 @@ export class CacheService implements OnApplicationShutdown {
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
this.flashAccessTokensCache = new RedisKVCache<FlashToken | null>(this.redisClient, 'flashAccessTokens', {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60, // 1m
fetcher: async (key) => null,
toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => JSON.parse(value),
});
this.redisForSub.on('message', this.onMessage);
}
@ -168,6 +177,17 @@ export class CacheService implements OnApplicationShutdown {
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
this.userByIdCache.dispose();
this.localUserByNativeTokenCache.dispose();
this.localUserByIdCache.dispose();
this.uriPersonCache.dispose();
this.userProfileCache.dispose();
this.userMutingsCache.dispose();
this.userBlockingCache.dispose();
this.userBlockedCache.dispose();
this.renoteMutingsCache.dispose();
this.userFollowingsCache.dispose();
this.userFollowingChannelsCache.dispose();
}
@bindThis

View file

@ -118,6 +118,7 @@ import { ApMentionService } from './activitypub/models/ApMentionService.js';
import { ApNoteService } from './activitypub/models/ApNoteService.js';
import { ApPersonService } from './activitypub/models/ApPersonService.js';
import { ApQuestionService } from './activitypub/models/ApQuestionService.js';
import { ApEventService } from './activitypub/models/ApEventService.js';
import { QueueModule } from './QueueModule.js';
import { QueueService } from './QueueService.js';
import { LoggerService } from './LoggerService.js';
@ -247,6 +248,7 @@ const $ApMentionService: Provider = { provide: 'ApMentionService', useExisting:
const $ApNoteService: Provider = { provide: 'ApNoteService', useExisting: ApNoteService };
const $ApPersonService: Provider = { provide: 'ApPersonService', useExisting: ApPersonService };
const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting: ApQuestionService };
const $ApEventService: Provider = { provide: 'ApEventService', useExisting: ApEventService };
//#endregion
@Module({
@ -374,6 +376,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ApNoteService,
ApPersonService,
ApQuestionService,
ApEventService,
QueueService,
//#region 文字列ベースでのinjection用(循環参照対応のため)
@ -497,6 +500,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ApNoteService,
$ApPersonService,
$ApQuestionService,
$ApEventService,
//#endregion
],
exports: [
@ -620,6 +624,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ApNoteService,
ApPersonService,
ApQuestionService,
ApEventService,
QueueService,
//#region 文字列ベースでのinjection用(循環参照対応のため)
@ -742,6 +747,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ApNoteService,
$ApPersonService,
$ApQuestionService,
$ApEventService,
//#endregion
],
})

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DataSource, In, IsNull } from 'typeorm';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
@ -18,7 +18,7 @@ import type { Serialized } from '@/server/api/stream/types.js';
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
@Injectable()
export class CustomEmojiService {
export class CustomEmojiService implements OnApplicationShutdown {
private cache: MemoryKVCache<Emoji | null>;
public localEmojisCache: RedisSingleCache<Map<string, Emoji>>;
@ -352,4 +352,14 @@ export class CustomEmojiService {
this.cache.set(`${emoji.name} ${emoji.host}`, emoji);
}
}
@bindThis
public dispose(): void {
this.cache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { InstancesRepository } from '@/models/index.js';
import type { Instance } from '@/models/entities/Instance.js';
@ -9,7 +9,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
@Injectable()
export class FederatedInstanceService {
export class FederatedInstanceService implements OnApplicationShutdown {
public federatedInstanceCache: RedisKVCache<Instance | null>;
constructor(
@ -77,4 +77,14 @@ export class FederatedInstanceService {
this.federatedInstanceCache.set(result.host, result);
}
@bindThis
public dispose(): void {
this.federatedInstanceCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -7,14 +7,18 @@ import type { KEYWORD } from 'color-convert/conversions';
@Injectable()
export class LoggerService {
private cloudLogging;
constructor(
@Inject(DI.config)
private config: Config,
) {
if (this.config.cloudLogging) {
this.cloudLogging = this.config.cloudLogging;
}
}
@bindThis
public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) {
return new Logger(domain, color, store);
return new Logger(domain, color, store, this.cloudLogging);
}
}

View file

@ -8,7 +8,7 @@ import { intersperse } from '@/misc/prelude/array.js';
import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
import { bindThis } from '@/decorators.js';
import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js';
import type * as mfm from 'mfm-js';
import type * as mfm from 'cherrypick-mfm-js';
const treeAdapter = TreeAdapter.defaultTreeAdapter;

View file

@ -1,5 +1,5 @@
import { setImmediate } from 'node:timers/promises';
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
import { In, DataSource } from 'typeorm';
import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
@ -9,6 +9,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
import { Note } from '@/models/entities/Note.js';
import { Event, IEvent } from '@/models/entities/Event.js';
import type { ChannelFollowingsRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { App } from '@/models/entities/App.js';
@ -128,6 +129,7 @@ type Option = {
renote?: Note | null;
files?: DriveFile[] | null;
poll?: IPoll | null;
event?: IEvent | null;
localOnly?: boolean | null;
reactionAcceptance?: Note['reactionAcceptance'];
disableRightClick?: boolean | null;
@ -364,6 +366,7 @@ export class NoteCreateService implements OnApplicationShutdown {
name: data.name,
text: data.text,
hasPoll: data.poll != null,
hasEvent: data.event != null,
cw: data.cw == null ? null : data.cw,
tags: tags.map(tag => normalizeForSearch(tag)),
emojis,
@ -409,23 +412,40 @@ export class NoteCreateService implements OnApplicationShutdown {
// 投稿を作成
try {
if (insert.hasPoll) {
if (insert.hasPoll || insert.hasEvent) {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
await transactionalEntityManager.insert(Note, insert);
const poll = new Poll({
noteId: insert.id,
choices: data.poll!.choices,
expiresAt: data.poll!.expiresAt,
multiple: data.poll!.multiple,
votes: new Array(data.poll!.choices.length).fill(0),
noteVisibility: insert.visibility,
userId: user.id,
userHost: user.host,
});
if (insert.hasPoll) {
const poll = new Poll({
noteId: insert.id,
choices: data.poll!.choices,
expiresAt: data.poll!.expiresAt,
multiple: data.poll!.multiple,
votes: new Array(data.poll!.choices.length).fill(0),
noteVisibility: insert.visibility,
userId: user.id,
userHost: user.host,
});
await transactionalEntityManager.insert(Poll, poll);
await transactionalEntityManager.insert(Poll, poll);
}
if (insert.hasEvent) {
const event = new Event({
noteId: insert.id,
start: data.event!.start,
end: data.event!.end ?? undefined,
title: data.event!.title,
metadata: data.event!.metadata,
noteVisibility: insert.visibility,
userId: user.id,
userHost: user.host,
});
await transactionalEntityManager.insert(Event, event);
}
});
} else {
await this.notesRepository.insert(insert);

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import push from 'web-push';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
@ -45,7 +45,7 @@ function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: Pus
}
@Injectable()
export class PushNotificationService {
export class PushNotificationService implements OnApplicationShutdown {
private subscriptionsCache: RedisKVCache<SwSubscription[]>;
constructor(
@ -120,4 +120,14 @@ export class PushNotificationService {
});
}
}
@bindThis
public dispose(): void {
this.subscriptionsCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -435,6 +435,7 @@ export class RoleService implements OnApplicationShutdown {
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
this.roleAssignmentByUserIdCache.dispose();
}
@bindThis

View file

@ -120,6 +120,7 @@ export class SearchService {
userId?: Note['userId'] | null;
channelId?: Note['channelId'] | null;
host?: string | null;
origin?: string | null;
}, pagination: {
untilId?: Note['id'];
sinceId?: Note['id'];
@ -156,6 +157,12 @@ export class SearchService {
} else {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
if (opts.origin === 'local') {
query.andWhere('note.userHost IS NULL');
} else if (opts.origin === 'remote') {
query.andWhere('note.userHost IS NOT NULL');
}
if (opts.userId) {
query.andWhere('note.userId = :userId', { userId: opts.userId });
} else if (opts.channelId) {

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { User } from '@/models/entities/User.js';
import type { UserKeypairsRepository } from '@/models/index.js';
@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@Injectable()
export class UserKeypairService {
export class UserKeypairService implements OnApplicationShutdown {
private cache: RedisKVCache<UserKeypair>;
constructor(
@ -31,4 +31,14 @@ export class UserKeypairService {
public async getUserKeypair(userId: User['id']): Promise<UserKeypair> {
return await this.cache.fetch(userId);
}
@bindThis
public dispose(): void {
this.cache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -46,7 +46,7 @@ export class WebfingerService {
const m = query.match(mRegex);
if (m) {
const hostname = m[2];
const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true';
const useHttp = process.env.CHERRYPICK_WEBFINGER_USE_HTTP && process.env.CHERRYPICK_WEBFINGER_USE_HTTP.toLowerCase() === 'true';
return `http${useHttp ? '' : 's'}://${hostname}/.well-known/webfinger?${urlQuery({ resource: `acct:${query}` })}`;
}

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import escapeRegexp from 'escape-regexp';
import { DI } from '@/di-symbols.js';
import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
@ -31,7 +31,7 @@ export type UriParseResult = {
};
@Injectable()
export class ApDbResolverService {
export class ApDbResolverService implements OnApplicationShutdown {
private publicKeyCache: MemoryKVCache<UserPublickey | null>;
private publicKeyByUserIdCache: MemoryKVCache<UserPublickey | null>;
@ -183,4 +183,15 @@ export class ApDbResolverService {
key,
};
}
@bindThis
public dispose(): void {
this.publicKeyCache.dispose();
this.publicKeyByUserIdCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common';
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { MfmService } from '@/core/MfmService.js';

View file

@ -2,7 +2,7 @@ import { createPublicKey } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import { In, IsNull } from 'typeorm';
import { v4 as uuid } from 'uuid';
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { PartialLocalUser, LocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
@ -20,7 +20,7 @@ import { MfmService } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { UserKeypair } from '@/models/entities/UserKeypair.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository, EventsRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { isNotNull } from '@/misc/is-not-null.js';
@ -53,6 +53,9 @@ export class ApRendererService {
@Inject(DI.pollsRepository)
private pollsRepository: PollsRepository,
@Inject(DI.eventsRepository)
private eventsRepository: EventsRepository,
private customEmojiService: CustomEmojiService,
private userEntityService: UserEntityService,
private driveFileEntityService: DriveFileEntityService,
@ -429,6 +432,18 @@ export class ApRendererService {
_misskey_talk: true,
} as const : {};
let asEvent = {};
if (note.hasEvent) {
const event = await this.eventsRepository.findOneBy({ noteId: note.id });
asEvent = event ? {
type: 'Event',
name: event.title,
startTime: event.start,
endTime: event.end,
...event.metadata,
} as const : {};
}
return {
id: `${this.config.url}/notes/${note.id}`,
type: 'Note',
@ -449,6 +464,7 @@ export class ApRendererService {
attachment: files.map(x => this.renderDocument(x)),
sensitive: note.cw != null || files.some(file => file.isSensitive),
tag,
...asEvent,
...asPoll,
...asTalk,
};

View file

@ -0,0 +1,67 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { EventsRepository, NotesRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { IEvent } from '@/models/entities/Event.js';
import { isEvent } from '../type.js';
import { ApLoggerService } from '../ApLoggerService.js';
import { ApResolverService } from '../ApResolverService.js';
import type { Resolver } from '../ApResolverService.js';
import type { IObject } from '../type.js';
@Injectable()
export class ApEventService {
private logger: Logger;
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.eventsRepository)
private eventsRepository: EventsRepository,
private apResolverService: ApResolverService,
private apLoggerService: ApLoggerService,
) {
this.logger = this.apLoggerService.logger;
}
@bindThis
public async extractEventFromNote(source: string | IObject, resolverParam?: Resolver): Promise<IEvent> {
const resolver = resolverParam ?? this.apResolverService.createResolver();
const note = await resolver.resolve(source);
if (!isEvent(note)) {
throw new Error('invalid type');
}
if (note.name && note.startTime) {
const title = note.name;
const start = note.startTime;
const end = note.endTime ?? null;
return {
title,
start,
end,
metadata: {
'@type': 'Event',
name: note.name,
url: note.href,
startDate: note.startTime.toISOString(),
endDate: note.endTime?.toISOString(),
description: note.summary,
identifier: note.id,
},
};
} else {
throw new Error('Invalid event properties');
}
}
}

View file

@ -31,6 +31,7 @@ import { ApPersonService } from './ApPersonService.js';
import { extractApHashtags } from './tag.js';
import { ApMentionService } from './ApMentionService.js';
import { ApQuestionService } from './ApQuestionService.js';
import { ApEventService } from './ApEventService.js';
import { ApImageService } from './ApImageService.js';
import type { Resolver } from '../ApResolverService.js';
import type { IObject, IPost } from '../type.js';
@ -65,6 +66,7 @@ export class ApNoteService {
private apMentionService: ApMentionService,
private apImageService: ApImageService,
private apQuestionService: ApQuestionService,
private apEventService: ApEventService,
private metaService: MetaService,
private messagingService: MessagingService,
private appLockService: AppLockService,
@ -295,6 +297,7 @@ export class ApNoteService {
const apEmojis = emojis.map(emoji => emoji.name);
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
const event = await this.apEventService.extractEventFromNote(note, resolver).catch(() => undefined);
if (isMessaging) {
for (const recipient of visibleUsers) {
@ -319,6 +322,7 @@ export class ApNoteService {
apHashtags,
apEmojis,
poll,
event,
uri: note.id,
url: url,
}, silent);

View file

@ -134,6 +134,9 @@ export interface IQuestion extends IObject {
export const isQuestion = (object: IObject): object is IQuestion =>
getApType(object) === 'Note' || getApType(object) === 'Question';
export const isEvent = (object: IObject): object is IObject =>
getApType(object) === 'Note' || getApType(object) === 'Event';
interface IQuestionChoice {
name?: string;
replies?: ICollection;

View file

@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { DataSource, In } from 'typeorm';
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js';
import type { Packed } from '@/misc/json-schema.js';
@ -9,7 +9,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
import type { User } from '@/models/entities/User.js';
import type { Note } from '@/models/entities/Note.js';
import type { NoteReaction } from '@/models/entities/NoteReaction.js';
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository } from '@/models/index.js';
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository, EventsRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { isNotNull } from '@/misc/is-not-null.js';
import type { OnModuleInit } from '@nestjs/common';
@ -43,6 +43,9 @@ export class NoteEntityService implements OnModuleInit {
@Inject(DI.pollsRepository)
private pollsRepository: PollsRepository,
@Inject(DI.eventsRepository)
private eventsRepository: EventsRepository,
@Inject(DI.pollVotesRepository)
private pollVotesRepository: PollVotesRepository,
@ -169,6 +172,17 @@ export class NoteEntityService implements OnModuleInit {
};
}
@bindThis
private async populateEvent(note: Note) {
const event = await this.eventsRepository.findOneByOrFail({ noteId: note.id });
return {
title: event.title,
start: event.start,
end: event.end,
metadata: event.metadata,
};
}
@bindThis
private async populateMyReaction(note: Note, meId: User['id'], _hint_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
@ -354,6 +368,7 @@ export class NoteEntityService implements OnModuleInit {
}) : undefined,
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
event: note.hasEvent ? this.populateEvent(note) : undefined,
...(meId ? {
myReaction: this.populateMyReaction(note, meId, options?._hint_),

View file

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { AccessTokensRepository, FollowRequestsRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js';
import type { AccessTokensRepository, FollowRequestsRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository, UserGroupInvitationsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Notification } from '@/models/entities/Notification.js';
import type { Note } from '@/models/entities/Note.js';
@ -43,6 +43,9 @@ export class NotificationEntityService implements OnModuleInit {
@Inject(DI.accessTokensRepository)
private accessTokensRepository: AccessTokensRepository,
@Inject(DI.userGroupInvitationsRepository)
private userGroupInvitationsRepository: UserGroupInvitationsRepository,
//private userEntityService: UserEntityService,
//private noteEntityService: NoteEntityService,
//private userGroupInvitationEntityService: UserGroupInvitationEntityService,
@ -156,6 +159,14 @@ export class NotificationEntityService implements OnModuleInit {
validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
}
const groupInvitedNotifications = validNotifications.filter(x => x.type === 'groupInvited');
if (groupInvitedNotifications.length > 0) {
const existingInvitationIds = await this.userGroupInvitationsRepository.find({
where: { id: In(groupInvitedNotifications.map(x => x.userGroupInvitationId!)) },
});
validNotifications = validNotifications.filter(x => (x.type !== 'groupInvited') || existingInvitationIds.some(r => r.id === x.userGroupInvitationId));
}
return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, {
packedNotes,
packedUsers,

View file

@ -37,6 +37,7 @@ export const DI = {
followRequestsRepository: Symbol('followRequestsRepository'),
instancesRepository: Symbol('instancesRepository'),
emojisRepository: Symbol('emojisRepository'),
eventsRepository: Symbol('eventsRepository'),
driveFilesRepository: Symbol('driveFilesRepository'),
driveFoldersRepository: Symbol('driveFoldersRepository'),
metasRepository: Symbol('metasRepository'),

View file

@ -1,7 +1,10 @@
import cluster from 'node:cluster';
import util from 'util';
import chalk from 'chalk';
import { default as convertColor } from 'color-convert';
import { format as dateFormat } from 'date-fns';
import { Logging } from '@google-cloud/logging';
import stripAnsi from 'strip-ansi';
import { bindThis } from '@/decorators.js';
import { envOption } from './env.js';
import type { KEYWORD } from 'color-convert/conversions';
@ -12,18 +15,21 @@ type Context = {
};
type Level = 'error' | 'success' | 'warning' | 'debug' | 'info';
type CloudLogging = any | undefined;
export default class Logger {
private context: Context;
private parentLogger: Logger | null = null;
private store: boolean;
private clConfig?: CloudLogging;
constructor(context: string, color?: KEYWORD, store = true) {
constructor(context: string, color?: KEYWORD, store = true, clConfig?: CloudLogging) {
this.context = {
name: context,
color: color,
};
this.store = store;
this.clConfig = clConfig;
}
@bindThis
@ -44,7 +50,8 @@ export default class Logger {
return;
}
const time = dateFormat(new Date(), 'HH:mm:ss');
const timestamp = new Date();
const time = dateFormat(timestamp, 'HH:mm:ss');
const worker = cluster.isPrimary ? '*' : cluster.worker!.id;
const l =
level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') :
@ -66,7 +73,43 @@ export default class Logger {
if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log;
console.log(important ? chalk.bold(log) : log);
if (level === 'error' && data) console.log(data);
if (level === 'error' && data) {
console.log(data);
this.writeCloudLogging(level, log, timestamp, data);
} else {
this.writeCloudLogging(level, log, timestamp, null);
}
}
private async writeCloudLogging(level: Level, message: string, time: Date, data?: Record<string, any> | null) {
if (!this.clConfig) return;
if (!this.clConfig.projectId || !this.clConfig.saKeyPath) return;
let lv = level;
if (level === 'success') lv = 'info';
const projectId = this.clConfig.projectId;
const logging = new Logging({ projectId: projectId, keyFilename: this.clConfig.saKeyPath });
const logName = this.clConfig.logName ?? 'misskey';
const log = logging.log(logName);
const logMessage = stripAnsi(message);
const metadata = {
severity: lv.toUpperCase(),
resource: {
type: 'global',
timestamp: time,
},
labels: {
name: `${this.context.name}`,
color: `${this.context.color}`,
},
};
const dataString = data ? '\n' + util.inspect(data, { depth: null }) : '';
const entry = log.entry(metadata, logMessage + dataString);
await log.write(entry);
}
@bindThis

View file

@ -83,6 +83,16 @@ export class RedisKVCache<T> {
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
}
@bindThis
public gc() {
this.memoryCache.gc();
}
@bindThis
public dispose() {
this.memoryCache.dispose();
}
}
export class RedisSingleCache<T> {
@ -174,10 +184,15 @@ export class RedisSingleCache<T> {
export class MemoryKVCache<T> {
public cache: Map<string, { date: number; value: T; }>;
private lifetime: number;
private gcIntervalHandle: NodeJS.Timer;
constructor(lifetime: MemoryKVCache<never>['lifetime']) {
this.cache = new Map();
this.lifetime = lifetime;
this.gcIntervalHandle = setInterval(() => {
this.gc();
}, 1000 * 60 * 3);
}
@bindThis
@ -200,7 +215,7 @@ export class MemoryKVCache<T> {
}
@bindThis
public delete(key: string) {
public delete(key: string): void {
this.cache.delete(key);
}
@ -255,6 +270,21 @@ export class MemoryKVCache<T> {
}
return value;
}
@bindThis
public gc(): void {
const now = Date.now();
for (const [key, { date }] of this.cache.entries()) {
if ((now - date) > this.lifetime) {
this.cache.delete(key);
}
}
}
@bindThis
public dispose(): void {
clearInterval(this.gcIntervalHandle);
}
}
export class MemorySingleCache<T> {

View file

@ -1,4 +1,4 @@
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
import { unique } from '@/misc/prelude/array.js';
export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] {

View file

@ -1,4 +1,4 @@
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
import { unique } from '@/misc/prelude/array.js';
export function extractHashtags(nodes: mfm.MfmNode[]): string[] {

View file

@ -1,6 +1,6 @@
// test is located in test/extract-mentions
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] {
// TODO: 重複を削除

View file

@ -0,0 +1,6 @@
import type { LocalUser } from '@/models/entities/User.js';
export type FlashToken = {
permissions: string[];
user: LocalUser
};

View file

@ -1,3 +1,3 @@
# Prelude
このディレクトリのコードはJavaScriptの表現能力を補うためのコードです。
Misskey固有の処理とは独立したコードの集まりですが、Misskeyのコードを読みやすくすることを目的としています。
CherryPick固有の処理とは独立したコードの集まりですが、CherryPickのコードを読みやすくすることを目的としています。

View file

@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js';
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, Event, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, MessagingMessage, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js';
import type { DataSource } from 'typeorm';
import type { Provider } from '@nestjs/common';
@ -184,6 +184,12 @@ const $emojisRepository: Provider = {
inject: [DI.db],
};
const $eventsRepository: Provider = {
provide: DI.eventsRepository,
useFactory: (db: DataSource) => db.getRepository(Event),
inject: [DI.db],
};
const $driveFilesRepository: Provider = {
provide: DI.driveFilesRepository,
useFactory: (db: DataSource) => db.getRepository(DriveFile),
@ -458,6 +464,7 @@ const $userMemosRepository: Provider = {
$followRequestsRepository,
$instancesRepository,
$emojisRepository,
$eventsRepository,
$driveFilesRepository,
$driveFoldersRepository,
$metasRepository,
@ -530,6 +537,7 @@ const $userMemosRepository: Provider = {
$followRequestsRepository,
$instancesRepository,
$emojisRepository,
$eventsRepository,
$driveFilesRepository,
$driveFoldersRepository,
$metasRepository,

View file

@ -0,0 +1,119 @@
import { Entity, Index, Column, PrimaryColumn, OneToOne, JoinColumn } from 'typeorm';
import { id } from '../id.js';
import { noteVisibilities } from '../../types.js';
import { Note } from './Note.js';
import type { User } from './User.js';
@Entity()
export class Event {
@PrimaryColumn(id())
public noteId: Note['id'];
@OneToOne(type => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
public note: Note | null;
@Index()
@Column('timestamp with time zone', {
comment: 'The start time of the event',
})
public start: Date;
@Column('timestamp with time zone', {
comment: 'The end of the event',
nullable: true,
})
public end: Date;
@Column({
type: 'varchar',
length: 128,
comment: 'short name of event',
})
public title: string;
@Column('jsonb', {
default: {
'@context': 'https://schema.org/',
'@type': 'Event',
},
comment: 'metadata object describing the event. Follows https://schema.org/Event',
})
public metadata: EventSchema;
//#region Denormalized fields
@Column('enum', {
enum: noteVisibilities,
comment: '[Denormalized]',
})
public noteVisibility: typeof noteVisibilities[number];
@Index()
@Column({
...id(),
comment: '[Denormalized]',
})
public userId: User['id'];
@Index()
@Column('varchar', {
length: 128, nullable: true,
comment: '[Denormalized]',
})
public userHost: string | null;
//#endregion
constructor(data: Partial<Event>) {
if (data == null) return;
for (const [k, v] of Object.entries(data)) {
(this as any)[k] = v;
}
}
}
export type EventSchema = {
'@type': 'Event';
name?: string;
url?: string;
description?: string;
audience?: {
'@type': 'Audience';
name: string;
};
doorTime?: string;
startDate?: string;
endDate?: string;
eventStatus?: 'https://schema.org/EventCancelled' | 'https://schema.org/EventMovedOnline' | 'https://schema.org/EventPostponed' | 'https://schema.org/EventRescheduled' | 'https://schema.org/EventScheduled';
inLanguage?: string;
isAccessibleForFree?: boolean;
keywords?: string;
location?: string;
offers?: {
'@type': 'Offer';
price?: string;
priceCurrency?: string;
availabilityStarts?: string;
availabilityEnds?: string;
url?: string;
};
organizer?: {
name: string;
sameAs?: string; // ie. URL to website/social
};
performer?: {
name: string;
sameAs?: string; // ie. URL to website/social
}[];
typicalAgeRange?: string;
identifier?: string;
}
export type IEvent = {
start: Date;
end: Date | null
title: string;
metadata: EventSchema;
}

View file

@ -101,13 +101,25 @@ export class Meta {
length: 1024,
nullable: true,
})
public errorImageUrl: string | null;
public iconUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public iconUrl: string | null;
public serverErrorImageUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public notFoundImageUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public infoImageUrl: string | null;
@Column('boolean', {
default: true,

View file

@ -53,6 +53,11 @@ export class Note {
})
public threadId: string | null;
@Column('boolean', {
default: false,
})
public hasEvent: boolean;
// TODO: varcharにしたい
@Column('text', {
nullable: true,

View file

@ -16,6 +16,7 @@ import { ClipFavorite } from '@/models/entities/ClipFavorite.js';
import { DriveFile } from '@/models/entities/DriveFile.js';
import { DriveFolder } from '@/models/entities/DriveFolder.js';
import { Emoji } from '@/models/entities/Emoji.js';
import { Event } from '@/models/entities/Event.js';
import { Following } from '@/models/entities/Following.js';
import { FollowRequest } from '@/models/entities/FollowRequest.js';
import { GalleryLike } from '@/models/entities/GalleryLike.js';
@ -89,6 +90,7 @@ export {
DriveFile,
DriveFolder,
Emoji,
Event,
Following,
FollowRequest,
GalleryLike,
@ -161,6 +163,7 @@ export type ClipFavoritesRepository = Repository<ClipFavorite>;
export type DriveFilesRepository = Repository<DriveFile>;
export type DriveFoldersRepository = Repository<DriveFolder>;
export type EmojisRepository = Repository<Emoji>;
export type EventsRepository = Repository<Event>;
export type FollowingsRepository = Repository<Following>;
export type FollowRequestsRepository = Repository<FollowRequest>;
export type GalleryLikesRepository = Repository<GalleryLike>;

View file

@ -24,6 +24,7 @@ import { ClipFavorite } from '@/models/entities/ClipFavorite.js';
import { DriveFile } from '@/models/entities/DriveFile.js';
import { DriveFolder } from '@/models/entities/DriveFolder.js';
import { Emoji } from '@/models/entities/Emoji.js';
import { Event } from '@/models/entities/Event.js';
import { Following } from '@/models/entities/Following.js';
import { FollowRequest } from '@/models/entities/FollowRequest.js';
import { GalleryLike } from '@/models/entities/GalleryLike.js';
@ -165,6 +166,7 @@ export const entities = [
Poll,
PollVote,
Emoji,
Event,
Hashtag,
SwSubscription,
AbuseUserReport,

View file

@ -73,7 +73,7 @@ export class DeleteAccountProcessorService {
await this.notesRepository.delete(notes.map(note => note.id));
}
this.logger.succ('All of notes deleted');
this.logger.succ(`All of notes deleted: ${job.data.user.id}`);
}
{ // Delete files
@ -102,7 +102,7 @@ export class DeleteAccountProcessorService {
}
}
this.logger.succ('All of files deleted');
this.logger.succ(`All of files deleted: ${job.data.user.id}`);
}
{ // Send email notification
@ -121,6 +121,6 @@ export class DeleteAccountProcessorService {
await this.usersRepository.delete(job.data.user.id);
}
return 'Account deleted';
return `[${job.data.user.id}] Account deleted`;
}
}

View file

@ -30,6 +30,7 @@ import { HashtagChannelService } from './api/stream/channels/hashtag.js';
import { HomeTimelineChannelService } from './api/stream/channels/home-timeline.js';
import { HybridTimelineChannelService } from './api/stream/channels/hybrid-timeline.js';
import { LocalTimelineChannelService } from './api/stream/channels/local-timeline.js';
import { MediaTimelineChannelService } from './api/stream/channels/media-timeline.js';
import { MessagingIndexChannelService } from './api/stream/channels/messaging-index.js';
import { MessagingChannelService } from './api/stream/channels/messaging.js';
import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
@ -76,6 +77,7 @@ import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.
HomeTimelineChannelService,
HybridTimelineChannelService,
LocalTimelineChannelService,
MediaTimelineChannelService,
MessagingIndexChannelService,
MessagingChannelService,
QueueStatsChannelService,

View file

@ -20,6 +20,7 @@ import { AuthenticateService, AuthenticationError } from './AuthenticateService.
import type { FastifyRequest, FastifyReply } from 'fastify';
import type { OnApplicationShutdown } from '@nestjs/common';
import type { IEndpointMeta, IEndpoint } from './endpoints.js';
import type { FlashToken } from '@/misc/flash-token.js';
const pump = promisify(pipeline);
@ -68,8 +69,8 @@ export class ApiCallService implements OnApplicationShutdown {
reply.code(400);
return;
}
this.authenticateService.authenticate(token).then(([user, app]) => {
this.call(endpoint, user, app, body, null, request).then((res) => {
this.authenticateService.authenticate(token).then(([user, app, flashToken]) => {
this.call(endpoint, user, app, flashToken, body, null, request).then((res) => {
if (request.method === 'GET' && endpoint.meta.cacheSec && !body?.['i'] && !user) {
reply.header('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
}
@ -122,8 +123,8 @@ export class ApiCallService implements OnApplicationShutdown {
reply.code(400);
return;
}
this.authenticateService.authenticate(token).then(([user, app]) => {
this.call(endpoint, user, app, fields, {
this.authenticateService.authenticate(token).then(([user, app, flashToken]) => {
this.call(endpoint, user, app, flashToken, fields, {
name: multipartData.filename,
path: path,
}, request).then((res) => {
@ -199,6 +200,7 @@ export class ApiCallService implements OnApplicationShutdown {
ep: IEndpoint & { exec: any },
user: LocalUser | null | undefined,
token: AccessToken | null | undefined,
flashToken: FlashToken | null | undefined,
data: any,
file: {
name: string;
@ -206,7 +208,7 @@ export class ApiCallService implements OnApplicationShutdown {
} | null,
request: FastifyRequest<{ Body: Record<string, unknown> | undefined, Querystring: Record<string, unknown> }>,
) {
const isSecure = user != null && token == null;
const isSecure = user != null && token == null && flashToken == null;
if (ep.meta.secure && !isSecure) {
throw new ApiError(accessDenied);
@ -309,6 +311,14 @@ export class ApiCallService implements OnApplicationShutdown {
});
}
if (flashToken && ep.meta.kind && !flashToken.permissions.some(p => p === ep.meta.kind)) {
throw new ApiError({
message: 'Your flash does not have the necessary permissions to use this endpoint.',
code: 'PERMISSION_DENIED',
id: '11924d17-113a-4ab0-954a-c567ee8a6ce5',
});
}
// Cast non JSON input
if ((ep.meta.requireFile || request.method === 'GET') && ep.params.properties) {
for (const k of Object.keys(ep.params.properties)) {
@ -331,7 +341,7 @@ export class ApiCallService implements OnApplicationShutdown {
}
// API invoking
return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => {
return await ep.exec(data, user, token, flashToken, file, request.ip, request.headers).catch((err: Error) => {
if (err instanceof ApiError || err instanceof AuthenticationError) {
throw err;
} else {

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js';
import type { LocalUser } from '@/models/entities/User.js';
@ -8,6 +8,7 @@ import type { App } from '@/models/entities/App.js';
import { CacheService } from '@/core/CacheService.js';
import isNativeToken from '@/misc/is-native-token.js';
import { bindThis } from '@/decorators.js';
import type { FlashToken } from '@/misc/flash-token';
export class AuthenticationError extends Error {
constructor(message: string) {
@ -17,7 +18,7 @@ export class AuthenticationError extends Error {
}
@Injectable()
export class AuthenticateService {
export class AuthenticateService implements OnApplicationShutdown {
private appCache: MemoryKVCache<App>;
constructor(
@ -36,9 +37,9 @@ export class AuthenticateService {
}
@bindThis
public async authenticate(token: string | null | undefined): Promise<[LocalUser | null, AccessToken | null]> {
public async authenticate(token: string | null | undefined): Promise<[LocalUser | null, AccessToken | null, FlashToken | null]> {
if (token == null) {
return [null, null];
return [null, null, null];
}
if (isNativeToken(token)) {
@ -49,7 +50,7 @@ export class AuthenticateService {
throw new AuthenticationError('user not found');
}
return [user, null];
return [user, null, null];
} else {
const accessToken = await this.accessTokensRepository.findOne({
where: [{
@ -60,7 +61,12 @@ export class AuthenticateService {
});
if (accessToken == null) {
throw new AuthenticationError('invalid signature');
const flashToken = await this.cacheService.flashAccessTokensCache.get(token);
if (flashToken !== null && typeof flashToken !== 'undefined') {
return [flashToken.user, null, flashToken];
} else {
throw new AuthenticationError('invalid signature');
}
}
this.accessTokensRepository.update(accessToken.id, {
@ -79,10 +85,20 @@ export class AuthenticateService {
return [user, {
id: accessToken.id,
permission: app.permission,
} as AccessToken];
} as AccessToken, null];
} else {
return [user, accessToken];
return [user, accessToken, null];
}
}
}
@bindThis
public dispose(): void {
this.appCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -39,6 +39,7 @@ import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'
import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
import * as ep___invite from './endpoints/invite.js';
import * as ep___inviteRevoke from './endpoints/invite-revoke.js';
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
@ -260,9 +261,11 @@ import * as ep___notes_featured from './endpoints/notes/featured.js';
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
import * as ep___notes_mediaTimeline from './endpoints/notes/media-timeline.js';
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
import * as ep___notes_events_search from './endpoints/notes/events/search.js';
import * as ep___notes_reactions from './endpoints/notes/reactions.js';
import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
@ -291,6 +294,7 @@ import * as ep___pages_update from './endpoints/pages/update.js';
import * as ep___flash_create from './endpoints/flash/create.js';
import * as ep___flash_delete from './endpoints/flash/delete.js';
import * as ep___flash_featured from './endpoints/flash/featured.js';
import * as ep___flash_genToken from './endpoints/flash/gen-token.js';
import * as ep___flash_like from './endpoints/flash/like.js';
import * as ep___flash_show from './endpoints/flash/show.js';
import * as ep___flash_unlike from './endpoints/flash/unlike.js';
@ -352,9 +356,9 @@ import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___users_achievements from './endpoints/users/achievements.js';
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
import * as ep___users_translate from './endpoints/users/translate.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
import * as ep___retention from './endpoints/retention.js';
import { GetterService } from './GetterService.js';
@ -399,6 +403,7 @@ const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', us
const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default };
const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default };
const $invite: Provider = { provide: 'ep:invite', useClass: ep___invite.default };
const $inviteRevoke: Provider = { provide: 'ep:invite-revoke', useClass: ep___inviteRevoke.default };
const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default };
const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default };
const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default };
@ -620,9 +625,11 @@ const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep__
const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
const $notes_mediaTimeline: Provider = { provide: 'ep:notes/media-timeline', useClass: ep___notes_mediaTimeline.default };
const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
const $notes_events_search: Provider = { provide: 'ep:notes/events/search', useClass: ep___notes_events_search.default };
const $notes_reactions: Provider = { provide: 'ep:notes/reactions', useClass: ep___notes_reactions.default };
const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create', useClass: ep___notes_reactions_create.default };
const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default };
@ -651,6 +658,7 @@ const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pag
const $flash_create: Provider = { provide: 'ep:flash/create', useClass: ep___flash_create.default };
const $flash_delete: Provider = { provide: 'ep:flash/delete', useClass: ep___flash_delete.default };
const $flash_featured: Provider = { provide: 'ep:flash/featured', useClass: ep___flash_featured.default };
const $flash_genToken: Provider = { provide: 'ep:flash/gen-token', useClass: ep___flash_genToken.default };
const $flash_like: Provider = { provide: 'ep:flash/like', useClass: ep___flash_like.default };
const $flash_show: Provider = { provide: 'ep:flash/show', useClass: ep___flash_show.default };
const $flash_unlike: Provider = { provide: 'ep:flash/unlike', useClass: ep___flash_unlike.default };
@ -712,9 +720,9 @@ const $users_reportAbuse: Provider = { provide: 'ep:users/report-abuse', useClas
const $users_searchByUsernameAndHost: Provider = { provide: 'ep:users/search-by-username-and-host', useClass: ep___users_searchByUsernameAndHost.default };
const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___users_search.default };
const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default };
const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default };
const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default };
const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default };
const $users_translate: Provider = { provide: 'ep:users/translate', useClass: ep___users_translate.default };
const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
@ -763,6 +771,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_getTableStats,
$admin_getUserIps,
$invite,
$inviteRevoke,
$admin_promo_create,
$admin_queue_clear,
$admin_queue_deliverDelayed,
@ -984,9 +993,11 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_globalTimeline,
$notes_hybridTimeline,
$notes_localTimeline,
$notes_mediaTimeline,
$notes_mentions,
$notes_polls_recommendation,
$notes_polls_vote,
$notes_events_search,
$notes_reactions,
$notes_reactions_create,
$notes_reactions_delete,
@ -1015,6 +1026,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$flash_create,
$flash_delete,
$flash_featured,
$flash_genToken,
$flash_like,
$flash_show,
$flash_unlike,
@ -1076,9 +1088,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$users_searchByUsernameAndHost,
$users_search,
$users_show,
$users_stats,
$users_achievements,
$users_updateMemo,
$users_translate,
$fetchRss,
$retention,
],
@ -1121,6 +1133,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_getTableStats,
$admin_getUserIps,
$invite,
$inviteRevoke,
$admin_promo_create,
$admin_queue_clear,
$admin_queue_deliverDelayed,
@ -1341,9 +1354,11 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_globalTimeline,
$notes_hybridTimeline,
$notes_localTimeline,
$notes_mediaTimeline,
$notes_mentions,
$notes_polls_recommendation,
$notes_polls_vote,
$notes_events_search,
$notes_reactions,
$notes_reactions_create,
$notes_reactions_delete,
@ -1372,6 +1387,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$flash_create,
$flash_delete,
$flash_featured,
$flash_genToken,
$flash_like,
$flash_show,
$flash_unlike,
@ -1431,9 +1447,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$users_searchByUsernameAndHost,
$users_search,
$users_show,
$users_stats,
$users_achievements,
$users_updateMemo,
$users_translate,
$fetchRss,
$retention,
],

View file

@ -1,8 +1,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, UsersRepository } from '@/models/index.js';
import type { NotesRepository, UsersRepository, UserProfilesRepository } from '@/models/index.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import type { UserProfile } from '@/models/entities/UserProfile.js';
import type { Note } from '@/models/entities/Note.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
@ -16,6 +17,9 @@ export class GetterService {
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private userEntityService: UserEntityService,
) {
}
@ -75,5 +79,16 @@ export class GetterService {
return user;
}
@bindThis
public async getUserProfiles(userId: UserProfile['userId']) {
const user = await this.userProfilesRepository.findOneBy({ userId: userId });
if (user == null) {
throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.');
}
return user;
}
}

View file

@ -128,26 +128,27 @@ export class StreamingApiServerService {
ev.removeAllListeners();
stream.dispose();
this.redisForSub.off('message', onRedisMessage);
this.#connections.delete(connection);
if (userUpdateIntervalId) clearInterval(userUpdateIntervalId);
});
connection.on('message', async (data) => {
connection.on('pong', () => {
this.#connections.set(connection, Date.now());
if (data.toString() === 'ping') {
connection.send('pong');
}
});
});
// 一定期間通信が無いコネクションは実際には切断されている可能性があるため定期的にterminateする
this.#cleanConnectionsIntervalId = setInterval(() => {
const now = Date.now();
for (const [connection, lastActive] of this.#connections.entries()) {
if (now - lastActive > 1000 * 60 * 5) {
if (now - lastActive > 1000 * 60 * 2) {
connection.terminate();
this.#connections.delete(connection);
} else {
connection.ping();
}
}
}, 1000 * 60 * 5);
}, 1000 * 60);
}
@bindThis

View file

@ -3,6 +3,7 @@ import Ajv from 'ajv';
import type { Schema, SchemaType } from '@/misc/json-schema.js';
import type { LocalUser } from '@/models/entities/User.js';
import type { AccessToken } from '@/models/entities/AccessToken.js';
import type { FlashToken } from '@/misc/flash-token.js';
import { ApiError } from './error.js';
import type { IEndpointMeta } from './endpoints.js';
@ -21,16 +22,16 @@ type File = {
// TODO: paramsの型をT['params']のスキーマ定義から推論する
type Executor<T extends IEndpointMeta, Ps extends Schema> =
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record<string, string> | null) =>
(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, flashToken: FlashToken | null, file?: File, cleanup?: () => any, ip?: string | null, headers?: Record<string, string> | null) =>
Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
export abstract class Endpoint<T extends IEndpointMeta, Ps extends Schema> {
public exec: (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => Promise<any>;
public exec: (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, flashToken: FlashToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => Promise<any>;
constructor(meta: T, paramDef: Ps, cb: Executor<T, Ps>) {
const validate = ajv.compile(paramDef);
this.exec = (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => {
this.exec = (params: any, user: T['requireCredential'] extends true ? LocalUser : LocalUser | null, token: AccessToken | null, flashToken: FlashToken | null, file?: File, ip?: string | null, headers?: Record<string, string> | null) => {
let cleanup: undefined | (() => void) = undefined;
if (meta.requireFile) {
@ -61,7 +62,7 @@ export abstract class Endpoint<T extends IEndpointMeta, Ps extends Schema> {
return Promise.reject(err);
}
return cb(params as SchemaType<Ps>, user, token, file, cleanup, ip, headers);
return cb(params as SchemaType<Ps>, user, token, flashToken, file, cleanup, ip, headers);
};
}
}

View file

@ -39,6 +39,7 @@ import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'
import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
import * as ep___invite from './endpoints/invite.js';
import * as ep___inviteRevoke from './endpoints/invite-revoke.js';
import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
@ -260,9 +261,11 @@ import * as ep___notes_featured from './endpoints/notes/featured.js';
import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
import * as ep___notes_mediaTimeline from './endpoints/notes/media-timeline.js';
import * as ep___notes_mentions from './endpoints/notes/mentions.js';
import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
import * as ep___notes_events_search from './endpoints/notes/events/search.js';
import * as ep___notes_reactions from './endpoints/notes/reactions.js';
import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
@ -291,6 +294,7 @@ import * as ep___pages_update from './endpoints/pages/update.js';
import * as ep___flash_create from './endpoints/flash/create.js';
import * as ep___flash_delete from './endpoints/flash/delete.js';
import * as ep___flash_featured from './endpoints/flash/featured.js';
import * as ep___flash_genToken from './endpoints/flash/gen-token.js';
import * as ep___flash_like from './endpoints/flash/like.js';
import * as ep___flash_show from './endpoints/flash/show.js';
import * as ep___flash_unlike from './endpoints/flash/unlike.js';
@ -352,9 +356,9 @@ import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___users_achievements from './endpoints/users/achievements.js';
import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
import * as ep___users_translate from './endpoints/users/translate.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
import * as ep___retention from './endpoints/retention.js';
@ -397,6 +401,7 @@ const eps = [
['admin/get-table-stats', ep___admin_getTableStats],
['admin/get-user-ips', ep___admin_getUserIps],
['invite', ep___invite],
['invite-revoke', ep___inviteRevoke],
['admin/promo/create', ep___admin_promo_create],
['admin/queue/clear', ep___admin_queue_clear],
['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
@ -618,9 +623,11 @@ const eps = [
['notes/global-timeline', ep___notes_globalTimeline],
['notes/hybrid-timeline', ep___notes_hybridTimeline],
['notes/local-timeline', ep___notes_localTimeline],
['notes/media-timeline', ep___notes_mediaTimeline],
['notes/mentions', ep___notes_mentions],
['notes/polls/recommendation', ep___notes_polls_recommendation],
['notes/polls/vote', ep___notes_polls_vote],
['notes/events/search', ep___notes_events_search],
['notes/reactions', ep___notes_reactions],
['notes/reactions/create', ep___notes_reactions_create],
['notes/reactions/delete', ep___notes_reactions_delete],
@ -649,6 +656,7 @@ const eps = [
['flash/create', ep___flash_create],
['flash/delete', ep___flash_delete],
['flash/featured', ep___flash_featured],
['flash/gen-token', ep___flash_genToken],
['flash/like', ep___flash_like],
['flash/show', ep___flash_show],
['flash/unlike', ep___flash_unlike],
@ -710,9 +718,9 @@ const eps = [
['users/search-by-username-and-host', ep___users_searchByUsernameAndHost],
['users/search', ep___users_search],
['users/show', ep___users_show],
['users/stats', ep___users_stats],
['users/achievements', ep___users_achievements],
['users/update-memo', ep___users_updateMemo],
['users/translate', ep___users_translate],
['fetch-rss', ep___fetchRss],
['retention', ep___retention],
];

View file

@ -61,10 +61,17 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
errorImageUrl: {
serverErrorImageUrl: {
type: 'string',
optional: false, nullable: true,
},
infoImageUrl: {
type: 'string',
optional: false, nullable: true,
},
notFoundImageUrl: {
type: 'string',
optional: false, nullable: true,
default: 'https://xn--931a.moe/aiart/yubitun.png',
},
iconUrl: {
type: 'string',
@ -309,7 +316,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl,
bannerUrl: instance.bannerUrl,
errorImageUrl: instance.errorImageUrl,
serverErrorImageUrl: instance.serverErrorImageUrl,
notFoundImageUrl: instance.notFoundImageUrl,
infoImageUrl: instance.infoImageUrl,
iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl,

View file

@ -32,7 +32,9 @@ export const paramDef = {
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
mascotImageUrl: { type: 'string', nullable: true },
bannerUrl: { type: 'string', nullable: true },
errorImageUrl: { type: 'string', nullable: true },
serverErrorImageUrl: { type: 'string', nullable: true },
infoImageUrl: { type: 'string', nullable: true },
notFoundImageUrl: { type: 'string', nullable: true },
iconUrl: { type: 'string', nullable: true },
backgroundImageUrl: { type: 'string', nullable: true },
logoImageUrl: { type: 'string', nullable: true },
@ -155,6 +157,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
set.iconUrl = ps.iconUrl;
}
if (ps.serverErrorImageUrl !== undefined) {
set.serverErrorImageUrl = ps.serverErrorImageUrl;
}
if (ps.infoImageUrl !== undefined) {
set.infoImageUrl = ps.infoImageUrl;
}
if (ps.notFoundImageUrl !== undefined) {
set.notFoundImageUrl = ps.notFoundImageUrl;
}
if (ps.backgroundImageUrl !== undefined) {
set.backgroundImageUrl = ps.backgroundImageUrl;
}
@ -287,10 +301,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
set.smtpPass = ps.smtpPass;
}
if (ps.errorImageUrl !== undefined) {
set.errorImageUrl = ps.errorImageUrl;
}
if (ps.enableServiceWorker !== undefined) {
set.enableServiceWorker = ps.enableServiceWorker;
}

View file

@ -40,8 +40,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private appEntityService: AppEntityService,
) {
super(meta, paramDef, async (ps, user, token) => {
const isSecure = user != null && token == null;
super(meta, paramDef, async (ps, user, token, flashToken) => {
const isSecure = user != null && token == null && flashToken == null;
// Lookup app
const ap = await this.appsRepository.findOneBy({ id: ps.appId });

View file

@ -78,7 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private metaService: MetaService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => {
super(meta, paramDef, async (ps, me, _1, _2, file, cleanup, ip, headers) => {
// Get 'name' parameter
let name = ps.name ?? file!.name ?? null;
if (name != null) {

View file

@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private driveService: DriveService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => {
super(meta, paramDef, async (ps, user, _1, _2, _3, _4, ip, headers) => {
this.driveService.uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => {
this.driveFileEntityService.pack(file, { self: true }).then(packedFile => {
this.globalEventService.publishMainStream(user.id, 'urlUploadFinished', {

View file

@ -0,0 +1,56 @@
import { Injectable } from '@nestjs/common';
import ms from 'ms';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CacheService } from '@/core/CacheService.js';
export const meta = {
tags: ['flash'],
requireCredential: true,
prohibitMoved: true,
secure: true,
limit: {
duration: ms('1hour'),
max: 30,
},
res: {
type: 'object',
optional: false, nullable: false,
properties: {
token: { type: 'string' },
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
permissions: { type: 'array', items: {
type: 'string',
} },
},
required: ['permissions'],
} as const;
@Injectable() // eslint-disable-next-line import/no-default-export
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor (
private cacheService: CacheService,
) {
super(meta, paramDef, async (ps, me) => {
const token = secureRndstr(32, true);
await this.cacheService.flashAccessTokensCache.set(token, {
user: me,
permissions: ps.permissions,
});
return {
token,
};
});
}
}

View file

@ -44,8 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private userEntityService: UserEntityService,
) {
super(meta, paramDef, async (ps, user, token) => {
const isSecure = token == null;
super(meta, paramDef, async (ps, user, token, flashToken) => {
const isSecure = token == null && flashToken == null;
const now = new Date();
const today = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`;

View file

@ -1,5 +1,5 @@
import RE2 from 're2';
import * as mfm from 'mfm-js';
import * as mfm from 'cherrypick-mfm-js';
import { Inject, Injectable } from '@nestjs/common';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
@ -194,9 +194,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private roleService: RoleService,
private cacheService: CacheService,
) {
super(meta, paramDef, async (ps, _user, token) => {
super(meta, paramDef, async (ps, _user, token, flashToken) => {
const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
const isSecure = token == null;
const isSecure = token == null && flashToken == null;
const updates = {} as Partial<User>;
const profileUpdates = {} as Partial<UserProfile>;

View file

@ -0,0 +1,31 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RegistrationTicketsRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireRolePolicy: 'canInvite',
requireModerator: true,
} as const;
export const paramDef = {
type: 'object',
properties: {},
required: [],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.registrationTicketsRepository)
private registrationTicketsRepository: RegistrationTicketsRepository,
) {
super(meta, paramDef, async () => {
await this.registrationTicketsRepository.delete({
});
});
}
}

View file

@ -51,24 +51,16 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
groupId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
markAsRead: { type: 'boolean', default: true },
},
anyOf: [
{
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
},
{
properties: {
groupId: { type: 'string', format: 'misskey:id' },
},
required: ['groupId'],
},
{ required: ['userId'] },
{ required: ['groupId'] },
],
} as const;

Some files were not shown because too many files have changed in this diff Show more