Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
This commit is contained in:
		
						commit
						aba06b4ef9
					
				
					 26 changed files with 92 additions and 213 deletions
				
			
		|  | @ -21,6 +21,12 @@ You should also include the user name that made the change. | |||
| - 新たに動的なPagesを作ることはできなくなりました | ||||
| 	- 代わりに今後AiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能の実装を予定しています。 | ||||
| - iOS15以下のデバイスはサポートされなくなりました | ||||
| - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました | ||||
| 	- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。 | ||||
| 	- e.g. `https://p1.a9z.dev/emoji/misskey.webp` | ||||
| 	- remote: `https://p1.a9z.dev/emoji/syuilo_birth_present@mk.f72u.net.webp` | ||||
| - API: `user`および`note`エンティティに`emojis`プロパティが含まれなくなりました | ||||
| - API: `user`エンティティに`avatarColor`および`bannerColor`プロパティが含まれなくなりました | ||||
| 
 | ||||
| ### Improvements | ||||
| - Push notification of Antenna note @tamaina | ||||
|  |  | |||
|  | @ -914,6 +914,8 @@ windowMaximize: "Maximieren" | |||
| windowRestore: "Wiederherstellen" | ||||
| caption: "Beschreibung" | ||||
| loggedInAsBot: "Momentan als Bot angemeldet" | ||||
| tools: "Werkzeuge" | ||||
| cannotLoad: "Kann nicht geladen werden" | ||||
| _sensitiveMediaDetection: | ||||
|   description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht." | ||||
|   sensitivity: "Erkennungssensitivität" | ||||
|  |  | |||
|  | @ -914,6 +914,8 @@ windowMaximize: "Maximize" | |||
| windowRestore: "Restore" | ||||
| caption: "Caption" | ||||
| loggedInAsBot: "Currently logged in as bot" | ||||
| tools: "Tools" | ||||
| cannotLoad: "Unable to load" | ||||
| _sensitiveMediaDetection: | ||||
|   description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server." | ||||
|   sensitivity: "Detection sensitivity" | ||||
|  |  | |||
|  | @ -49,6 +49,7 @@ deleteAndEdit: "ほかして直す" | |||
| deleteAndEditConfirm: "このノートをほかして書き直すんか?このノートへのリアクション、Renote、返信も全部消えてまうで。" | ||||
| addToList: "リストに入れたる" | ||||
| sendMessage: "メッセージを送る" | ||||
| copyRSS: "RSSをコピー" | ||||
| copyUsername: "ユーザー名をコピー" | ||||
| searchUser: "ユーザーを検索" | ||||
| reply: "返事" | ||||
|  | @ -456,6 +457,8 @@ language: "言語" | |||
| uiLanguage: "UIの表示言語" | ||||
| groupInvited: "グループに招待されとるで" | ||||
| aboutX: "{x}について" | ||||
| emojiStyle: "絵文字のスタイル" | ||||
| native: "ネイティブ" | ||||
| disableDrawer: "メニューをドロワーで表示せぇへん" | ||||
| youHaveNoGroups: "グループがあらへんねぇ。" | ||||
| joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループ作ってからやってな" | ||||
|  | @ -714,6 +717,7 @@ accentColor: "アクセント" | |||
| textColor: "文字" | ||||
| saveAs: "名前を付けて保存" | ||||
| advanced: "高度" | ||||
| advancedSettings: "高度な設定" | ||||
| value: "値" | ||||
| createdAt: "作成した日" | ||||
| updatedAt: "更新日時" | ||||
|  | @ -910,6 +914,8 @@ windowMaximize: "最大化" | |||
| windowRestore: "元に戻す" | ||||
| caption: "キャプション" | ||||
| loggedInAsBot: "Botアカウントでログイン中やで" | ||||
| tools: "ツール" | ||||
| cannotLoad: "読み込めへんで" | ||||
| _sensitiveMediaDetection: | ||||
|   description: "機械学習を使って自動でセンシティブなメディアを検出して、モデレーションに役立てることができるで。サーバーの負荷が少し増えてまうなあ。" | ||||
|   sensitivity: "検出感度やで" | ||||
|  | @ -1310,6 +1316,7 @@ _widgets: | |||
|   serverMetric: "サーバーメトリクス" | ||||
|   aiscript: "AiScriptコンソール" | ||||
|   aichan: "藍" | ||||
|   userList: "ユーザーリスト" | ||||
|   _userList: | ||||
|     chooseList: "リストを選ぶ" | ||||
| _cw: | ||||
|  |  | |||
|  | @ -717,6 +717,7 @@ accentColor: "강조 색상" | |||
| textColor: "문자 색" | ||||
| saveAs: "다른 이름으로 저장" | ||||
| advanced: "고급" | ||||
| advancedSettings: "고급 설정" | ||||
| value: "값" | ||||
| createdAt: "생성된 날짜" | ||||
| updatedAt: "수정한 날짜" | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| _lang_: "Polski" | ||||
| headlineMisskey: "Sieć połączona wpisami" | ||||
| introMisskey: "Misskey jest serwisem mikroblogowym typu open source.\nMisskey to opensource'owy serwis mikroblogowy, w którym możesz tworzyć \"notatki\", aby dzielić się tym, co się dzieje i opowiadać wszystkim o sobie.\nMożesz również użyć funkcji \"Reakcje\", aby szybko dodać własne reakcje do notatek innych użytkowników👍.\nOdkrywaj nowy świat🚀!" | ||||
| poweredByMisskeyDescription: "{name} jest jedną z usług działającą na otwartoźródłowej platformie <b>Misskey</b> (określana jako \"instancja Misskey\")." | ||||
| monthAndDay: "{month}-{day}" | ||||
| search: "Szukaj" | ||||
| notifications: "Powiadomienia" | ||||
|  | @ -48,6 +49,7 @@ deleteAndEdit: "Usuń i edytuj" | |||
| deleteAndEditConfirm: "Czy na pewno chcesz usunąć ten wpis i zedytować go? Utracisz wszystkie reakcje, udostępnienia i odpowiedzi do tego wpisu." | ||||
| addToList: "Dodaj do listy" | ||||
| sendMessage: "Wyślij wiadomość" | ||||
| copyRSS: "Kopiuj RSS" | ||||
| copyUsername: "Kopiuj nazwę użytkownika" | ||||
| searchUser: "Wyszukiwanie użytkowników" | ||||
| reply: "Odpowiedz" | ||||
|  | @ -346,6 +348,8 @@ recaptcha: "reCAPTCHA" | |||
| enableRecaptcha: "Włącz reCAPTCHA" | ||||
| recaptchaSiteKey: "Klucz strony" | ||||
| recaptchaSecretKey: "Tajny klucz" | ||||
| turnstile: "Turnstile" | ||||
| enableTurnstile: "Włącz Turnstile" | ||||
| turnstileSiteKey: "Klucz strony" | ||||
| turnstileSecretKey: "Tajny klucz" | ||||
| avoidMultiCaptchaConfirm: "Używanie wielu Captchy może spowodować zakłócenia. Czy chcesz wyłączyć inną Captchę? Możesz zostawić wiele jednocześnie, klikając Anuluj." | ||||
|  | @ -450,6 +454,8 @@ language: "Język" | |||
| uiLanguage: "Język wyświetlania UI" | ||||
| groupInvited: "Zaproszony(-a) do grupy" | ||||
| aboutX: "O {x}" | ||||
| emojiStyle: "Styl emoji" | ||||
| native: "Natywny" | ||||
| disableDrawer: "Nie używaj menu w stylu szuflady" | ||||
| youHaveNoGroups: "Nie masz żadnych grup" | ||||
| joinOrCreateGroup: "Uzyskaj zaproszenie do dołączenia do grupy lub utwórz własną grupę." | ||||
|  | @ -824,7 +830,16 @@ size: "Rozmiar" | |||
| numberOfColumn: "Liczba kolumn" | ||||
| searchByGoogle: "Szukaj" | ||||
| indefinitely: "Nigdy" | ||||
| tenMinutes: "10 minut" | ||||
| oneHour: "1 godzina" | ||||
| oneDay: "1 dzień" | ||||
| oneWeek: "1 tydzień" | ||||
| file: "Pliki" | ||||
| recommended: "Zalecane" | ||||
| check: "Zweryfikuj" | ||||
| deleteAccount: "Usuń konto" | ||||
| document: "Dokumentacja" | ||||
| numberOfPageCache: "Ilość stron w cache" | ||||
| logoutConfirm: "Czy na pewno chcesz się wylogować?" | ||||
| lastActiveDate: "Ostatnio użyte w" | ||||
| statusbar: "Pasek stanu" | ||||
|  |  | |||
|  | @ -49,6 +49,7 @@ deleteAndEdit: "Odstrániť a upraviť" | |||
| deleteAndEditConfirm: "Naozaj chcete odstrániť túto poznámku a upraviť ju? Stratíte tým všetky reakcie a odpovede na ňu." | ||||
| addToList: "Pridať do zoznamu" | ||||
| sendMessage: "Odoslať správu" | ||||
| copyRSS: "Kopírovať RSS" | ||||
| copyUsername: "Kopírovať meno používateľa" | ||||
| searchUser: "Hľadať používateľov" | ||||
| reply: "Odpovedať" | ||||
|  | @ -456,6 +457,8 @@ language: "Jazyk" | |||
| uiLanguage: "Jazyk používateľského prostredia" | ||||
| groupInvited: "Pozvať do skupiny" | ||||
| aboutX: "O {x}" | ||||
| emojiStyle: "Štýl emoji" | ||||
| native: "Natívne" | ||||
| disableDrawer: "Nepoužívať šuflíkové menu" | ||||
| youHaveNoGroups: "Nemáte žiadne skupiny" | ||||
| joinOrCreateGroup: "Požiadajte o pozvanie do existujúcej skupiny alebo vytvorte novú." | ||||
|  | @ -713,6 +716,7 @@ accentColor: "Akcent" | |||
| textColor: "Text" | ||||
| saveAs: "Uložiť ako..." | ||||
| advanced: "Rozšírené" | ||||
| advancedSettings: "Rozšírené nastavenia" | ||||
| value: "Hodnoty" | ||||
| createdAt: "Vytvorené" | ||||
| updatedAt: "Upravené" | ||||
|  | @ -906,6 +910,8 @@ sendPushNotificationReadMessageCaption: "Na chvíľu sa zobrazí oznámenie \"{e | |||
| windowMaximize: "Maximalizovať" | ||||
| windowRestore: "Obnoviť" | ||||
| caption: "Nadpis" | ||||
| tools: "Nástroje" | ||||
| cannotLoad: "Nedá sa načítať." | ||||
| _sensitiveMediaDetection: | ||||
|   description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera." | ||||
|   sensitivity: "Citlivosť detekcie" | ||||
|  |  | |||
|  | @ -164,7 +164,7 @@ host: "โฮสต์" | |||
| selectUser: "เลือกผู้ใช้งาน" | ||||
| recipient: "ผู้รับ" | ||||
| annotation: "ความคิดเห็น" | ||||
| federation: "สหพันธ์" | ||||
| federation: "เฟดิเวิร์ส" | ||||
| instances: "ตัวอย่าง" | ||||
| registeredAt: "จดทะเบียนที่" | ||||
| latestRequestSentAt: "ส่งคำขอล่าสุดไปแล้ว" | ||||
|  | @ -228,10 +228,10 @@ newPassword: "รหัสผ่านใหม่" | |||
| newPasswordRetype: "ใส่รหัสผ่านใหม่อีกครั้ง" | ||||
| attachFile: "แนบไฟล์" | ||||
| more: "เพิ่มเติม!" | ||||
| featured: "เป็นจุดเด่น" | ||||
| featured: "ไฮไลท์" | ||||
| usernameOrUserId: "ชื่อผู้ใช้หรือรหัสผู้ใช้งาน" | ||||
| noSuchUser: "ไม่มีผู้ใช้นี้อยู่ในระบบ" | ||||
| lookup: "ค้นหา" | ||||
| lookup: "การค้นหา" | ||||
| announcements: "ประกาศ" | ||||
| imageUrl: "url รูปภาพ" | ||||
| remove: "ลบ" | ||||
|  | @ -914,6 +914,8 @@ windowMaximize: "ขยายใหญ่สุดแล้ว" | |||
| windowRestore: "เลิกทำ" | ||||
| caption: "รายละเอียด" | ||||
| loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้" | ||||
| tools: "เครื่องมือ" | ||||
| cannotLoad: "ไม่สามารถโหลดได้" | ||||
| _sensitiveMediaDetection: | ||||
|   description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย" | ||||
|   sensitivity: "การตรวจจับความไว" | ||||
|  |  | |||
|  | @ -1019,6 +1019,8 @@ _mfm: | |||
|   font: "Шрифт" | ||||
|   fontDescription: "Встановлює шрифт для контенту." | ||||
|   rotate: "Обертати" | ||||
|   plain: "Звичайний" | ||||
|   plainDescription: "Деактивує всі ефекти MFM, що містяться в цьому ефекті MFM." | ||||
| _instanceTicker: | ||||
|   none: "Не відображати" | ||||
|   remote: "Відображати для віддалених користувачів" | ||||
|  | @ -1053,6 +1055,7 @@ _wordMute: | |||
| _instanceMute: | ||||
|   instanceMuteDescription2: "Розділяйте новими рядками" | ||||
|   title: "Приховує нотатки з перелічених інстансів." | ||||
|   heading: "Список заглушених інстансів" | ||||
| _theme: | ||||
|   explore: "Оглянути теми" | ||||
|   install: "Встановити тему" | ||||
|  | @ -1070,7 +1073,10 @@ _theme: | |||
|   color: "Колір" | ||||
|   key: "Ключ" | ||||
|   func: "Функції" | ||||
|   funcKind: "Тип функції" | ||||
|   argument: "Аргумент" | ||||
|   alpha: "Непрозорість" | ||||
|   darken: "Затемнення" | ||||
|   lighten: "Яскравість" | ||||
|   inputConstantName: "Введіть назву константи" | ||||
|   importInfo: "Вставляючи сюди код теми, ви можете добавити її до редактору тем" | ||||
|  | @ -1165,10 +1171,17 @@ _tutorial: | |||
|   step7_1: "Вітаю! Ви пройшли ознайомлення з Misskey." | ||||
|   step7_2: "Якщо ви хочете більше дізнатись про Misskey, зайдіть в розділ {help}." | ||||
|   step7_3: "Насолоджуйтесь Misskey! 🚀" | ||||
|   step8_1: "Наостанку, чи бажаєте ви ввімкнути push-сповіщення?" | ||||
|   step8_3: "Ви завжди можете змінити цей параметр пізніше." | ||||
| _2fa: | ||||
|   alreadyRegistered: "Двофакторна автентифікація вже налаштована." | ||||
|   registerDevice: "Зареєструвати новий пристрій" | ||||
|   registerKey: "Зареєструвати новий ключ безпеки" | ||||
|   step1: "Спершу встановіть на свій пристрій програму автентифікації (наприклад {a} або {b})." | ||||
|   step2: "Потім відскануйте QR-код, який відображається на цьому екрані." | ||||
|   step2Url: "Ви також можете ввести цю URL-адресу, якщо використовуєте програму для ПК:" | ||||
|   step3: "Щоб завершити налаштування, введіть токен, наданий вашою програмою." | ||||
|   step4: "Відтепер будь-які майбутні спроби входу вимагатимуть такого токена." | ||||
| _permissions: | ||||
|   "read:account": "Переглядати дані профілю" | ||||
|   "write:account": "Змінити дані акаунту" | ||||
|  | @ -1186,6 +1199,7 @@ _permissions: | |||
|   "write:mutes": "Змінювати список ігнорованих" | ||||
|   "write:notes": "Писати і видаляти нотатки" | ||||
|   "read:notifications": "Переглядати сповіщення" | ||||
|   "write:notifications": "Керування сповіщеннями" | ||||
|   "read:reactions": "Переглядати реакції" | ||||
|   "write:reactions": "Змінювати реакції" | ||||
|   "write:votes": "Голосувати в опитуваннях" | ||||
|  | @ -1224,7 +1238,9 @@ _widgets: | |||
|   activity: "Активність" | ||||
|   photos: "Фото" | ||||
|   digitalClock: "Цифровий годинник" | ||||
|   unixClock: "Unix-годинник" | ||||
|   federation: "Федіверс" | ||||
|   instanceCloud: "Хмара інстансів" | ||||
|   postForm: "Створення нотатки" | ||||
|   slideshow: "Слайд-шоу" | ||||
|   button: "Кнопка" | ||||
|  | @ -1232,6 +1248,8 @@ _widgets: | |||
|   jobQueue: "Черга завдань" | ||||
|   serverMetric: "Показники сервера " | ||||
|   aiscript: "Консоль AiScript" | ||||
|   aichan: "Ai" | ||||
|   userList: "Список користувачів" | ||||
|   _userList: | ||||
|     chooseList: "Виберіть список" | ||||
| _cw: | ||||
|  | @ -1301,16 +1319,23 @@ _exportOrImport: | |||
|   muteList: "Ігнорувати" | ||||
|   blockingList: "Заблокувати" | ||||
|   userLists: "Списки" | ||||
|   excludeMutingUsers: "Виключити ігнорованих користувачів" | ||||
|   excludeInactiveUsers: "Виключити неактивних користувачів" | ||||
| _charts: | ||||
|   federation: "Федіверс" | ||||
|   apRequest: "Запити" | ||||
|   usersIncDec: "Зміни кількості користувачів" | ||||
|   usersTotal: "Загальна кількість користувачів" | ||||
|   activeUsers: "Активні користувачі" | ||||
|   notesIncDec: "Зміни кількості нотаток" | ||||
|   localNotesIncDec: "Зміни кількості локальних нотаток" | ||||
|   remoteNotesIncDec: "Зміни кількості віддалених нотаток" | ||||
|   notesTotal: "Загальна кількість нотаток" | ||||
|   filesIncDec: "Зміни кількості файлів" | ||||
|   filesTotal: "Загальна кількість файлів" | ||||
| _instanceCharts: | ||||
|   requests: "Запити" | ||||
|   users: "Зміни кількості користувачів" | ||||
|   usersTotal: "Сумарна кількість користувачів" | ||||
|   notes: "Різниця кількості зроблених записів" | ||||
|   notesTotal: "Сумарна кількість нотаток" | ||||
|  |  | |||
|  | @ -914,6 +914,7 @@ windowMaximize: "最大化" | |||
| windowRestore: "还原" | ||||
| caption: "标题" | ||||
| loggedInAsBot: "已登录的Bot" | ||||
| tools: "工具" | ||||
| _sensitiveMediaDetection: | ||||
|   description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。" | ||||
|   sensitivity: "检测敏感度" | ||||
|  |  | |||
|  | @ -717,6 +717,7 @@ accentColor: "重點色彩" | |||
| textColor: "文字" | ||||
| saveAs: "另存為..." | ||||
| advanced: "進階" | ||||
| advancedSettings: "進階設定" | ||||
| value: "數值" | ||||
| createdAt: "建立於" | ||||
| updatedAt: "最後更新" | ||||
|  | @ -913,6 +914,7 @@ windowMaximize: "最大化" | |||
| windowRestore: "復原" | ||||
| caption: "標題" | ||||
| loggedInAsBot: "以機器人帳號登入中" | ||||
| tools: "工具" | ||||
| _sensitiveMediaDetection: | ||||
|   description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" | ||||
|   sensitivity: "檢測敏感度" | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
| 	"name": "misskey", | ||||
| 	"version": "13.0.0-beta.8", | ||||
| 	"version": "13.0.0-beta.10", | ||||
| 	"codename": "indigo", | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
|  |  | |||
|  | @ -1,29 +1,14 @@ | |||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import { DataSource, In, IsNull } from 'typeorm'; | ||||
| import { GlobalEventService } from '@/core/GlobalEventService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import type { DriveFile } from '@/models/entities/DriveFile.js'; | ||||
| import type { Emoji } from '@/models/entities/Emoji.js'; | ||||
| import { Cache } from '@/misc/cache.js'; | ||||
| import type { Note } from '@/models/entities/Note.js'; | ||||
| import type { EmojisRepository } from '@/models/index.js'; | ||||
| import { UtilityService } from '@/core/UtilityService.js'; | ||||
| import { ReactionService } from '@/core/ReactionService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| 
 | ||||
