Compare commits
51 Commits
2023.10.0-
...
tl-push
Author | SHA1 | Date | |
---|---|---|---|
![]() |
77498f84d8 | ||
![]() |
0575207463 | ||
![]() |
7a3dd400d8 | ||
![]() |
e840544dd2 | ||
![]() |
0e58f515fd | ||
![]() |
c3714c02ba | ||
![]() |
9de11da170 | ||
![]() |
e12943c15b | ||
![]() |
7022b16bce | ||
![]() |
ee3d40bc1b | ||
![]() |
878e73cd37 | ||
![]() |
96da6e28ea | ||
![]() |
45c3ab2142 | ||
![]() |
58eec94250 | ||
![]() |
b66df850e5 | ||
![]() |
72d5b1f4ae | ||
![]() |
15caa375a5 | ||
![]() |
0e302c69bd | ||
![]() |
236eed94bb | ||
![]() |
6d68cfd1e3 | ||
![]() |
ea0d050b71 | ||
![]() |
d6ff810560 | ||
![]() |
aad48b4b24 | ||
![]() |
2f00e4b2b1 | ||
![]() |
152047ca14 | ||
![]() |
58d2512d0e | ||
![]() |
bbcda73af8 | ||
![]() |
880448d068 | ||
![]() |
55e5056216 | ||
![]() |
d40f35b3ad | ||
![]() |
8843669684 | ||
![]() |
c7c4c7807a | ||
![]() |
79e5075564 | ||
![]() |
caca0da912 | ||
![]() |
35e743c955 | ||
![]() |
6f17993cba | ||
![]() |
e4de402ca1 | ||
![]() |
0db117b0ab | ||
![]() |
cb821d42a6 | ||
![]() |
b4c1de11f5 | ||
![]() |
3924a9e494 | ||
![]() |
d9aac112d3 | ||
![]() |
7f4c00541c | ||
![]() |
85430fd889 | ||
![]() |
f0a2c3ce76 | ||
![]() |
c019e9cad5 | ||
![]() |
167aaabf20 | ||
![]() |
72f7413f40 | ||
![]() |
783a97fe06 | ||
![]() |
3dd3c69303 | ||
![]() |
06cfe618bb |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -15,25 +15,21 @@
|
||||
## 2023.10.0
|
||||
### NOTE
|
||||
- muted_noteテーブルは使われなくなったため手動で削除を行ってください。
|
||||
- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました
|
||||
|
||||
### Changes
|
||||
- API: users/notes, notes/local-timeline で fileType 指定はできなくなりました
|
||||
- API: notes/global-timeline は現在常に `[]` を返します
|
||||
|
||||
### General
|
||||
- Feat: ユーザーごとに他ユーザーへの返信をタイムラインに含めるか設定可能になりました
|
||||
- Feat: ユーザーリスト内のメンバーごとに他ユーザーへの返信をユーザーリストタイムラインに含めるか設定可能になりました
|
||||
- Enhance: ソフトワードミュートとハードワードミュートは統合されました
|
||||
- Enhance: モデレーションログ機能の強化
|
||||
- Enhance: ローカリゼーションの更新
|
||||
- ユーザーごとに他ユーザーへの返信をタイムラインに含めるか設定可能になりました
|
||||
- ユーザーリスト内のメンバーごとに他ユーザーへの返信をユーザーリストタイムラインに含めるか設定可能になりました
|
||||
- ソフトワードミュートとハードワードミュートは統合されました
|
||||
|
||||
### Client
|
||||
- Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に
|
||||
- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正
|
||||
|
||||
### Server
|
||||
- Enhance: タイムライン取得時のパフォーマンスを改善
|
||||
- タイムライン取得時のパフォーマンスを改善
|
||||
|
||||
## 2023.9.3
|
||||
### General
|
||||
|
@@ -1248,6 +1248,8 @@ _sfx:
|
||||
note: "الملاحظات"
|
||||
noteMy: "ملاحظتي"
|
||||
notification: "الإشعارات"
|
||||
chat: "المحادثة"
|
||||
chatBg: "المحادثة (الخلفية)"
|
||||
antenna: "الهوائيات"
|
||||
channel: "إشعارات القنات"
|
||||
_ago:
|
||||
|
@@ -1020,6 +1020,8 @@ _sfx:
|
||||
note: "নোটগুলি"
|
||||
noteMy: "নোট (আপনার)"
|
||||
notification: "বিজ্ঞপ্তি"
|
||||
chat: "চ্যাট"
|
||||
chatBg: "চ্যাট (ব্যাকগ্রাউন্ড)"
|
||||
antenna: "অ্যান্টেনাগুলি"
|
||||
channel: "চ্যানেলের বিজ্ঞপ্তি"
|
||||
_ago:
|
||||
|
@@ -398,6 +398,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "Notes"
|
||||
notification: "Notificacions"
|
||||
chat: "Xat"
|
||||
antenna: "Antenes"
|
||||
_2fa:
|
||||
renewTOTPCancel: "No, gràcies"
|
||||
|
@@ -1647,6 +1647,8 @@ _sfx:
|
||||
note: "Poznámky"
|
||||
noteMy: "Moje poznámka"
|
||||
notification: "Oznámení"
|
||||
chat: "Zprávy"
|
||||
chatBg: "Chat (Pozadí)"
|
||||
antenna: "Antény"
|
||||
channel: "Oznámení kanálu"
|
||||
_ago:
|
||||
|
@@ -1697,6 +1697,8 @@ _sfx:
|
||||
note: "Notizen"
|
||||
noteMy: "Meine Notizen"
|
||||
notification: "Benachrichtigungen"
|
||||
chat: "Chat"
|
||||
chatBg: "Chat (Hintergrund)"
|
||||
antenna: "Antennen"
|
||||
channel: "Kanalbenachrichtigung"
|
||||
_ago:
|
||||
|
@@ -303,6 +303,8 @@ _theme:
|
||||
_sfx:
|
||||
note: "Σημειώματα"
|
||||
notification: "Ειδοποιήσεις"
|
||||
chat: "Συνομιλία"
|
||||
chatBg: "Συνομιλία (Παρασκήνιο)"
|
||||
antenna: "Αντένες"
|
||||
channel: "Ειδοποιήσεις καναλιών"
|
||||
_ago:
|
||||
|
@@ -1697,6 +1697,8 @@ _sfx:
|
||||
note: "New note"
|
||||
noteMy: "Own note"
|
||||
notification: "Notifications"
|
||||
chat: "Chat"
|
||||
chatBg: "Chat (Background)"
|
||||
antenna: "Antennas"
|
||||
channel: "Channel notifications"
|
||||
_ago:
|
||||
|
@@ -1691,6 +1691,8 @@ _sfx:
|
||||
note: "Notas"
|
||||
noteMy: "Nota (a mí mismo)"
|
||||
notification: "Notificaciones"
|
||||
chat: "Chat"
|
||||
chatBg: "Chat (Fondo)"
|
||||
antenna: "Antena receptora"
|
||||
channel: "Notificaciones del canal"
|
||||
_ago:
|
||||
|
@@ -1355,6 +1355,8 @@ _sfx:
|
||||
note: "Nouvelle note"
|
||||
noteMy: "Ma note"
|
||||
notification: "Notifications"
|
||||
chat: "Discuter"
|
||||
chatBg: "Discussion (arrière-plan)"
|
||||
antenna: "Réception de l’antenne"
|
||||
channel: "Notifications de canal"
|
||||
_ago:
|
||||
|
@@ -1652,6 +1652,8 @@ _sfx:
|
||||
note: "Catatan"
|
||||
noteMy: "Catatan (Saya)"
|
||||
notification: "Notifikasi"
|
||||
chat: "Pesan"
|
||||
chatBg: "Obrolan (Latar Belakang)"
|
||||
antenna: "Penerimaan Antenna"
|
||||
channel: "Notifikasi Kanal"
|
||||
_ago:
|
||||
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
@@ -1131,7 +1131,6 @@ export interface Locale {
|
||||
"fileAttachedOnly": string;
|
||||
"showRepliesToOthersInTimeline": string;
|
||||
"hideRepliesToOthersInTimeline": string;
|
||||
"externalServices": string;
|
||||
"_announcement": {
|
||||
"forExistingUsers": string;
|
||||
"forExistingUsersDescription": string;
|
||||
@@ -1545,6 +1544,7 @@ export interface Locale {
|
||||
"gtlAvailable": string;
|
||||
"ltlAvailable": string;
|
||||
"canPublicNote": string;
|
||||
"canEditNote": string;
|
||||
"canInvite": string;
|
||||
"inviteLimit": string;
|
||||
"inviteLimitCycle": string;
|
||||
@@ -1808,6 +1808,8 @@ export interface Locale {
|
||||
"note": string;
|
||||
"noteMy": string;
|
||||
"notification": string;
|
||||
"chat": string;
|
||||
"chatBg": string;
|
||||
"antenna": string;
|
||||
"channel": string;
|
||||
};
|
||||
|
@@ -1692,6 +1692,8 @@ _sfx:
|
||||
note: "Nota"
|
||||
noteMy: "Mia nota"
|
||||
notification: "Notifiche"
|
||||
chat: "Messaggi"
|
||||
chatBg: "Chat (sfondo)"
|
||||
antenna: "Ricezione dell'antenna"
|
||||
channel: "Notifiche di canale"
|
||||
_ago:
|
||||
|
@@ -1128,7 +1128,6 @@ mutualFollow: "相互フォロー"
|
||||
fileAttachedOnly: "ファイル付きのみ"
|
||||
showRepliesToOthersInTimeline: "TLに他の人への返信を含める"
|
||||
hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない"
|
||||
externalServices: "外部サービス"
|
||||
|
||||
_announcement:
|
||||
forExistingUsers: "既存ユーザーのみ"
|
||||
@@ -1466,6 +1465,7 @@ _role:
|
||||
gtlAvailable: "グローバルタイムラインの閲覧"
|
||||
ltlAvailable: "ローカルタイムラインの閲覧"
|
||||
canPublicNote: "パブリック投稿の許可"
|
||||
canEditNote: "ノートの編集"
|
||||
canInvite: "サーバー招待コードの発行"
|
||||
inviteLimit: "招待コードの作成可能数"
|
||||
inviteLimitCycle: "招待コードの発行間隔"
|
||||
@@ -1725,6 +1725,8 @@ _sfx:
|
||||
note: "ノート"
|
||||
noteMy: "ノート(自分)"
|
||||
notification: "通知"
|
||||
chat: "チャット"
|
||||
chatBg: "チャット(バックグラウンド)"
|
||||
antenna: "アンテナ受信"
|
||||
channel: "チャンネル通知"
|
||||
|
||||
|
@@ -1674,6 +1674,8 @@ _sfx:
|
||||
note: "ノート"
|
||||
noteMy: "ノート(自分)"
|
||||
notification: "通知"
|
||||
chat: "チャット"
|
||||
chatBg: "チャット(バックグラウンド)"
|
||||
antenna: "アンテナ受信"
|
||||
channel: "チャンネル通知"
|
||||
_ago:
|
||||
|
@@ -1688,6 +1688,8 @@ _sfx:
|
||||
note: "새 노트"
|
||||
noteMy: "내 노트"
|
||||
notification: "알림"
|
||||
chat: "대화"
|
||||
chatBg: "대화 (백그라운드)"
|
||||
antenna: "안테나 수신"
|
||||
channel: "채널 알림"
|
||||
_ago:
|
||||
|
@@ -407,6 +407,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "ບັນທຶກ"
|
||||
notification: "ການແຈ້ງເຕືອນ"
|
||||
chat: "ແຊ໋ດ"
|
||||
_2fa:
|
||||
renewTOTPCancel: "ບໍ່ແມ່ນຕອນນີ້"
|
||||
_widgets:
|
||||
|
@@ -438,6 +438,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "Notities"
|
||||
notification: "Meldingen"
|
||||
chat: "Chat"
|
||||
_2fa:
|
||||
renewTOTPCancel: "Nee, bedankt"
|
||||
_widgets:
|
||||
|
@@ -1066,6 +1066,8 @@ _sfx:
|
||||
note: "Wpisy"
|
||||
noteMy: "Mój wpis"
|
||||
notification: "Powiadomienia"
|
||||
chat: "Wiadomości"
|
||||
chatBg: "Rozmowy (tło)"
|
||||
antenna: "Anteny"
|
||||
channel: "Powiadomienia kanału"
|
||||
_ago:
|
||||
|
@@ -1320,6 +1320,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "Posts"
|
||||
notification: "Notificações"
|
||||
chat: "Chat"
|
||||
_ago:
|
||||
invalid: "Não há nada aqui"
|
||||
_timelineTutorial:
|
||||
|
@@ -647,6 +647,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "Note"
|
||||
notification: "Notificări"
|
||||
chat: "Chat"
|
||||
_ago:
|
||||
invalid: "Nu e nimic de văzut aici"
|
||||
_widgets:
|
||||
|
@@ -1576,6 +1576,8 @@ _sfx:
|
||||
note: "Заметки"
|
||||
noteMy: "Собственные заметки"
|
||||
notification: "Уведомления"
|
||||
chat: "Сообщения"
|
||||
chatBg: "Сообщения (фон)"
|
||||
antenna: "Антенна"
|
||||
channel: "Канал"
|
||||
_ago:
|
||||
|
@@ -1127,6 +1127,8 @@ _sfx:
|
||||
note: "Poznámky"
|
||||
noteMy: "Vlastná poznámka"
|
||||
notification: "Oznámenia"
|
||||
chat: "Chat"
|
||||
chatBg: "Chat (pozadie)"
|
||||
antenna: "Antény"
|
||||
channel: "Upozornenia kanála"
|
||||
_ago:
|
||||
|
@@ -507,6 +507,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "Noter"
|
||||
notification: "Notifikationer"
|
||||
chat: "Chatt"
|
||||
antenna: "Antenner"
|
||||
_2fa:
|
||||
renewTOTPCancel: "Nej tack"
|
||||
|
@@ -1686,6 +1686,8 @@ _sfx:
|
||||
note: "หมายเหตุ"
|
||||
noteMy: "โน้ตของตัวเอง"
|
||||
notification: "การเเจ้งเตือน"
|
||||
chat: "แชท"
|
||||
chatBg: "แชท (พื้นหลัง)"
|
||||
antenna: "เสาอากาศ"
|
||||
channel: "การแจ้งเตือนช่อง"
|
||||
_ago:
|
||||
|
@@ -386,6 +386,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "notlar"
|
||||
notification: "Bildirim"
|
||||
chat: "Mesajlar"
|
||||
_2fa:
|
||||
renewTOTPCancel: "Hayır, teşekkürler"
|
||||
_permissions:
|
||||
|
@@ -1315,6 +1315,8 @@ _sfx:
|
||||
note: "Нотатки"
|
||||
noteMy: "Мої нотатки"
|
||||
notification: "Сповіщення"
|
||||
chat: "Чати"
|
||||
chatBg: "Чати (фон)"
|
||||
antenna: "Прийом антени"
|
||||
channel: "Повідомлення каналу"
|
||||
_ago:
|
||||
|
@@ -910,6 +910,7 @@ _theme:
|
||||
_sfx:
|
||||
note: "Qaydlar"
|
||||
notification: "Xabarnomalar"
|
||||
chat: "Suhbat"
|
||||
_ago:
|
||||
minutesAgo: "{n} daqiqa oldin"
|
||||
hoursAgo: "{n} soat oldin"
|
||||
|
@@ -1492,6 +1492,8 @@ _sfx:
|
||||
note: "Tút"
|
||||
noteMy: "Tút của tôi"
|
||||
notification: "Thông báo"
|
||||
chat: "Trò chuyện"
|
||||
chatBg: "Chat (Nền)"
|
||||
antenna: "Trạm phát sóng"
|
||||
channel: "Kênh"
|
||||
_ago:
|
||||
|
@@ -1697,6 +1697,8 @@ _sfx:
|
||||
note: "帖子"
|
||||
noteMy: "我的帖子"
|
||||
notification: "通知"
|
||||
chat: "聊天"
|
||||
chatBg: "聊天背景"
|
||||
antenna: "天线接收"
|
||||
channel: "频道通知"
|
||||
_ago:
|
||||
|
@@ -1695,6 +1695,8 @@ _sfx:
|
||||
note: "貼文"
|
||||
noteMy: "我的貼文"
|
||||
notification: "通知"
|
||||
chat: "聊天"
|
||||
chatBg: "聊天背景"
|
||||
antenna: "天線接收"
|
||||
channel: "頻道通知"
|
||||
_ago:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2023.10.0-beta.3",
|
||||
"version": "2023.9.3",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -1,33 +0,0 @@
|
||||
export class Clean1696332072038 {
|
||||
name = 'Clean1696332072038'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_d844bfc6f3f523a05189076efaa"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_605472305f26818cc93d1baaa74"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_d844bfc6f3f523a05189076efa"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_605472305f26818cc93d1baaa7"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_90f7da835e4c10aca6853621e1"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_membership"."createdAt" IS 'The created date of the UserListMembership.'`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_021015e6683570ae9f6b0c62be" ON "user_list_membership" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_cddcaf418dc4d392ecfcca842a" ON "user_list_membership" ("userListId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e4f3094c43f2d665e6030b0337" ON "user_list_membership" ("userId", "userListId") `);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_021015e6683570ae9f6b0c62bee" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_cddcaf418dc4d392ecfcca842a7" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_cddcaf418dc4d392ecfcca842a7"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_021015e6683570ae9f6b0c62bee"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_e4f3094c43f2d665e6030b0337"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_cddcaf418dc4d392ecfcca842a"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_021015e6683570ae9f6b0c62be"`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_membership"."createdAt" IS 'The created date of the UserListJoining.'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{admin,administrator,root,system,maintainer,host,mod,moderator,owner,superuser,staff,auth,i,me,everyone,all,mention,mentions,example,user,users,account,accounts,official,help,helps,support,supports,info,information,informations,announce,announces,announcement,announcements,notice,notification,notifications,dev,developer,developers,tech,misskey}'`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_90f7da835e4c10aca6853621e1" ON "user_list_membership" ("userId", "userListId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_605472305f26818cc93d1baaa7" ON "user_list_membership" ("userListId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_d844bfc6f3f523a05189076efa" ON "user_list_membership" ("userId") `);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_605472305f26818cc93d1baaa74" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_d844bfc6f3f523a05189076efaa" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class MetaCacheSettings1696373953614 {
|
||||
name = 'MetaCacheSettings1696373953614'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "perLocalUserUserTimelineCacheMax" integer NOT NULL DEFAULT '300'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "perRemoteUserUserTimelineCacheMax" integer NOT NULL DEFAULT '100'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "perUserHomeTimelineCacheMax" integer NOT NULL DEFAULT '300'`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "perUserListTimelineCacheMax" integer NOT NULL DEFAULT '300'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserListTimelineCacheMax"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserHomeTimelineCacheMax"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perRemoteUserUserTimelineCacheMax"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perLocalUserUserTimelineCacheMax"`);
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class RevertNoteEdit1696388600237 {
|
||||
name = 'RevertNoteEdit1696388600237'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class CleanUp1696405744672 {
|
||||
name = 'CleanUp1696405744672'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_e7c0567f5261063592f022e9b5"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_25dfc71b0369b003a4cd434d0b"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE INDEX "IDX_25dfc71b0369b003a4cd434d0b" ON "note" ("attachedFileTypes") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e7c0567f5261063592f022e9b5" ON "note" ("createdAt") `);
|
||||
}
|
||||
}
|
@@ -158,13 +158,9 @@ export class AnnouncementService {
|
||||
|
||||
if (moderator) {
|
||||
if (announcement.userId) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
|
||||
this.moderationLogService.log(moderator, 'deleteUserAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
userId: announcement.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
} else {
|
||||
this.moderationLogService.log(moderator, 'deleteGlobalAnnouncement', {
|
||||
|
@@ -481,10 +481,12 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
// Increment notes count (user)
|
||||
this.incNotesCountOfUser(user);
|
||||
|
||||
if (data.visibility === 'specified') {
|
||||
// TODO?
|
||||
} else {
|
||||
if (data.visibility === 'public' || data.visibility === 'home') {
|
||||
this.pushToTl(note, user);
|
||||
} else if (data.visibility === 'followers') {
|
||||
this.pushToTl(note, user);
|
||||
} else if (data.visibility === 'specified') {
|
||||
// TODO
|
||||
}
|
||||
|
||||
this.antennaService.addNoteToAntennas(note, user);
|
||||
@@ -801,17 +803,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
|
||||
@bindThis
|
||||
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
|
||||
const meta = await this.metaService.fetch();
|
||||
|
||||
const redisPipeline = this.redisForTimelines.pipeline();
|
||||
|
||||
if (note.channelId) {
|
||||
redisPipeline.xadd(
|
||||
`userTimelineWithChannel:${user.id}`,
|
||||
'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(),
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
const channelFollowings = await this.channelFollowingsRepository.find({
|
||||
where: {
|
||||
followeeId: note.channelId,
|
||||
@@ -822,14 +816,14 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
for (const channelFollowing of channelFollowings) {
|
||||
redisPipeline.xadd(
|
||||
`homeTimeline:${channelFollowing.followerId}`,
|
||||
'MAXLEN', '~', meta.perUserHomeTimelineCacheMax.toString(),
|
||||
'MAXLEN', '~', '200',
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
if (note.fileIds.length > 0) {
|
||||
redisPipeline.xadd(
|
||||
`homeTimelineWithFiles:${channelFollowing.followerId}`,
|
||||
'MAXLEN', '~', (meta.perUserHomeTimelineCacheMax / 2).toString(),
|
||||
'MAXLEN', '~', '100',
|
||||
'*',
|
||||
'note', note.id);
|
||||
}
|
||||
@@ -861,14 +855,14 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
|
||||
redisPipeline.xadd(
|
||||
`homeTimeline:${following.followerId}`,
|
||||
'MAXLEN', '~', meta.perUserHomeTimelineCacheMax.toString(),
|
||||
'MAXLEN', '~', '200',
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
if (note.fileIds.length > 0) {
|
||||
redisPipeline.xadd(
|
||||
`homeTimelineWithFiles:${following.followerId}`,
|
||||
'MAXLEN', '~', (meta.perUserHomeTimelineCacheMax / 2).toString(),
|
||||
'MAXLEN', '~', '100',
|
||||
'*',
|
||||
'note', note.id);
|
||||
}
|
||||
@@ -888,14 +882,14 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
|
||||
redisPipeline.xadd(
|
||||
`userListTimeline:${userListMembership.userListId}`,
|
||||
'MAXLEN', '~', meta.perUserListTimelineCacheMax.toString(),
|
||||
'MAXLEN', '~', '200',
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
if (note.fileIds.length > 0) {
|
||||
redisPipeline.xadd(
|
||||
`userListTimelineWithFiles:${userListMembership.userListId}`,
|
||||
'MAXLEN', '~', (meta.perUserListTimelineCacheMax / 2).toString(),
|
||||
'MAXLEN', '~', '100',
|
||||
'*',
|
||||
'note', note.id);
|
||||
}
|
||||
@@ -904,55 +898,57 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||
{ // 自分自身のHTL
|
||||
redisPipeline.xadd(
|
||||
`homeTimeline:${user.id}`,
|
||||
'MAXLEN', '~', meta.perUserHomeTimelineCacheMax.toString(),
|
||||
'MAXLEN', '~', '200',
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
if (note.fileIds.length > 0) {
|
||||
redisPipeline.xadd(
|
||||
`homeTimelineWithFiles:${user.id}`,
|
||||
'MAXLEN', '~', (meta.perUserHomeTimelineCacheMax / 2).toString(),
|
||||
'MAXLEN', '~', '100',
|
||||
'*',
|
||||
'note', note.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 自分自身以外への返信
|
||||
if (note.replyId && note.replyUserId !== note.userId) {
|
||||
redisPipeline.xadd(
|
||||
`userTimelineWithReplies:${user.id}`,
|
||||
'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(),
|
||||
'*',
|
||||
'note', note.id);
|
||||
} else {
|
||||
redisPipeline.xadd(
|
||||
`userTimeline:${user.id}`,
|
||||
'MAXLEN', '~', note.userHost == null ? meta.perLocalUserUserTimelineCacheMax.toString() : meta.perRemoteUserUserTimelineCacheMax.toString(),
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
if (note.fileIds.length > 0) {
|
||||
if (note.visibility === 'public' || note.visibility === 'home') {
|
||||
// 自分自身以外への返信
|
||||
if (note.replyId && note.replyUserId !== note.userId) {
|
||||
redisPipeline.xadd(
|
||||
`userTimelineWithFiles:${user.id}`,
|
||||
'MAXLEN', '~', note.userHost == null ? (meta.perLocalUserUserTimelineCacheMax / 2).toString() : (meta.perRemoteUserUserTimelineCacheMax / 2).toString(),
|
||||
`userTimelineWithReplies:${user.id}`,
|
||||
'MAXLEN', '~', '1000',
|
||||
'*',
|
||||
'note', note.id);
|
||||
}
|
||||
|
||||
if (note.visibility === 'public' && note.userHost == null) {
|
||||
} else {
|
||||
redisPipeline.xadd(
|
||||
'localTimeline',
|
||||
`userTimeline:${user.id}`,
|
||||
'MAXLEN', '~', '1000',
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
if (note.fileIds.length > 0) {
|
||||
redisPipeline.xadd(
|
||||
'localTimelineWithFiles',
|
||||
`userTimelineWithFiles:${user.id}`,
|
||||
'MAXLEN', '~', '500',
|
||||
'*',
|
||||
'note', note.id);
|
||||
}
|
||||
|
||||
if (note.visibility === 'public' && note.userHost == null) {
|
||||
redisPipeline.xadd(
|
||||
'localTimeline',
|
||||
'MAXLEN', '~', '1000',
|
||||
'*',
|
||||
'note', note.id);
|
||||
|
||||
if (note.fileIds.length > 0) {
|
||||
redisPipeline.xadd(
|
||||
'localTimelineWithFiles',
|
||||
'MAXLEN', '~', '500',
|
||||
'*',
|
||||
'note', note.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,6 +26,7 @@ export type RolePolicies = {
|
||||
gtlAvailable: boolean;
|
||||
ltlAvailable: boolean;
|
||||
canPublicNote: boolean;
|
||||
canEditNote: boolean;
|
||||
canInvite: boolean;
|
||||
inviteLimit: number;
|
||||
inviteLimitCycle: number;
|
||||
@@ -51,6 +52,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||
gtlAvailable: true,
|
||||
ltlAvailable: true,
|
||||
canPublicNote: true,
|
||||
canEditNote: true,
|
||||
canInvite: false,
|
||||
inviteLimit: 0,
|
||||
inviteLimitCycle: 60 * 24 * 7,
|
||||
@@ -296,6 +298,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
||||
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
||||
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
||||
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
|
||||
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
||||
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
|
||||
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
||||
|
@@ -308,6 +308,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||
const packed: Packed<'Note'> = await awaitAll({
|
||||
id: note.id,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
|
||||
userId: note.userId,
|
||||
user: this.userEntityService.pack(note.user ?? note.userId, me, {
|
||||
detail: false,
|
||||
|
@@ -3,16 +3,16 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
|
||||
if (userIds.has(note.userId) && !ignoreAuthor) {
|
||||
export function isUserRelated(note: any, userIds: Set<string>): boolean {
|
||||
if (userIds.has(note.userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (note.reply != null && note.reply.userId !== note.userId && userIds.has(note.reply.userId)) {
|
||||
if (note.reply != null && userIds.has(note.reply.userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (note.renote != null && note.renote.userId !== note.userId && userIds.has(note.renote.userId)) {
|
||||
if (note.renote != null && userIds.has(note.renote.userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -471,24 +471,4 @@ export class MiMeta {
|
||||
length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }',
|
||||
})
|
||||
public preservedUsernames: string[];
|
||||
|
||||
@Column('integer', {
|
||||
default: 300,
|
||||
})
|
||||
public perLocalUserUserTimelineCacheMax: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 100,
|
||||
})
|
||||
public perRemoteUserUserTimelineCacheMax: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 300,
|
||||
})
|
||||
public perUserHomeTimelineCacheMax: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 300,
|
||||
})
|
||||
public perUserListTimelineCacheMax: number;
|
||||
}
|
||||
|
@@ -18,11 +18,17 @@ export class MiNote {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Note.',
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
default: null,
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
@@ -150,6 +156,7 @@ export class MiNote {
|
||||
})
|
||||
public fileIds: MiDriveFile['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 256, array: true, default: '{}',
|
||||
})
|
||||
|
@@ -17,6 +17,11 @@ export const packedNoteSchema = {
|
||||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
deletedAt: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
|
@@ -257,6 +257,7 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
import * as ep___notes_create from './endpoints/notes/create.js';
|
||||
import * as ep___notes_delete from './endpoints/notes/delete.js';
|
||||
import * as ep___notes_update from './endpoints/notes/update.js';
|
||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||
@@ -606,6 +607,7 @@ const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes
|
||||
const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
|
||||
const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
|
||||
const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
|
||||
const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___notes_update.default };
|
||||
const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
|
||||
const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
|
||||
const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
|
||||
@@ -959,6 +961,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$notes_conversation,
|
||||
$notes_create,
|
||||
$notes_delete,
|
||||
$notes_update,
|
||||
$notes_favorites_create,
|
||||
$notes_favorites_delete,
|
||||
$notes_featured,
|
||||
@@ -1306,6 +1309,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$notes_conversation,
|
||||
$notes_create,
|
||||
$notes_delete,
|
||||
$notes_update,
|
||||
$notes_favorites_create,
|
||||
$notes_favorites_delete,
|
||||
$notes_featured,
|
||||
|
@@ -257,6 +257,7 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
import * as ep___notes_create from './endpoints/notes/create.js';
|
||||
import * as ep___notes_delete from './endpoints/notes/delete.js';
|
||||
import * as ep___notes_update from './endpoints/notes/update.js';
|
||||
import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
|
||||
import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
|
||||
import * as ep___notes_featured from './endpoints/notes/featured.js';
|
||||
@@ -604,6 +605,7 @@ const eps = [
|
||||
['notes/conversation', ep___notes_conversation],
|
||||
['notes/create', ep___notes_create],
|
||||
['notes/delete', ep___notes_delete],
|
||||
['notes/update', ep___notes_update],
|
||||
['notes/favorites/create', ep___notes_favorites_create],
|
||||
['notes/favorites/delete', ep___notes_favorites_delete],
|
||||
['notes/featured', ep___notes_featured],
|
||||
|
@@ -105,32 +105,40 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
userStarForReactionFallback: {
|
||||
type: 'boolean',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
pinnedUsers: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
hiddenTags: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
blockedHosts: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
sensitiveWords: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
preservedUsernames: {
|
||||
@@ -138,124 +146,129 @@ export const meta = {
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
hcaptchaSecretKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
recaptchaSecretKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
turnstileSecretKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
sensitiveMediaDetection: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
sensitiveMediaDetectionSensitivity: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
setSensitiveFlagAutomatically: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
enableSensitiveMediaDetectionForVideos: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
proxyAccountId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
format: 'id',
|
||||
},
|
||||
summaryProxy: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpSecure: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
smtpHost: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpPort: {
|
||||
type: 'number',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpUser: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
smtpPass: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
swPrivateKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
useObjectStorage: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageBaseUrl: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageBucket: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStoragePrefix: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageEndpoint: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageRegion: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStoragePort: {
|
||||
type: 'number',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageAccessKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageSecretKey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
objectStorageUseSSL: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageUseProxy: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
objectStorageSetPublicRead: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
enableIpLogging: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
enableActiveEmailValidation: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
enableChartsForRemoteUser: {
|
||||
type: 'boolean',
|
||||
@@ -275,28 +288,12 @@ export const meta = {
|
||||
},
|
||||
manifestJsonOverride: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
policies: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
perLocalUserUserTimelineCacheMax: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
perRemoteUserUserTimelineCacheMax: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
perUserHomeTimelineCacheMax: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
perUserListTimelineCacheMax: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -316,7 +313,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
private metaService: MetaService,
|
||||
) {
|
||||
super(meta, paramDef, async () => {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const instance = await this.metaService.fetch(true);
|
||||
|
||||
return {
|
||||
@@ -402,10 +399,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||
manifestJsonOverride: instance.manifestJsonOverride,
|
||||
perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax,
|
||||
perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
|
||||
perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
|
||||
perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@@ -108,10 +108,6 @@ export const paramDef = {
|
||||
serverRules: { type: 'array', items: { type: 'string' } },
|
||||
preservedUsernames: { type: 'array', items: { type: 'string' } },
|
||||
manifestJsonOverride: { type: 'string' },
|
||||
perLocalUserUserTimelineCacheMax: { type: 'integer' },
|
||||
perRemoteUserUserTimelineCacheMax: { type: 'integer' },
|
||||
perUserHomeTimelineCacheMax: { type: 'integer' },
|
||||
perUserListTimelineCacheMax: { type: 'integer' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -445,22 +441,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
set.manifestJsonOverride = ps.manifestJsonOverride;
|
||||
}
|
||||
|
||||
if (ps.perLocalUserUserTimelineCacheMax !== undefined) {
|
||||
set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax;
|
||||
}
|
||||
|
||||
if (ps.perRemoteUserUserTimelineCacheMax !== undefined) {
|
||||
set.perRemoteUserUserTimelineCacheMax = ps.perRemoteUserUserTimelineCacheMax;
|
||||
}
|
||||
|
||||
if (ps.perUserHomeTimelineCacheMax !== undefined) {
|
||||
set.perUserHomeTimelineCacheMax = ps.perUserHomeTimelineCacheMax;
|
||||
}
|
||||
|
||||
if (ps.perUserListTimelineCacheMax !== undefined) {
|
||||
set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax;
|
||||
}
|
||||
|
||||
const before = await this.metaService.fetch(true);
|
||||
|
||||
await this.metaService.update(set);
|
||||
|
@@ -79,14 +79,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
let timeline: MiNote[] = [];
|
||||
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
let noteIdsRes: [string, string[]][] = [];
|
||||
|
||||
if (!ps.sinceId && !ps.sinceDate) {
|
||||
noteIdsRes = await this.redisForTimelines.xrevrange(
|
||||
`channelTimeline:${channel.id}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'-',
|
||||
'COUNT', limit);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
timeline = await query.limit(ps.limit).getMany();
|
||||
} else {
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
|
@@ -214,11 +214,11 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
localTimeline: {
|
||||
localTimeLine: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
globalTimeline: {
|
||||
globalTimeLine: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
|
@@ -91,27 +91,25 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
let timeline: MiNote[] = [];
|
||||
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
let htlNoteIdsRes: [string, string[]][] = [];
|
||||
let ltlNoteIdsRes: [string, string[]][] = [];
|
||||
|
||||
if (!ps.sinceId && !ps.sinceDate) {
|
||||
[htlNoteIdsRes, ltlNoteIdsRes] = await Promise.all([
|
||||
this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'COUNT', limit),
|
||||
this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'COUNT', limit),
|
||||
]);
|
||||
htlNoteIdsRes = await this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
'-',
|
||||
'COUNT', limit);
|
||||
ltlNoteIdsRes = await this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
'-',
|
||||
'COUNT', limit);
|
||||
}
|
||||
|
||||
const htlNoteIds = htlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||
const ltlNoteIds = ltlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||
const htlNoteIds = htlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
const ltlNoteIds = ltlNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
|
@@ -87,18 +87,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
let timeline: MiNote[] = [];
|
||||
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
let noteIdsRes: [string, string[]][] = [];
|
||||
|
||||
if (!ps.sinceId && !ps.sinceDate) {
|
||||
noteIdsRes = await this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'-',
|
||||
'COUNT', limit);
|
||||
}
|
||||
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
|
@@ -78,18 +78,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
let timeline: MiNote[] = [];
|
||||
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
let noteIdsRes: [string, string[]][] = [];
|
||||
|
||||
if (!ps.sinceId && !ps.sinceDate) {
|
||||
noteIdsRes = await this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'-',
|
||||
'COUNT', limit);
|
||||
}
|
||||
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
|
89
packages/backend/src/server/api/endpoints/notes/update.ts
Normal file
89
packages/backend/src/server/api/endpoints/notes/update.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UsersRepository, NotesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { NoteDeleteService } from '@/core/NoteDeleteService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canEditNote',
|
||||
|
||||
kind: 'write:notes',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 10,
|
||||
minInterval: ms('1sec'),
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: 'a6584e14-6e01-4ad3-b566-851e7bf0d474',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
noteId: { type: 'string', format: 'misskey:id' },
|
||||
text: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: MAX_NOTE_TEXT_LENGTH,
|
||||
nullable: false,
|
||||
},
|
||||
cw: { type: 'string', nullable: true, maxLength: 100 },
|
||||
},
|
||||
required: ['noteId', 'text', 'cw'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
||||
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (note.userId !== me.id) {
|
||||
throw new ApiError(meta.errors.noSuchNote);
|
||||
}
|
||||
|
||||
await this.notesRepository.update({ id: note.id }, {
|
||||
updatedAt: new Date(),
|
||||
cw: ps.cw,
|
||||
text: ps.text,
|
||||
});
|
||||
|
||||
this.globalEventService.publishNoteStream(note.id, 'updated', {
|
||||
cw: ps.cw,
|
||||
text: ps.text,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -102,18 +102,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
let timeline: MiNote[] = [];
|
||||
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
let noteIdsRes: [string, string[]][] = [];
|
||||
|
||||
if (!ps.sinceId && !ps.sinceDate) {
|
||||
noteIdsRes = await this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'-',
|
||||
'COUNT', limit);
|
||||
}
|
||||
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
|
@@ -13,12 +13,13 @@ import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users', 'notes'],
|
||||
|
||||
description: 'Show all notes that this user created.',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
@@ -44,7 +45,6 @@ export const paramDef = {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
withReplies: { type: 'boolean', default: false },
|
||||
withRenotes: { type: 'boolean', default: true },
|
||||
withChannelNotes: { type: 'boolean', default: false },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
@@ -72,50 +72,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
] = me ? await Promise.all([
|
||||
this.cacheService.userMutingsCache.fetch(me.id),
|
||||
]) : [new Set<string>()];
|
||||
|
||||
let timeline: MiNote[] = [];
|
||||
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
|
||||
let noteIdsRes: [string, string[]][] = [];
|
||||
let repliesNoteIdsRes: [string, string[]][] = [];
|
||||
let channelNoteIdsRes: [string, string[]][] = [];
|
||||
|
||||
if (!ps.sinceId && !ps.sinceDate) {
|
||||
[noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([
|
||||
this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'COUNT', limit),
|
||||
ps.withReplies
|
||||
? this.redisForTimelines.xrevrange(
|
||||
`userTimelineWithReplies:${ps.userId}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'COUNT', limit)
|
||||
: Promise.resolve([]),
|
||||
ps.withChannelNotes
|
||||
? this.redisForTimelines.xrevrange(
|
||||
`userTimelineWithChannel:${ps.userId}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
|
||||
'COUNT', limit)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
noteIdsRes = await this.redisForTimelines.xrevrange(
|
||||
ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : ps.withReplies ? `userTimelineWithReplies:${ps.userId}` : `userTimeline:${ps.userId}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
|
||||
'-',
|
||||
'COUNT', limit);
|
||||
}
|
||||
|
||||
let noteIds = Array.from(new Set([
|
||||
...noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId),
|
||||
...repliesNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId),
|
||||
...channelNoteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId),
|
||||
]));
|
||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||
noteIds = noteIds.slice(0, ps.limit);
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
@@ -135,8 +105,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
timeline = await query.getMany();
|
||||
|
||||
timeline = timeline.filter(note => {
|
||||
if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false;
|
||||
|
||||
if (note.renoteId) {
|
||||
if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
|
||||
if (ps.withRenotes === false) return false;
|
||||
|
@@ -171,9 +171,6 @@ export type ModerationLogPayloads = {
|
||||
deleteUserAnnouncement: {
|
||||
announcementId: string;
|
||||
announcement: any;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
resetPassword: {
|
||||
userId: string;
|
||||
|
@@ -7,18 +7,10 @@ process.env.NODE_ENV = 'test';
|
||||
process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl, randomString } from '../utils.js';
|
||||
import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl } from '../utils.js';
|
||||
import type { INestApplicationContext } from '@nestjs/common';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
function genHost() {
|
||||
return randomString() + '.example.com';
|
||||
}
|
||||
|
||||
function waitForPushToTl() {
|
||||
return sleep(300);
|
||||
}
|
||||
|
||||
let app: INestApplicationContext;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -36,7 +28,7 @@ describe('Timelines', () => {
|
||||
|
||||
const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -48,11 +40,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -64,11 +55,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -81,11 +71,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -98,11 +87,10 @@ describe('Timelines', () => {
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -115,11 +103,10 @@ describe('Timelines', () => {
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -132,11 +119,10 @@ describe('Timelines', () => {
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -150,11 +136,10 @@ describe('Timelines', () => {
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/following/create', { userId: carol.id }, alice);
|
||||
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -169,11 +154,10 @@ describe('Timelines', () => {
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/following/create', { userId: carol.id }, alice);
|
||||
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -185,11 +169,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -203,7 +186,7 @@ describe('Timelines', () => {
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -215,11 +198,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { renoteId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -231,11 +213,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { renoteId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {
|
||||
withRenotes: false,
|
||||
@@ -249,11 +230,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {
|
||||
withRenotes: false,
|
||||
@@ -267,10 +247,9 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -282,11 +261,10 @@ describe('Timelines', () => {
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -300,11 +278,10 @@ describe('Timelines', () => {
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await api('/mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -313,13 +290,12 @@ describe('Timelines', () => {
|
||||
});
|
||||
|
||||
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -327,13 +303,12 @@ describe('Timelines', () => {
|
||||
});
|
||||
|
||||
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
@@ -344,7 +319,6 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const [bobFile, carolFile] = await Promise.all([
|
||||
uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
|
||||
uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
|
||||
@@ -354,7 +328,7 @@ describe('Timelines', () => {
|
||||
const carolNote1 = await post(carol, { text: 'hi' });
|
||||
const carolNote2 = await post(carol, { fileIds: [carolFile.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/timeline', { withFiles: true }, alice);
|
||||
|
||||
@@ -363,21 +337,6 @@ describe('Timelines', () => {
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote1.id), false);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote2.id), false);
|
||||
}, 1000 * 10);
|
||||
|
||||
test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/notes/timeline', {}, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Local TL', () => {
|
||||
@@ -387,7 +346,7 @@ describe('Timelines', () => {
|
||||
const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
@@ -395,25 +354,12 @@ describe('Timelines', () => {
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('チャンネル投稿が含まれない', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
|
||||
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
|
||||
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
@@ -424,12 +370,13 @@ describe('Timelines', () => {
|
||||
test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
await api('/following/create', {
|
||||
userId: carol.id,
|
||||
}, alice);
|
||||
const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
@@ -441,11 +388,10 @@ describe('Timelines', () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
@@ -458,11 +404,10 @@ describe('Timelines', () => {
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
@@ -476,11 +421,10 @@ describe('Timelines', () => {
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await api('/following/update', { userId: bob.id, withReplies: true }, alice);
|
||||
await api('/mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
@@ -495,7 +439,7 @@ describe('Timelines', () => {
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { fileIds: [file.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', { withFiles: true }, alice);
|
||||
|
||||
@@ -510,7 +454,7 @@ describe('Timelines', () => {
|
||||
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
||||
|
||||
@@ -522,7 +466,7 @@ describe('Timelines', () => {
|
||||
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
||||
|
||||
@@ -533,10 +477,9 @@ describe('Timelines', () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
||||
|
||||
@@ -544,11 +487,11 @@ describe('Timelines', () => {
|
||||
});
|
||||
|
||||
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
|
||||
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/local-timeline', {}, alice);
|
||||
|
||||
@@ -556,13 +499,12 @@ describe('Timelines', () => {
|
||||
});
|
||||
|
||||
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
||||
|
||||
@@ -570,13 +512,12 @@ describe('Timelines', () => {
|
||||
});
|
||||
|
||||
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||
const [alice, bob] = await Promise.all([signup(), signup({ host: 'example.com' })]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
||||
|
||||
@@ -590,7 +531,7 @@ describe('Timelines', () => {
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { fileIds: [file.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/hybrid-timeline', { withFiles: true }, alice);
|
||||
|
||||
@@ -605,10 +546,9 @@ describe('Timelines', () => {
|
||||
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -620,10 +560,9 @@ describe('Timelines', () => {
|
||||
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -636,10 +575,9 @@ describe('Timelines', () => {
|
||||
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -652,10 +590,9 @@ describe('Timelines', () => {
|
||||
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -668,11 +605,10 @@ describe('Timelines', () => {
|
||||
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -684,11 +620,10 @@ describe('Timelines', () => {
|
||||
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -702,11 +637,10 @@ describe('Timelines', () => {
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await api('/users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -719,10 +653,9 @@ describe('Timelines', () => {
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -735,10 +668,9 @@ describe('Timelines', () => {
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
|
||||
|
||||
@@ -755,7 +687,7 @@ describe('Timelines', () => {
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { fileIds: [file.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
await sleep(100); // redisに追加されるのを待つ
|
||||
|
||||
const res = await api('/notes/user-list-timeline', { listId: list.id, withFiles: true }, alice);
|
||||
|
||||
@@ -764,166 +696,6 @@ describe('Timelines', () => {
|
||||
}, 1000 * 10);
|
||||
});
|
||||
|
||||
describe('User TL', () => {
|
||||
test.concurrent('ノートが含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
const bobNote = await post(bob, { text: 'hi' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||
});
|
||||
|
||||
test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('/following/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
||||
});
|
||||
|
||||
test.concurrent('チャンネル投稿が含まれない', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
|
||||
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('[withReplies: false] 他人への返信が含まれない', async () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('[withReplies: true] 他人への返信が含まれる', async () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||
});
|
||||
|
||||
test.concurrent('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified' });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { fileIds: [file.id] });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id, withFiles: true }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||
}, 1000 * 10);
|
||||
|
||||
test.concurrent('[withChannelNotes: true] チャンネル投稿が含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
|
||||
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||
});
|
||||
|
||||
test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
|
||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||
|
||||
await api('/mute/create', { userId: carol.id }, alice);
|
||||
await sleep(1000);
|
||||
const carolNote = await post(carol, { text: 'hi' });
|
||||
const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||
});
|
||||
|
||||
test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => {
|
||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||
|
||||
await api('/mute/create', { userId: bob.id }, alice);
|
||||
await sleep(1000);
|
||||
const bobNote1 = await post(bob, { text: 'hi' });
|
||||
const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
|
||||
const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id });
|
||||
|
||||
await waitForPushToTl();
|
||||
|
||||
const res = await api('/users/notes', { userId: bob.id }, alice);
|
||||
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote3.id), true);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: リノートミュート済みユーザーのテスト
|
||||
// TODO: ページネーションのテスト
|
||||
});
|
||||
|
@@ -92,9 +92,6 @@ describe('ActivityPub', () => {
|
||||
const metaInitial = {
|
||||
cacheRemoteFiles: true,
|
||||
cacheRemoteSensitiveFiles: true,
|
||||
perUserHomeTimelineCacheMax: 100,
|
||||
perLocalUserUserTimelineCacheMax: 100,
|
||||
perRemoteUserUserTimelineCacheMax: 100,
|
||||
blockedHosts: [] as string[],
|
||||
sensitiveWords: [] as string[],
|
||||
} as MiMeta;
|
||||
|
@@ -99,7 +99,7 @@ export const relativeFetch = async (path: string, init?: RequestInit | undefined
|
||||
return await fetch(new URL(path, `http://127.0.0.1:${port}/`).toString(), init);
|
||||
};
|
||||
|
||||
export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', length = 16) {
|
||||
function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', length = 16) {
|
||||
let randomString = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
randomString += chars[Math.floor(Math.random() * chars.length)];
|
||||
|
@@ -93,6 +93,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<footer>
|
||||
<div :class="$style.noteFooterInfo">
|
||||
<div v-if="appearNote.updatedAt">
|
||||
{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
|
||||
</div>
|
||||
<MkA :to="notePage(appearNote)">
|
||||
<MkTime :time="appearNote.createdAt" mode="detail"/>
|
||||
</MkA>
|
||||
|
@@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
|
||||
</div>
|
||||
<div :class="$style.info">
|
||||
<span v-if="note.updatedAt" style="margin-right: 0.5em;" :title="i18n.ts.edited"><i class="ti ti-pencil"></i></span>
|
||||
<MkA :to="notePage(note)">
|
||||
<MkTime :time="note.createdAt"/>
|
||||
</MkA>
|
||||
|
@@ -143,6 +143,7 @@ const props = withDefaults(defineProps<{
|
||||
fixed?: boolean;
|
||||
autofocus?: boolean;
|
||||
freezeAfterPosted?: boolean;
|
||||
updateMode?: boolean;
|
||||
}>(), {
|
||||
initialVisibleUsers: () => [],
|
||||
autofocus: true,
|
||||
@@ -709,6 +710,7 @@ async function post(ev?: MouseEvent) {
|
||||
visibility: visibility,
|
||||
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
|
||||
reactionAcceptance,
|
||||
noteId: props.updateMode ? props.initialNote?.id : undefined,
|
||||
};
|
||||
|
||||
if (withHashtags && hashtags && hashtags.trim() !== '') {
|
||||
@@ -731,7 +733,7 @@ async function post(ev?: MouseEvent) {
|
||||
}
|
||||
|
||||
posting = true;
|
||||
os.api('notes/create', postData, token).then(() => {
|
||||
os.api(props.updateMode ? 'notes/update' : 'notes/create', postData, token).then(() => {
|
||||
if (props.freezeAfterPosted) {
|
||||
posted = true;
|
||||
} else {
|
||||
|
@@ -30,6 +30,7 @@ const props = defineProps<{
|
||||
instant?: boolean;
|
||||
fixed?: boolean;
|
||||
autofocus?: boolean;
|
||||
updateMode?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@@ -61,6 +61,7 @@ export const ROLE_POLICIES = [
|
||||
'gtlAvailable',
|
||||
'ltlAvailable',
|
||||
'canPublicNote',
|
||||
'canEditNote',
|
||||
'canInvite',
|
||||
'inviteLimit',
|
||||
'inviteLimitCycle',
|
||||
|
@@ -1,81 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
||||
<FormSuspense :p="init">
|
||||
<FormSection>
|
||||
<template #label>DeepL Translation</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="deeplAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>DeepL Auth Key</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model="deeplIsPro">
|
||||
<template #label>Pro account</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
|
||||
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</MkSpacer>
|
||||
</div>
|
||||
</template>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import XHeader from './_header_.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { fetchInstance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
let deeplAuthKey: string = $ref('');
|
||||
let deeplIsPro: boolean = $ref(false);
|
||||
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
deeplAuthKey = meta.deeplAuthKey;
|
||||
deeplIsPro = meta.deeplIsPro;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
deeplAuthKey,
|
||||
deeplIsPro,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.externalServices,
|
||||
icon: 'ti ti-link',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.footer {
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
}
|
||||
</style>
|
@@ -198,11 +198,6 @@ const menuDef = $computed(() => [{
|
||||
text: i18n.ts.proxyAccount,
|
||||
to: '/admin/proxy-account',
|
||||
active: currentPage?.route.name === 'proxy-account',
|
||||
}, {
|
||||
icon: 'ti ti-link',
|
||||
text: i18n.ts.externalServices,
|
||||
to: '/admin/external-services',
|
||||
active: currentPage?.route.name === 'external-services',
|
||||
}, {
|
||||
icon: 'ti ti-adjustments',
|
||||
text: i18n.ts.other,
|
||||
|
@@ -29,12 +29,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-else-if="log.type === 'unmarkSensitiveDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'suspendRemoteInstance'">: {{ log.info.host }}</span>
|
||||
<span v-else-if="log.type === 'unsuspendRemoteInstance'">: {{ log.info.host }}</span>
|
||||
<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
|
||||
<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
|
||||
<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
|
||||
<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
|
||||
</template>
|
||||
@@ -92,16 +88,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'updateGlobalAnnouncement'">
|
||||
<div :class="$style.diff">
|
||||
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'updateUserAnnouncement'">
|
||||
<div :class="$style.diff">
|
||||
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<details>
|
||||
<summary>raw</summary>
|
||||
|
@@ -160,6 +160,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="role.policies.canEditNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else>{{ role.policies.canEditNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canEditNote)"></i></span>
|
||||
</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="role.policies.canEditNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="role.policies.canEditNote.value" :disabled="role.policies.canEditNote.useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange v-model="role.policies.canEditNote.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
|
||||
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
|
||||
<template #suffix>
|
||||
|
@@ -48,6 +48,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
|
||||
<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
|
||||
<template #suffix>{{ policies.canEditNote ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
<MkSwitch v-model="policies.canEditNote">
|
||||
<template #label>{{ i18n.ts.enable }}</template>
|
||||
</MkSwitch>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
|
||||
<template #label>{{ i18n.ts._role._options.canInvite }}</template>
|
||||
<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||
|
@@ -81,24 +81,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>Timeline caching</template>
|
||||
<template #label>DeepL Translation</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
|
||||
<template #label>perLocalUserUserTimelineCacheMax</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number">
|
||||
<template #label>perRemoteUserUserTimelineCacheMax</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="perUserHomeTimelineCacheMax" type="number">
|
||||
<template #label>perUserHomeTimelineCacheMax</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="perUserListTimelineCacheMax" type="number">
|
||||
<template #label>perUserListTimelineCacheMax</template>
|
||||
<MkInput v-model="deeplAuthKey">
|
||||
<template #prefix><i class="ti ti-key"></i></template>
|
||||
<template #label>DeepL Auth Key</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model="deeplIsPro">
|
||||
<template #label>Pro account</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
@@ -141,10 +133,8 @@ let cacheRemoteSensitiveFiles: boolean = $ref(false);
|
||||
let enableServiceWorker: boolean = $ref(false);
|
||||
let swPublicKey: any = $ref(null);
|
||||
let swPrivateKey: any = $ref(null);
|
||||
let perLocalUserUserTimelineCacheMax: number = $ref(0);
|
||||
let perRemoteUserUserTimelineCacheMax: number = $ref(0);
|
||||
let perUserHomeTimelineCacheMax: number = $ref(0);
|
||||
let perUserListTimelineCacheMax: number = $ref(0);
|
||||
let deeplAuthKey: string = $ref('');
|
||||
let deeplIsPro: boolean = $ref(false);
|
||||
|
||||
async function init(): Promise<void> {
|
||||
const meta = await os.api('admin/meta');
|
||||
@@ -159,10 +149,8 @@ async function init(): Promise<void> {
|
||||
enableServiceWorker = meta.enableServiceWorker;
|
||||
swPublicKey = meta.swPublickey;
|
||||
swPrivateKey = meta.swPrivateKey;
|
||||
perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax;
|
||||
perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax;
|
||||
perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax;
|
||||
perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax;
|
||||
deeplAuthKey = meta.deeplAuthKey;
|
||||
deeplIsPro = meta.deeplIsPro;
|
||||
}
|
||||
|
||||
function save(): void {
|
||||
@@ -178,10 +166,8 @@ function save(): void {
|
||||
enableServiceWorker,
|
||||
swPublicKey,
|
||||
swPrivateKey,
|
||||
perLocalUserUserTimelineCacheMax,
|
||||
perRemoteUserUserTimelineCacheMax,
|
||||
perUserHomeTimelineCacheMax,
|
||||
perUserListTimelineCacheMax,
|
||||
deeplAuthKey,
|
||||
deeplIsPro,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
|
@@ -83,8 +83,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #value><code class="_monospace">{{ code }}</code></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
|
||||
<MkButton primary rounded gradate @click="downloadBackupCodes"><i class="ti ti-download"></i> {{ i18n.ts.download }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
@@ -110,7 +108,6 @@ import * as os from '@/os.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { confetti } from '@/scripts/confetti.js';
|
||||
import { $i } from '@/account.js';
|
||||
|
||||
defineProps<{
|
||||
twoFactorData: {
|
||||
@@ -146,16 +143,6 @@ async function tokenDone() {
|
||||
});
|
||||
}
|
||||
|
||||
function downloadBackupCodes() {
|
||||
if (backupCodes.value !== undefined) {
|
||||
const txtBlob = new Blob([backupCodes.value.join('\n')], { type: 'text/plain' });
|
||||
const dummya = document.createElement('a');
|
||||
dummya.href = URL.createObjectURL(txtBlob);
|
||||
dummya.download = `${$i?.username}-2fa-backup-codes.txt`;
|
||||
dummya.click();
|
||||
}
|
||||
}
|
||||
|
||||
function allDone() {
|
||||
dialog.value.close();
|
||||
}
|
||||
|
@@ -139,11 +139,21 @@ const menuDef = computed(() => [{
|
||||
text: i18n.ts.roles,
|
||||
to: '/settings/roles',
|
||||
active: currentPage?.route.name === 'roles',
|
||||
}, {
|
||||
icon: 'ti ti-planet-off',
|
||||
text: i18n.ts.instanceMute,
|
||||
to: '/settings/instance-mute',
|
||||
active: currentPage?.route.name === 'instance-mute',
|
||||
}, {
|
||||
icon: 'ti ti-ban',
|
||||
text: i18n.ts.muteAndBlock,
|
||||
to: '/settings/mute-block',
|
||||
active: currentPage?.route.name === 'mute-block',
|
||||
}, {
|
||||
icon: 'ti ti-message-off',
|
||||
text: i18n.ts.wordMute,
|
||||
to: '/settings/word-mute',
|
||||
active: currentPage?.route.name === 'word-mute',
|
||||
}, {
|
||||
icon: 'ti ti-api',
|
||||
text: 'API',
|
||||
|
@@ -22,6 +22,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
const instanceMutes = ref($i!.mutedInstances.join('\n'));
|
||||
const changed = ref(false);
|
||||
@@ -45,4 +46,13 @@ async function save() {
|
||||
watch(instanceMutes, () => {
|
||||
changed.value = true;
|
||||
});
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.instanceMute,
|
||||
icon: 'ti ti-planet-off',
|
||||
});
|
||||
</script>
|
@@ -5,20 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.wordMute }}</template>
|
||||
|
||||
<XWordMute/>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-planet-off"></i></template>
|
||||
<template #label>{{ i18n.ts.instanceMute }}</template>
|
||||
|
||||
<XInstanceMute/>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-repeat-off"></i></template>
|
||||
<template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
|
||||
@@ -120,8 +106,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import XInstanceMute from './mute-block.instance-mute.vue';
|
||||
import XWordMute from './mute-block.word-mute.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
@@ -38,12 +38,14 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
|
||||
const masterVolume = computed(soundConfigStore.makeGetterSetter('sound_masterVolume'));
|
||||
|
||||
const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel'] as const;
|
||||
const soundsKeys = ['note', 'noteMy', 'notification', 'chat', 'chatBg', 'antenna', 'channel'] as const;
|
||||
|
||||
const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
|
||||
note: soundConfigStore.reactiveState.sound_note,
|
||||
noteMy: soundConfigStore.reactiveState.sound_noteMy,
|
||||
notification: soundConfigStore.reactiveState.sound_notification,
|
||||
chat: soundConfigStore.reactiveState.sound_chat,
|
||||
chatBg: soundConfigStore.reactiveState.sound_chatBg,
|
||||
antenna: soundConfigStore.reactiveState.sound_antenna,
|
||||
channel: soundConfigStore.reactiveState.sound_channel,
|
||||
});
|
||||
|
@@ -91,4 +91,13 @@ async function save() {
|
||||
|
||||
changed.value = false;
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
const headerTabs = $computed(() => []);
|
||||
|
||||
definePageMetadata({
|
||||
title: i18n.ts.wordMute,
|
||||
icon: 'ti ti-message-off',
|
||||
});
|
||||
</script>
|
@@ -29,7 +29,7 @@ const props = defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
}>();
|
||||
|
||||
const include = ref<string | null>('all');
|
||||
const include = ref<string | null>(null);
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'users/notes' as const,
|
||||
@@ -38,7 +38,6 @@ const pagination = {
|
||||
userId: props.user.id,
|
||||
withRenotes: include.value === 'all',
|
||||
withReplies: include.value === 'all' || include.value === 'files',
|
||||
withChannelNotes: include.value === 'all',
|
||||
withFiles: include.value === 'files',
|
||||
})),
|
||||
};
|
||||
|
@@ -126,10 +126,18 @@ export const routes = [{
|
||||
path: '/import-export',
|
||||
name: 'import-export',
|
||||
component: page(() => import('./pages/settings/import-export.vue')),
|
||||
}, {
|
||||
path: '/instance-mute',
|
||||
name: 'instance-mute',
|
||||
component: page(() => import('./pages/settings/instance-mute.vue')),
|
||||
}, {
|
||||
path: '/mute-block',
|
||||
name: 'mute-block',
|
||||
component: page(() => import('./pages/settings/mute-block.vue')),
|
||||
}, {
|
||||
path: '/word-mute',
|
||||
name: 'word-mute',
|
||||
component: page(() => import('./pages/settings/word-mute.vue')),
|
||||
}, {
|
||||
path: '/api',
|
||||
name: 'api',
|
||||
@@ -427,10 +435,6 @@ export const routes = [{
|
||||
path: '/proxy-account',
|
||||
name: 'proxy-account',
|
||||
component: page(() => import('./pages/admin/proxy-account.vue')),
|
||||
}, {
|
||||
path: '/external-services',
|
||||
name: 'external-services',
|
||||
component: page(() => import('./pages/admin/external-services.vue')),
|
||||
}, {
|
||||
path: '/other-settings',
|
||||
name: 'other-settings',
|
||||
|
@@ -172,6 +172,10 @@ export function getNoteMenu(props: {
|
||||
});
|
||||
}
|
||||
|
||||
function edit(): void {
|
||||
os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel, updateMode: true });
|
||||
}
|
||||
|
||||
function toggleFavorite(favorite: boolean): void {
|
||||
claimAchievement('noteFavorited1');
|
||||
os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
|
||||
@@ -352,6 +356,11 @@ export function getNoteMenu(props: {
|
||||
),
|
||||
...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
|
||||
null,
|
||||
appearNote.userId === $i.id && $i.policies.canEditNote ? {
|
||||
icon: 'ti ti-edit',
|
||||
text: i18n.ts.edit,
|
||||
action: edit,
|
||||
} : undefined,
|
||||
appearNote.userId === $i.id ? {
|
||||
icon: 'ti ti-edit',
|
||||
text: i18n.ts.deleteAndEdit,
|
||||
|
@@ -27,6 +27,14 @@ export const soundConfigStore = markRaw(new Storage('sound', {
|
||||
where: 'account',
|
||||
default: { type: 'syuilo/n-ea', volume: 1 },
|
||||
},
|
||||
sound_chat: {
|
||||
where: 'account',
|
||||
default: { type: 'syuilo/pope1', volume: 1 },
|
||||
},
|
||||
sound_chatBg: {
|
||||
where: 'account',
|
||||
default: { type: 'syuilo/waon', volume: 1 },
|
||||
},
|
||||
sound_antenna: {
|
||||
where: 'account',
|
||||
default: { type: 'syuilo/triple', volume: 1 },
|
||||
|
@@ -71,6 +71,13 @@ export function useNoteCapture(props: {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updated': {
|
||||
note.value.updatedAt = new Date().toISOString();
|
||||
note.value.cw = body.cw;
|
||||
note.value.text = body.text;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'deleted': {
|
||||
props.isDeletedRef.value = true;
|
||||
break;
|
||||
|
@@ -9,12 +9,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span>
|
||||
</template>
|
||||
|
||||
<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/>
|
||||
<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId"/>
|
||||
</XColumn>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
import { } from 'vue';
|
||||
import XColumn from './column.vue';
|
||||
import { updateColumn, Column } from './deck-store';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
@@ -27,18 +27,11 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
|
||||
const withRenotes = $ref(props.column.withRenotes ?? true);
|
||||
|
||||
if (props.column.listId == null) {
|
||||
setList();
|
||||
}
|
||||
|
||||
watch($$(withRenotes), v => {
|
||||
updateColumn(props.column.id, {
|
||||
withRenotes: v,
|
||||
});
|
||||
});
|
||||
|
||||
async function setList() {
|
||||
const lists = await os.api('users/lists/list');
|
||||
const { canceled, result: list } = await os.select({
|
||||
@@ -69,10 +62,5 @@ const menu = [
|
||||
text: i18n.ts.editList,
|
||||
action: editList,
|
||||
},
|
||||
{
|
||||
type: 'switch',
|
||||
text: i18n.ts.showRenotes,
|
||||
ref: $$(withRenotes),
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
@@ -2639,6 +2639,7 @@ export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
||||
type Note = {
|
||||
id: ID;
|
||||
createdAt: DateString;
|
||||
updatedAt?: DateString | null;
|
||||
text: string | null;
|
||||
cw: string | null;
|
||||
user: User;
|
||||
@@ -2978,7 +2979,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:594:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:595:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
@@ -189,9 +189,6 @@ export type ModerationLogPayloads = {
|
||||
deleteUserAnnouncement: {
|
||||
announcementId: string;
|
||||
announcement: any;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
resetPassword: {
|
||||
userId: string;
|
||||
|
@@ -177,6 +177,7 @@ export type GalleryPost = {
|
||||
export type Note = {
|
||||
id: ID;
|
||||
createdAt: DateString;
|
||||
updatedAt?: DateString | null;
|
||||
text: string | null;
|
||||
cw: string | null;
|
||||
user: User;
|
||||
|
@@ -133,6 +133,13 @@ export type NoteUpdatedEvent = {
|
||||
body: {
|
||||
deletedAt: string;
|
||||
};
|
||||
} | {
|
||||
id: Note['id'];
|
||||
type: 'updated';
|
||||
body: {
|
||||
cw: string | null;
|
||||
text: string;
|
||||
};
|
||||
} | {
|
||||
id: Note['id'];
|
||||
type: 'pollVoted';
|
||||
|
Reference in New Issue
Block a user