Merge remote-branch 'misskey/develop'

This commit is contained in:
NoriDev 2023-10-01 02:44:34 +09:00
commit 5ec40d5b72
23 changed files with 140 additions and 12 deletions

View file

@ -12,6 +12,19 @@
--> -->
## 2023.9.3
### General
- Enhance: ノートの翻訳機能の利用可否をロールで設定可能に
### Client
- Enhance: AiScriptでホストのアドレスを参照する定数`SERVER_URL`を追加
- Enhance: モデレーションログ機能の強化
- Enhance: ローカリゼーションの更新
### Server
- Fix: Redisに古いバージョンのキャッシュが残っている場合、キャッシュが消えるまでの間通知が届かなくなる問題を修正
- Fix: 後方互換性の修正
## 2023.9.2 ## 2023.9.2
### General ### General

View file

@ -1137,6 +1137,9 @@ authenticationRequiredToContinue: "Bitte authentifiziere dich, um fortzufahren"
dateAndTime: "Zeit" dateAndTime: "Zeit"
showRenotes: "Renotes anzeigen" showRenotes: "Renotes anzeigen"
edited: "Bearbeitet" edited: "Bearbeitet"
notificationRecieveConfig: "Benachrichtigungseinstellungen"
mutualFollow: "Gegenseitig gefolgt"
fileAttachedOnly: "Nur Notizen mit Dateien"
_announcement: _announcement:
forExistingUsers: "Nur für existierende Nutzer" forExistingUsers: "Nur für existierende Nutzer"
forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt." forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
@ -2218,3 +2221,6 @@ _moderationLogTypes:
unmarkSensitiveDriveFile: "Datei als nicht sensitiv markiert" unmarkSensitiveDriveFile: "Datei als nicht sensitiv markiert"
resolveAbuseReport: "Meldung bearbeitet" resolveAbuseReport: "Meldung bearbeitet"
createInvitation: "Einladung erstellt" createInvitation: "Einladung erstellt"
createAd: "Werbung erstellt"
deleteAd: "Werbung gelöscht"
updateAd: "Werbung aktualisiert"

View file

@ -1201,6 +1201,9 @@ authenticationRequiredToContinue: "Please authenticate to continue"
dateAndTime: "Timestamp" dateAndTime: "Timestamp"
showRenotes: "Show renotes" showRenotes: "Show renotes"
edited: "Edited" edited: "Edited"
notificationRecieveConfig: "Notification Settings"
mutualFollow: "Mutual follow"
fileAttachedOnly: "Only notes with files"
showCatOnly: "Show only cats" showCatOnly: "Show only cats"
additionalPermissionsForFlash: "Allow to add permission to Play" additionalPermissionsForFlash: "Allow to add permission to Play"
thisFlashRequiresTheFollowingPermissions: "This Play requires the following permissions" thisFlashRequiresTheFollowingPermissions: "This Play requires the following permissions"
@ -2395,6 +2398,9 @@ _moderationLogTypes:
unmarkSensitiveDriveFile: "File unmarked as sensitive" unmarkSensitiveDriveFile: "File unmarked as sensitive"
resolveAbuseReport: "Report resolved" resolveAbuseReport: "Report resolved"
createInvitation: "Invite generated" createInvitation: "Invite generated"
createAd: "Ad created"
deleteAd: "Ad deleted"
updateAd: "Ad updated"
_abuse: _abuse:
_resolver: _resolver:
1hour: "one hour" 1hour: "one hour"

1
locales/index.d.ts vendored
View file

@ -1736,6 +1736,7 @@ export interface Locale {
"descriptionOfRateLimitFactor": string; "descriptionOfRateLimitFactor": string;
"canHideAds": string; "canHideAds": string;
"canSearchNotes": string; "canSearchNotes": string;
"canUseTranslator": string;
}; };
"_condition": { "_condition": {
"isLocal": string; "isLocal": string;

View file

@ -1655,7 +1655,8 @@ _role:
rateLimitFactor: "レートリミット" rateLimitFactor: "レートリミット"
descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。" descriptionOfRateLimitFactor: "小さいほど制限が緩和され、大きいほど制限が強化されます。"
canHideAds: "広告の非表示" canHideAds: "広告の非表示"
canSearchNotes: "ノート検索の利用可否" canSearchNotes: "ノート検索の利用"
canUseTranslator: "翻訳機能の利用"
_condition: _condition:
isLocal: "ローカルユーザー" isLocal: "ローカルユーザー"
isRemote: "リモートユーザー" isRemote: "リモートユーザー"

View file

@ -464,6 +464,9 @@ totp: "인증 앱"
totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력" totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력"
moderator: "모더레이터" moderator: "모더레이터"
moderation: "모더레이션" moderation: "모더레이션"
moderationNote: "모더레이션 노트"
addModerationNote: "모더레이션 노트 추가하기"
moderationLogs: "모더레이션 로그"
nUsersMentioned: "{n}명이 언급함" nUsersMentioned: "{n}명이 언급함"
securityKeyAndPasskey: "보안 키 또는 패스 키" securityKeyAndPasskey: "보안 키 또는 패스 키"
securityKey: "보안 키" securityKey: "보안 키"
@ -1187,6 +1190,18 @@ youHaveUnreadAnnouncements: "읽지 않은 공지사항이 있어요."
useSecurityKey: "브라우저 또는 기기의 안내에 따라 보안 키 또는 패스키를 사용해 주세요." useSecurityKey: "브라우저 또는 기기의 안내에 따라 보안 키 또는 패스키를 사용해 주세요."
replies: "답글" replies: "답글"
renotes: "리노트" renotes: "리노트"
loadReplies: "답글 보기"
loadConversation: "대화 보기"
pinnedList: "고정해놓은 리스트"
keepScreenOn: "기기 화면 항상 켜기"
verifiedLink: "이 링크의 소유자임을 확인했어요."
notifyNotes: "새 노트 알림 켜기"
unnotifyNotes: "새 노트 알림 끄기"
authentication: "인증"
showRenotes: "리노트 표시"
edited: "수정됨"
notificationRecieveConfig: "알림 설정"
mutualFollow: "맞팔로우"
showCatOnly: "고양이만 보기" showCatOnly: "고양이만 보기"
additionalPermissionsForFlash: "Play에 대한 추가 권한" additionalPermissionsForFlash: "Play에 대한 추가 권한"
thisFlashRequiresTheFollowingPermissions: "이 Play는 다음 권한을 요구해요" thisFlashRequiresTheFollowingPermissions: "이 Play는 다음 권한을 요구해요"
@ -1302,6 +1317,12 @@ _event:
performers: "출연자" performers: "출연자"
_serverSettings: _serverSettings:
iconUrl: "아이콘 URL" iconUrl: "아이콘 URL"
appIconUsageExample: "예를 들어, PWA나 스마트폰 홈 화면에 북마크로 추가되었을 때 등"
appIconStyleRecommendation: "아이콘이 원형 또는 둥근 사각형으로 잘리는 경우가 있으므로, 가장자리 여백이 충분한 사진을 사용하는 것을 추천해요."
appIconResolutionMustBe: "해상도는 반드시 {resolution} 이어야 해요."
manifestJsonOverride: "manifest.json 덮어쓰기"
shortName: "약칭"
shortNameDescription: "서버의 정식 명칭이 긴 경우, 대신 표시할 수 있는 약칭이나 통칭."
_accountMigration: _accountMigration:
moveFrom: "다른 계정에서 이 계정으로 이사" moveFrom: "다른 계정에서 이 계정으로 이사"
moveFromSub: "다른 계정에 대한 별칭을 생성" moveFromSub: "다른 계정에 대한 별칭을 생성"

View file

@ -1134,6 +1134,9 @@ authentication: "การตรวจสอบสิทธิ์"
dateAndTime: "เวลาประทับ" dateAndTime: "เวลาประทับ"
showRenotes: "แสดงรีโน้ต" showRenotes: "แสดงรีโน้ต"
edited: "แก้ไขแล้ว" edited: "แก้ไขแล้ว"
notificationRecieveConfig: "การตั้งค่าการแจ้งเตือน"
mutualFollow: "ติดตามซึ่งกันและกัน"
fileAttachedOnly: "เฉพาะโน้ตที่มีไฟล์เท่านั้น"
_announcement: _announcement:
forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น" forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น"
forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน" forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน"
@ -2190,3 +2193,6 @@ _moderationLogTypes:
resetPassword: "รีเซ็ตรหัสผ่าน" resetPassword: "รีเซ็ตรหัสผ่าน"
resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว" resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว"
createInvitation: "สร้างคำเชิญ" createInvitation: "สร้างคำเชิญ"
createAd: "สร้างโฆษณาแล้ว"
deleteAd: "ลบโฆษณาออกแล้ว"
updateAd: "อัปเดตโฆษณาแล้ว"

View file

@ -1137,6 +1137,9 @@ authenticationRequiredToContinue: "要继续,请先进行验证"
dateAndTime: "日期和时间" dateAndTime: "日期和时间"
showRenotes: "显示转帖" showRenotes: "显示转帖"
edited: "已编辑" edited: "已编辑"
notificationRecieveConfig: "通知接收设置"
mutualFollow: "互相关注"
fileAttachedOnly: "仅限媒体"
_announcement: _announcement:
forExistingUsers: "仅限现有用户" forExistingUsers: "仅限现有用户"
forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。" forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
@ -2216,3 +2219,6 @@ _moderationLogTypes:
unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体" unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体"
resolveAbuseReport: "处理举报" resolveAbuseReport: "处理举报"
createInvitation: "发行邀请码" createInvitation: "发行邀请码"
createAd: "创建了广告"
deleteAd: "删除了广告"
updateAd: "更新了广告"

View file

@ -1136,6 +1136,8 @@ authentication: "驗證"
authenticationRequiredToContinue: "請於繼續前完成驗證" authenticationRequiredToContinue: "請於繼續前完成驗證"
dateAndTime: "日期與時間" dateAndTime: "日期與時間"
showRenotes: "顯示轉發貼文" showRenotes: "顯示轉發貼文"
edited: "已編輯"
mutualFollow: "互相追隨"
_announcement: _announcement:
forExistingUsers: "僅限既有的使用者" forExistingUsers: "僅限既有的使用者"
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。" forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
@ -2217,3 +2219,6 @@ _moderationLogTypes:
unmarkSensitiveDriveFile: "撤銷標記為敏感檔案" unmarkSensitiveDriveFile: "撤銷標記為敏感檔案"
resolveAbuseReport: "解決檢舉" resolveAbuseReport: "解決檢舉"
createInvitation: "建立邀請碼" createInvitation: "建立邀請碼"
createAd: "建立廣告"
deleteAd: "刪除廣告"
updateAd: "更新廣告"

View file

@ -1,7 +1,7 @@
{ {
"name": "cherrypick", "name": "cherrypick",
"version": "4.3.1", "version": "4.3.1",
"basedMisskeyVersion": "2023.9.2", "basedMisskeyVersion": "2023.9.3",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -80,7 +80,10 @@ export class NotificationService implements OnApplicationShutdown {
notifierId?: MiUser['id'] | null, notifierId?: MiUser['id'] | null,
): Promise<MiNotification | null> { ): Promise<MiNotification | null> {
const profile = await this.cacheService.userProfileCache.fetch(notifieeId); const profile = await this.cacheService.userProfileCache.fetch(notifieeId);
const recieveConfig = profile.notificationRecieveConfig[type];
// 古いMisskeyバージョンのキャッシュが残っている可能性がある
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const recieveConfig = (profile.notificationRecieveConfig ?? {})[type];
if (recieveConfig?.type === 'never') { if (recieveConfig?.type === 'never') {
return null; return null;
} }

View file

@ -34,6 +34,7 @@ export type RolePolicies = {
inviteExpirationTime: number; inviteExpirationTime: number;
canManageCustomEmojis: boolean; canManageCustomEmojis: boolean;
canSearchNotes: boolean; canSearchNotes: boolean;
canUseTranslator: boolean;
canHideAds: boolean; canHideAds: boolean;
driveCapacityMb: number; driveCapacityMb: number;
alwaysMarkNsfw: boolean; alwaysMarkNsfw: boolean;
@ -60,6 +61,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
inviteExpirationTime: 0, inviteExpirationTime: 0,
canManageCustomEmojis: false, canManageCustomEmojis: false,
canSearchNotes: false, canSearchNotes: false,
canUseTranslator: true,
canHideAds: false, canHideAds: false,
driveCapacityMb: 100, driveCapacityMb: 100,
alwaysMarkNsfw: false, alwaysMarkNsfw: false,
@ -306,6 +308,7 @@ export class RoleService implements OnApplicationShutdown {
inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)), inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)),
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)), canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),

View file

@ -490,6 +490,7 @@ export class UserEntityService implements OnModuleInit {
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id), hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
mutedWords: profile!.mutedWords, mutedWords: profile!.mutedWords,
mutedInstances: profile!.mutedInstances, mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: [], // 後方互換性のため
notificationRecieveConfig: profile!.notificationRecieveConfig, notificationRecieveConfig: profile!.notificationRecieveConfig,
emailNotificationTypes: profile!.emailNotificationTypes, emailNotificationTypes: profile!.emailNotificationTypes,
achievements: profile!.achievements, achievements: profile!.achievements,

View file

@ -14,12 +14,13 @@ import { MetaService } from '@/core/MetaService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
import { GetterService } from '@/server/api/GetterService.js'; import { GetterService } from '@/server/api/GetterService.js';
import { createTemp } from '@/misc/create-temp.js'; import { createTemp } from '@/misc/create-temp.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
tags: ['notes'], tags: ['notes'],
requireCredential: false, requireCredential: true,
res: { res: {
type: 'object', type: 'object',
@ -27,6 +28,11 @@ export const meta = {
}, },
errors: { errors: {
unavailable: {
message: 'Translate of notes unavailable.',
code: 'UNAVAILABLE',
id: '50a70314-2d8a-431b-b433-efa5cc56444c',
},
noSuchNote: { noSuchNote: {
message: 'No such note.', message: 'No such note.',
code: 'NO_SUCH_NOTE', code: 'NO_SUCH_NOTE',
@ -56,14 +62,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private getterService: GetterService, private getterService: GetterService,
private metaService: MetaService, private metaService: MetaService,
private httpRequestService: HttpRequestService, private httpRequestService: HttpRequestService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const policies = await this.roleService.getUserPolicies(me.id);
if (!policies.canUseTranslator) {
throw new ApiError(meta.errors.unavailable);
}
const note = await this.getterService.getNote(ps.noteId).catch(err => { const note = await this.getterService.getNote(ps.noteId).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw err; throw err;
}); });
if (!(await this.noteEntityService.isVisibleForMe(note, me ? me.id : null))) { if (!(await this.noteEntityService.isVisibleForMe(note, me.id))) {
return 204; // TODO: 良い感じのエラー返す return 204; // TODO: 良い感じのエラー返す
} }

View file

@ -13,12 +13,13 @@ import { MetaService } from '@/core/MetaService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
import { GetterService } from '@/server/api/GetterService.js'; import { GetterService } from '@/server/api/GetterService.js';
import { createTemp } from '@/misc/create-temp.js'; import { createTemp } from '@/misc/create-temp.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
tags: ['users'], tags: ['users'],
requireCredential: false, requireCredential: true,
res: { res: {
type: 'object', type: 'object',
@ -26,6 +27,11 @@ export const meta = {
}, },
errors: { errors: {
unavailable: {
message: 'Translate of notes unavailable.',
code: 'UNAVAILABLE',
id: '50a70314-2d8a-431b-b433-efa5cc56444c',
},
noSuchDescription: { noSuchDescription: {
message: 'No such description.', message: 'No such description.',
code: 'NO_SUCH_DESCRIPTION', code: 'NO_SUCH_DESCRIPTION',
@ -54,8 +60,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private getterService: GetterService, private getterService: GetterService,
private metaService: MetaService, private metaService: MetaService,
private httpRequestService: HttpRequestService, private httpRequestService: HttpRequestService,
private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps) => { super(meta, paramDef, async (ps, me) => {
const policies = await this.roleService.getUserPolicies(me.id);
if (!policies.canUseTranslator) {
throw new ApiError(meta.errors.unavailable);
}
const target = await this.getterService.getUserProfiles(ps.userId).catch(err => { const target = await this.getterService.getUserProfiles(ps.userId).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchDescription); if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchDescription);
throw err; throw err;

View file

@ -188,7 +188,7 @@ export class ClientServerService {
// Authenticate // Authenticate
fastify.addHook('onRequest', async (request, reply) => { fastify.addHook('onRequest', async (request, reply) => {
// %71ueueとかでリクエストされたら困るため // %71ueueとかでリクエストされたら困るため
const url = decodeURI(request.routerPath); const url = decodeURI(request.routeOptions.url);
if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) { if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
const token = request.cookies.token; const token = request.cookies.token;
if (token == null) { if (token == null) {

View file

@ -167,6 +167,7 @@ describe('ユーザー', () => {
unreadAnnouncements: user.unreadAnnouncements, unreadAnnouncements: user.unreadAnnouncements,
mutedWords: user.mutedWords, mutedWords: user.mutedWords,
mutedInstances: user.mutedInstances, mutedInstances: user.mutedInstances,
mutingNotificationTypes: user.mutingNotificationTypes,
notificationRecieveConfig: user.notificationRecieveConfig, notificationRecieveConfig: user.notificationRecieveConfig,
emailNotificationTypes: user.emailNotificationTypes, emailNotificationTypes: user.emailNotificationTypes,
achievements: user.achievements, achievements: user.achievements,
@ -416,6 +417,7 @@ describe('ユーザー', () => {
assert.deepStrictEqual(response.unreadAnnouncements, []); assert.deepStrictEqual(response.unreadAnnouncements, []);
assert.deepStrictEqual(response.mutedWords, []); assert.deepStrictEqual(response.mutedWords, []);
assert.deepStrictEqual(response.mutedInstances, []); assert.deepStrictEqual(response.mutedInstances, []);
assert.deepStrictEqual(response.mutingNotificationTypes, []);
assert.deepStrictEqual(response.notificationRecieveConfig, {}); assert.deepStrictEqual(response.notificationRecieveConfig, {});
assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest', 'groupInvited']); assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest', 'groupInvited']);
assert.deepStrictEqual(response.achievements, []); assert.deepStrictEqual(response.achievements, []);

View file

@ -69,6 +69,7 @@ export const ROLE_POLICIES = [
'inviteExpirationTime', 'inviteExpirationTime',
'canManageCustomEmojis', 'canManageCustomEmojis',
'canSearchNotes', 'canSearchNotes',
'canUseTranslator',
'canHideAds', 'canHideAds',
'driveCapacityMb', 'driveCapacityMb',
'alwaysMarkNsfw', 'alwaysMarkNsfw',

View file

@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'suspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span> <span v-else-if="log.type === 'suspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'unsuspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span> <span v-else-if="log.type === 'unsuspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span> <span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'assignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span> <span v-else-if="log.type === 'assignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-arrow-right"></i> {{ log.info.roleName }}</span>
<span v-else-if="log.type === 'unassignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span> <span v-else-if="log.type === 'unassignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-equal-not"></i> {{ log.info.roleName }}</span>
<span v-else-if="log.type === 'createRole'">: {{ log.info.role.name }}</span> <span v-else-if="log.type === 'createRole'">: {{ log.info.role.name }}</span>
<span v-else-if="log.type === 'updateRole'">: {{ log.info.before.name }}</span> <span v-else-if="log.type === 'updateRole'">: {{ log.info.before.name }}</span>
<span v-else-if="log.type === 'deleteRole'">: {{ log.info.role.name }}</span> <span v-else-if="log.type === 'deleteRole'">: {{ log.info.role.name }}</span>

View file

@ -319,6 +319,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</MkFolder> </MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUseTranslator, 'canUseTranslator'])">
<template #label>{{ i18n.ts._role._options.canUseTranslator }}</template>
<template #suffix>
<span v-if="role.policies.canUseTranslator.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
<span v-else>{{ role.policies.canUseTranslator.value ? i18n.ts.yes : i18n.ts.no }}</span>
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canUseTranslator)"></i></span>
</template>
<div class="_gaps">
<MkSwitch v-model="role.policies.canUseTranslator.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
<MkSwitch v-model="role.policies.canUseTranslator.value" :disabled="role.policies.canUseTranslator.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
<MkRange v-model="role.policies.canUseTranslator.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.driveCapacity, 'driveCapacityMb'])"> <MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template> <template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
<template #suffix> <template #suffix>

View file

@ -111,6 +111,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
</MkFolder> </MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUseTranslator, 'canSearchNotes'])">
<template #label>{{ i18n.ts._role._options.canUseTranslator }}</template>
<template #suffix>{{ policies.canUseTranslator ? i18n.ts.yes : i18n.ts.no }}</template>
<MkSwitch v-model="policies.canUseTranslator">
<template #label>{{ i18n.ts.enable }}</template>
</MkSwitch>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])"> <MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template> <template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
<template #suffix>{{ policies.driveCapacityMb }}MB</template> <template #suffix>{{ policies.driveCapacityMb }}MB</template>

View file

@ -10,7 +10,7 @@ import * as os from '@/os.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { customEmojis } from '@/custom-emojis.js'; import { customEmojis } from '@/custom-emojis.js';
import { lang } from '@/config.js'; import { url, lang } from '@/config.js';
export function createAiScriptEnv(opts) { export function createAiScriptEnv(opts) {
const table = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const table = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
@ -23,6 +23,7 @@ export function createAiScriptEnv(opts) {
USER_USERNAME: $i ? values.STR($i.username) : values.NULL, USER_USERNAME: $i ? values.STR($i.username) : values.NULL,
CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value), CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value),
LOCALE: values.STR(lang), LOCALE: values.STR(lang),
SERVER_URL: values.STR(url),
'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => {
await os.alert({ await os.alert({
type: type ? type.value : 'info', type: type ? type.value : 'info',

View file

@ -331,7 +331,7 @@ export function getNoteMenu(props: {
text: i18n.ts.share, text: i18n.ts.share,
action: share, action: share,
}, },
instance.translatorAvailable ? { $i && $i.policies.canUseTranslator && instance.translatorAvailable ? {
icon: 'ti ti-language-hiragana', icon: 'ti ti-language-hiragana',
text: i18n.ts.translate, text: i18n.ts.translate,
action: translate, action: translate,