| /** | ||||
|  * 添付用絵文字情報 | ||||
|  */ | ||||
| type PopulatedEmoji = { | ||||
| 	name: string; | ||||
| 	url: string; | ||||
| }; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class CustomEmojiService { | ||||
| 	private cache: Cache<Emoji | null>; | ||||
| 
 | ||||
| 	constructor( | ||||
| 		@Inject(DI.db) | ||||
| 		private db: DataSource, | ||||
|  | @ -32,11 +17,7 @@ export class CustomEmojiService { | |||
| 		private emojisRepository: EmojisRepository, | ||||
| 
 | ||||
| 		private idService: IdService, | ||||
| 		private globalEventServie: GlobalEventService, | ||||
| 		private utilityService: UtilityService, | ||||
| 		private reactionService: ReactionService, | ||||
| 	) { | ||||
| 		this.cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
|  | @ -63,117 +44,4 @@ export class CustomEmojiService { | |||
| 
 | ||||
| 		return emoji; | ||||
| 	} | ||||
| 	 | ||||
| 	@bindThis | ||||
| 	private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null { | ||||
| 	// クエリに使うホスト
 | ||||
| 		let host = src === '.' ? null	// .はローカルホスト (ここがマッチするのはリアクションのみ)
 | ||||
| 			: src === undefined ? noteUserHost	// ノートなどでホスト省略表記の場合はローカルホスト (ここがリアクションにマッチすることはない)
 | ||||
| 			: this.utilityService.isSelfHost(src) ? null	// 自ホスト指定
 | ||||
| 			: (src || noteUserHost);	// 指定されたホスト || ノートなどの所有者のホスト (こっちがリアクションにマッチすることはない)
 | ||||
| 
 | ||||
| 		host = this.utilityService.toPunyNullable(host); | ||||
| 
 | ||||
| 		return host; | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	private parseEmojiStr(emojiName: string, noteUserHost: string | null) { | ||||
| 		const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/); | ||||
| 		if (!match) return { name: null, host: null }; | ||||
| 
 | ||||
| 		const name = match[1]; | ||||
| 
 | ||||
| 		// ホスト正規化
 | ||||
| 		const host = this.utilityService.toPunyNullable(this.normalizeHost(match[2], noteUserHost)); | ||||
| 
 | ||||
| 		return { name, host }; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  * 添付用絵文字情報を解決する | ||||
|  * @param emojiName ノートやユーザープロフィールに添付された、またはリアクションのカスタム絵文字名 (:は含めない, リアクションでローカルホストの場合は@.を付ける (これはdecodeReactionで可能)) | ||||
|  * @param noteUserHost ノートやユーザープロフィールの所有者のホスト | ||||
|  * @returns 絵文字情報, nullは未マッチを意味する | ||||
|  */ | ||||
| 	@bindThis | ||||
| 	public async populateEmoji(emojiName: string, noteUserHost: string | null): Promise<PopulatedEmoji | null> { | ||||
| 		const { name, host } = this.parseEmojiStr(emojiName, noteUserHost); | ||||
| 		if (name == null) return null; | ||||
| 
 | ||||
| 		const queryOrNull = async () => (await this.emojisRepository.findOneBy({ | ||||
| 			name, | ||||
| 			host: host ?? IsNull(), | ||||
| 		})) ?? null; | ||||
| 
 | ||||
| 		const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); | ||||
| 
 | ||||
| 		if (emoji == null) return null; | ||||
| 
 | ||||
| 		const isLocal = emoji.host == null; | ||||
| 		// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 | ||||
| 		const emojiUrl = emoji.publicUrl || emoji.originalUrl; | ||||
| 		const url = emojiUrl; | ||||
| 
 | ||||
| 		return { | ||||
| 			name: emojiName, | ||||
| 			url, | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  * 複数の添付用絵文字情報を解決する (キャシュ付き, 存在しないものは結果から除外される) | ||||
|  */ | ||||
| 	@bindThis | ||||
| 	public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<PopulatedEmoji[]> { | ||||
| 		const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost))); | ||||
| 		return emojis.filter((x): x is PopulatedEmoji => x != null); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public aggregateNoteEmojis(notes: Note[]) { | ||||
| 		let emojis: { name: string | null; host: string | null; }[] = []; | ||||
| 		for (const note of notes) { | ||||
| 			emojis = emojis.concat(note.emojis | ||||
| 				.map(e => this.parseEmojiStr(e, note.userHost))); | ||||
| 			if (note.renote) { | ||||
| 				emojis = emojis.concat(note.renote.emojis | ||||
| 					.map(e => this.parseEmojiStr(e, note.renote!.userHost))); | ||||
| 				if (note.renote.user) { | ||||
| 					emojis = emojis.concat(note.renote.user.emojis | ||||
| 						.map(e => this.parseEmojiStr(e, note.renote!.userHost))); | ||||
| 				} | ||||
| 			} | ||||
| 			const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis; | ||||
| 			emojis = emojis.concat(customReactions); | ||||
| 			if (note.user) { | ||||
| 				emojis = emojis.concat(note.user.emojis | ||||
| 					.map(e => this.parseEmojiStr(e, note.userHost))); | ||||
| 			} | ||||
| 		} | ||||
| 		return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[]; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します | ||||
|  */ | ||||
| 	@bindThis | ||||
| 	public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> { | ||||
| 		const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null); | ||||
| 		const emojisQuery: any[] = []; | ||||
| 		const hosts = new Set(notCachedEmojis.map(e => e.host)); | ||||
| 		for (const host of hosts) { | ||||
| 			emojisQuery.push({ | ||||
| 				name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), | ||||
| 				host: host ?? IsNull(), | ||||
| 			}); | ||||
| 		} | ||||
| 		const _emojis = emojisQuery.length > 0 ? await this.emojisRepository.find({ | ||||
| 			where: emojisQuery, | ||||
| 			select: ['name', 'host', 'originalUrl', 'publicUrl'], | ||||
| 		}) : []; | ||||
| 		for (const emoji of _emojis) { | ||||
| 			this.cache.set(`${emoji.name} ${emoji.host}`, emoji); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -22,7 +22,6 @@ export class EmojiEntityService { | |||
| 	@bindThis | ||||
| 	public async pack( | ||||
| 		src: Emoji['id'] | Emoji, | ||||
| 		opts: { omitUrl?: boolean } = {}, | ||||
| 	): Promise<Packed<'Emoji'>> { | ||||
| 		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); | ||||
| 
 | ||||
|  | @ -32,17 +31,14 @@ export class EmojiEntityService { | |||
| 			name: emoji.name, | ||||
| 			category: emoji.category, | ||||
| 			host: emoji.host, | ||||
| 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 | ||||
| 			url: opts.omitUrl ? undefined : (emoji.publicUrl || emoji.originalUrl), | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public packMany( | ||||
| 		emojis: any[], | ||||
| 		opts: { omitUrl?: boolean } = {}, | ||||
| 	) { | ||||
| 		return Promise.all(emojis.map(x => this.pack(x, opts))); | ||||
| 		return Promise.all(emojis.map(x => this.pack(x))); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,12 +11,12 @@ import type { User } from '@/models/entities/User.js'; | |||
| import type { Note } from '@/models/entities/Note.js'; | ||||
| import type { NoteReaction } from '@/models/entities/NoteReaction.js'; | ||||
| import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, DriveFilesRepository } from '@/models/index.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { OnModuleInit } from '@nestjs/common'; | ||||
| import type { CustomEmojiService } from '../CustomEmojiService.js'; | ||||
| import type { ReactionService } from '../ReactionService.js'; | ||||
| import type { UserEntityService } from './UserEntityService.js'; | ||||
| import type { DriveFileEntityService } from './DriveFileEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class NoteEntityService implements OnModuleInit { | ||||
|  | @ -300,7 +300,6 @@ export class NoteEntityService implements OnModuleInit { | |||
| 			repliesCount: note.repliesCount, | ||||
| 			reactions: this.reactionService.convertLegacyReactions(note.reactions), | ||||
| 			tags: note.tags.length > 0 ? note.tags : undefined, | ||||
| 			emojis: this.customEmojiService.populateEmojis(note.emojis.concat(reactionEmojiNames), host), | ||||
| 			fileIds: note.fileIds, | ||||
| 			files: this.driveFileEntityService.packMany(note.fileIds), | ||||
| 			replyId: note.replyId, | ||||
|  | @ -385,8 +384,6 @@ export class NoteEntityService implements OnModuleInit { | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		await this.customEmojiService.prefetchEmojis(this.customEmojiService.aggregateNoteEmojis(notes)); | ||||
| 
 | ||||
| 		return await Promise.all(notes.map(n => this.pack(n, me, { | ||||
| 			...options, | ||||
| 			_hint_: { | ||||
|  |  | |||
|  | @ -8,12 +8,12 @@ import type { Notification } from '@/models/entities/Notification.js'; | |||
| import type { NoteReaction } from '@/models/entities/NoteReaction.js'; | ||||
| import type { Note } from '@/models/entities/Note.js'; | ||||
| import type { Packed } from '@/misc/schema.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import type { OnModuleInit } from '@nestjs/common'; | ||||
| import type { CustomEmojiService } from '../CustomEmojiService.js'; | ||||
| import type { UserEntityService } from './UserEntityService.js'; | ||||
| import type { NoteEntityService } from './NoteEntityService.js'; | ||||
| import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class NotificationEntityService implements OnModuleInit { | ||||
|  | @ -143,8 +143,6 @@ export class NotificationEntityService implements OnModuleInit { | |||
| 			myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) ?? null); | ||||
| 		} | ||||
| 
 | ||||
| 		await this.customEmojiService.prefetchEmojis(this.customEmojiService.aggregateNoteEmojis(notes)); | ||||
| 
 | ||||
| 		return await Promise.all(notifications.map(x => this.pack(x, { | ||||
| 			_hintForEachNotes_: { | ||||
| 				myReactions: myReactionsMap, | ||||
|  |  | |||
|  | @ -392,7 +392,6 @@ export class UserEntityService implements OnModuleInit { | |||
| 			host: user.host, | ||||
| 			avatarUrl: this.getAvatarUrlSync(user), | ||||
| 			avatarBlurhash: user.avatar?.blurhash ?? null, | ||||
| 			avatarColor: null, // 後方互換性のため
 | ||||
| 			isAdmin: user.isAdmin ?? falsy, | ||||
| 			isModerator: user.isModerator ?? falsy, | ||||
| 			isBot: user.isBot ?? falsy, | ||||
|  | @ -408,9 +407,7 @@ export class UserEntityService implements OnModuleInit { | |||
| 				faviconUrl: instance.faviconUrl, | ||||
| 				themeColor: instance.themeColor, | ||||
| 			} : undefined) : undefined, | ||||
| 			emojis: this.customEmojiService.populateEmojis(user.emojis, user.host), | ||||
| 			onlineStatus: this.getOnlineStatus(user), | ||||
| 			driveCapacityOverrideMb: user.driveCapacityOverrideMb, | ||||
| 
 | ||||
| 			...(opts.detail ? { | ||||
| 				url: profile!.url, | ||||
|  | @ -420,7 +417,6 @@ export class UserEntityService implements OnModuleInit { | |||
| 				lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, | ||||
| 				bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner, false) : null, | ||||
| 				bannerBlurhash: user.banner?.blurhash ?? null, | ||||
| 				bannerColor: null, // 後方互換性のため
 | ||||
| 				isLocked: user.isLocked, | ||||
| 				isSilenced: user.isSilenced ?? falsy, | ||||
| 				isSuspended: user.isSuspended ?? falsy, | ||||
|  | @ -447,6 +443,9 @@ export class UserEntityService implements OnModuleInit { | |||
| 						userId: user.id, | ||||
| 					}).then(result => result >= 1) | ||||
| 					: false, | ||||
| 				...(isMe || opts.includeSecrets ? { | ||||
| 					driveCapacityOverrideMb: user.driveCapacityOverrideMb, | ||||
| 				} : {}), | ||||
| 			} : {}), | ||||
| 
 | ||||
| 			...(opts.detail && isMe ? { | ||||
|  |  | |||
|  | @ -141,24 +141,6 @@ export const packedNoteSchema = { | |||
| 			type: 'boolean', | ||||
| 			optional: true, nullable: false, | ||||
| 		}, | ||||
| 		emojis: { | ||||
| 			type: 'array', | ||||
| 			optional: false, nullable: false, | ||||
| 			items: { | ||||
| 				type: 'object', | ||||
| 				optional: false, nullable: false, | ||||
| 				properties: { | ||||
| 					name: { | ||||
| 						type: 'string', | ||||
| 						optional: false, nullable: false, | ||||
| 					}, | ||||
| 					url: { | ||||
| 						type: 'string', | ||||
| 						optional: false, nullable: true, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		reactions: { | ||||
| 			type: 'object', | ||||
| 			optional: false, nullable: false, | ||||
|  |  | |||
|  | @ -32,11 +32,6 @@ export const packedUserLiteSchema = { | |||
| 			type: 'any', | ||||
| 			nullable: true, optional: false, | ||||
| 		}, | ||||
| 		avatarColor: { | ||||
| 			type: 'any', | ||||
| 			nullable: true, optional: false, | ||||
| 			default: null, | ||||
| 		}, | ||||
| 		isAdmin: { | ||||
| 			type: 'boolean', | ||||
| 			nullable: false, optional: true, | ||||
|  | @ -55,25 +50,6 @@ export const packedUserLiteSchema = { | |||
| 			type: 'boolean', | ||||
| 			nullable: false, optional: true, | ||||
| 		}, | ||||
| 		emojis: { | ||||
| 			type: 'array', | ||||
| 			nullable: false, optional: false, | ||||
| 			items: { | ||||
| 				type: 'object', | ||||
| 				nullable: false, optional: false, | ||||
| 				properties: { | ||||
| 					name: { | ||||
| 						type: 'string', | ||||
| 						nullable: false, optional: false, | ||||
| 					}, | ||||
| 					url: { | ||||
| 						type: 'string', | ||||
| 						nullable: false, optional: false, | ||||
| 						format: 'url', | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		onlineStatus: { | ||||
| 			type: 'string', | ||||
| 			format: 'url', | ||||
|  | @ -120,11 +96,6 @@ export const packedUserDetailedNotMeOnlySchema = { | |||
| 			type: 'any', | ||||
| 			nullable: true, optional: false, | ||||
| 		}, | ||||
| 		bannerColor: { | ||||
| 			type: 'any', | ||||
| 			nullable: true, optional: false, | ||||
| 			default: null, | ||||
| 		}, | ||||
| 		isLocked: { | ||||
| 			type: 'boolean', | ||||
| 			nullable: false, optional: false, | ||||
|  |  | |||
|  | @ -309,7 +309,6 @@ export const paramDef = { | |||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		detail: { type: 'boolean', default: true }, | ||||
| 		omitEmojiUrl: { type: 'boolean', default: false }, | ||||
| 	}, | ||||
| 	required: [], | ||||
| } as const; | ||||
|  | @ -391,7 +390,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { | |||
| 				backgroundImageUrl: instance.backgroundImageUrl, | ||||
| 				logoImageUrl: instance.logoImageUrl, | ||||
| 				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
 | ||||
| 				emojis: await this.emojiEntityService.packMany(emojis, { omitUrl: ps.omitEmojiUrl }), | ||||
| 				emojis: await this.emojiEntityService.packMany(emojis), | ||||
| 				defaultLightTheme: instance.defaultLightTheme, | ||||
| 				defaultDarkTheme: instance.defaultDarkTheme, | ||||
| 				ads: ads.map(ad => ({ | ||||
|  |  | |||
|  | @ -228,6 +228,8 @@ export class ClientServerService { | |||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			reply.header('Cache-Control', 'public, max-age=86400'); | ||||
| 
 | ||||
| 			const name = path.split('@')[0].replace('.webp', ''); | ||||
| 			const host = path.split('@')[1]?.replace('.webp', ''); | ||||
| 
 | ||||
|  | @ -244,7 +246,7 @@ export class ClientServerService { | |||
| 
 | ||||
| 			reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); | ||||
| 
 | ||||
| 			const url = new URL("/proxy/emoji.webp", this.config.url); | ||||
| 			const url = new URL('/proxy/emoji.webp', this.config.url); | ||||
| 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 | ||||
| 			url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl); | ||||
| 			url.searchParams.set('emoji', '1'); | ||||
|  | @ -347,15 +349,15 @@ export class ClientServerService { | |||
| 		fastify.get('/opensearch.xml', async (request, reply) => { | ||||
| 			const meta = await this.metaService.fetch(); | ||||
| 
 | ||||
| 			const name = meta.name || "Misskey"; | ||||
| 			let content = ""; | ||||
| 			content += `<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">`; | ||||
| 			const name = meta.name || 'Misskey'; | ||||
| 			let content = ''; | ||||
| 			content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">'; | ||||
| 			content += `<ShortName>${name} Search</ShortName>`; | ||||
| 			content += `<Description>${name} Search</Description>`; | ||||
| 			content += `<InputEncoding>UTF-8</InputEncoding>`; | ||||
| 			content += '<InputEncoding>UTF-8</InputEncoding>'; | ||||
| 			content += `<Image width="16" height="16" type="image/x-icon">${this.config.url}/favicon.ico</Image>`; | ||||
| 			content += `<Url type="text/html" template="${this.config.url}/search?q={searchTerms}"/>`; | ||||
| 			content += `</OpenSearchDescription>`; | ||||
| 			content += '</OpenSearchDescription>'; | ||||
| 
 | ||||
| 			reply.header('Content-Type', 'application/opensearchdescription+xml'); | ||||
| 			return await reply.send(content); | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ | |||
| 				</MkSelect> | ||||
| 			</div> | ||||
| 			<div class="chart _panel"> | ||||
| 				<MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart> | ||||
| 				<MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="true"></MkChart> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</MkFolder> | ||||
|  | @ -122,7 +122,7 @@ Chart.register( | |||
| 	Filler, | ||||
| ); | ||||
| 
 | ||||
| const chartLimit = 90; | ||||
| const chartLimit = 500; | ||||
| let chartSpan = $ref<'hour' | 'day'>('hour'); | ||||
| let chartSrc = $ref('active-users'); | ||||
| let heatmapSrc = $ref('active-users'); | ||||
|  |  | |||
|  | @ -81,7 +81,6 @@ useTooltip(buttonRef, async (showing) => { | |||
| 	os.popup(XDetails, { | ||||
| 		showing, | ||||
| 		reaction: props.reaction, | ||||
| 		emojis: props.note.emojis, | ||||
| 		users, | ||||
| 		count: props.count, | ||||
| 		targetElement: buttonRef.value, | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ async function renderChart() { | |||
| 	const wide = rootEl.offsetWidth > 600; | ||||
| 	const narrow = rootEl.offsetWidth < 400; | ||||
| 
 | ||||
| 	const maxDays = wide ? 20 : narrow ? 10 : 15; | ||||
| 	const maxDays = wide ? 20 : narrow ? 7 : 14; | ||||
| 
 | ||||
| 	const raw = await os.api('retention', { }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData | |||
| export async function fetchInstance() { | ||||
| 	const meta = await api('meta', { | ||||
| 		detail: false, | ||||
| 		omitEmojiUrl: true, | ||||
| 	}); | ||||
| 
 | ||||
| 	for (const [k, v] of Object.entries(meta)) { | ||||
|  |  | |||
|  | @ -76,7 +76,7 @@ | |||
| 		<XFederation/> | ||||
| 	</MkSpacer> | ||||
| 	<MkSpacer v-else-if="tab === 'charts'" :content-max="1000" :margin-min="20"> | ||||
| 		<MkInstanceStats :chart-limit="500" :detailed="true"/> | ||||
| 		<MkInstanceStats/> | ||||
| 	</MkSpacer> | ||||
| </MkStickyContainer> | ||||
| </template> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue