Skip to content

Commit

Permalink
Merge branch 'develop' into notification-hide-muting-user
Browse files Browse the repository at this point in the history
  • Loading branch information
tai-cha authored Feb 28, 2024
2 parents 03c9e64 + 0d47877 commit 66d0b71
Show file tree
Hide file tree
Showing 20 changed files with 181 additions and 58 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
### General
- 通知がミュート、凍結を考慮するようになりました
- Enhance: サーバーごとにモデレーションノートを残せるように
- Enhance: コンディショナルロールの条件に「マニュアルロールへのアサイン」を追加
- Enhance: 通知の受信設定に「フォロー中またはフォロワー」を追加

### Client
- Enhance: ノート作成画面のファイル添付メニューの区切り線の位置を調整
Expand All @@ -35,6 +37,7 @@
- 必須パラメータを`id`または`name`のいずれかのみに
- `id`の代わりに`name`で絵文字を指定可能に(`id``name`両指定時は従来通り`name`を変更する挙動)
- `category`および`licence`が指定なしの時勝手にnullに上書きされる挙動を修正
- Fix: 通知の受信設定で「相互フォロー」が正しく動作しない問題を修正

## 2024.2.0

Expand Down
8 changes: 8 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4656,6 +4656,10 @@ export interface Locale extends ILocale {
* 相互フォロー
*/
"mutualFollow": string;
/**
* フォロー中またはフォロワー
*/
"followingOrFollower": string;
/**
* ファイル付きのみ
*/
Expand Down Expand Up @@ -6528,6 +6532,10 @@ export interface Locale extends ILocale {
"avatarDecorationLimit": string;
};
"_condition": {
/**
* マニュアルロールにアサイン済み
*/
"roleAssignedTo": string;
/**
* ローカルユーザー
*/
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,7 @@ showRenotes: "リノートを表示"
edited: "編集済み"
notificationRecieveConfig: "通知の受信設定"
mutualFollow: "相互フォロー"
followingOrFollower: "フォロー中またはフォロワー"
fileAttachedOnly: "ファイル付きのみ"
showRepliesToOthersInTimeline: "TLに他の人への返信を含める"
hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない"
Expand Down Expand Up @@ -1687,6 +1688,7 @@ _role:
canUseTranslator: "翻訳機能の利用"
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
_condition:
roleAssignedTo: "マニュアルロールにアサイン済み"
isLocal: "ローカルユーザー"
isRemote: "リモートユーザー"
createdLessThan: "アカウント作成から~以内"
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/core/NotificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ export class NotificationService implements OnApplicationShutdown {
return null;
}
} else if (recieveConfig?.type === 'mutualFollow') {
const [isFollowing, isFollower] = await Promise.all([
this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
]);
if (!(isFollowing && isFollower)) {
return null;
}
} else if (recieveConfig?.type === 'followingOrFollower') {
const [isFollowing, isFollower] = await Promise.all([
this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
Expand Down
19 changes: 11 additions & 8 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,17 +200,20 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
}

@bindThis
private evalCond(user: MiUser, value: RoleCondFormulaValue): boolean {
private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue): boolean {
try {
switch (value.type) {
case 'and': {
return value.values.every(v => this.evalCond(user, v));
return value.values.every(v => this.evalCond(user, roles, v));
}
case 'or': {
return value.values.some(v => this.evalCond(user, v));
return value.values.some(v => this.evalCond(user, roles, v));
}
case 'not': {
return !this.evalCond(user, value.value);
return !this.evalCond(user, roles, value.value);
}
case 'roleAssignedTo': {
return roles.some(r => r.id === value.roleId);
}
case 'isLocal': {
return this.userEntityService.isLocalUser(user);
Expand Down Expand Up @@ -272,7 +275,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
const assigns = await this.getUserAssigns(userId);
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, assignedRoles, r.condFormula));
return [...assignedRoles, ...matchedCondRoles];
}

Expand All @@ -285,13 +288,13 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
// 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId);
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
const assignedBadgeRoles = assignedRoles.filter(r => r.asBadge);
const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
if (badgeCondRoles.length > 0) {
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula));
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, assignedRoles, r.condFormula));
return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
} else {
return assignedBadgeRoles;
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/misc/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
packedRoleCondFormulaLogicsSchema,
packedRoleCondFormulaValueNot,
packedRoleCondFormulaValueIsLocalOrRemoteSchema,
packedRoleCondFormulaValueAssignedRoleSchema,
packedRoleCondFormulaValueCreatedSchema,
packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
packedRoleCondFormulaValueSchema,
Expand Down Expand Up @@ -96,6 +97,7 @@ export const refs = {
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
RoleCondFormulaValueNot: packedRoleCondFormulaValueNot,
RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema,
RoleCondFormulaValueAssignedRole: packedRoleCondFormulaValueAssignedRoleSchema,
RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema,
RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
RoleCondFormulaValue: packedRoleCondFormulaValueSchema,
Expand Down
6 changes: 6 additions & 0 deletions packages/backend/src/models/Role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ type CondFormulaValueIsRemote = {
type: 'isRemote';
};

type CondFormulaValueRoleAssignedTo = {
type: 'roleAssignedTo';
roleId: string;
};

type CondFormulaValueCreatedLessThan = {
type: 'createdLessThan';
sec: number;
Expand Down Expand Up @@ -75,6 +80,7 @@ export type RoleCondFormulaValue = { id: string } & (
CondFormulaValueNot |
CondFormulaValueIsLocal |
CondFormulaValueIsRemote |
CondFormulaValueRoleAssignedTo |
CondFormulaValueCreatedLessThan |
CondFormulaValueCreatedMoreThan |
CondFormulaValueFollowersLessThanOrEq |
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/models/UserProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ export class MiUserProfile {
type: 'follower';
} | {
type: 'mutualFollow';
} | {
type: 'followingOrFollower';
} | {
type: 'list';
userListId: MiUserList['id'];
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/models/json-schema/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ export const packedRoleCondFormulaValueIsLocalOrRemoteSchema = {
},
} as const;

export const packedRoleCondFormulaValueAssignedRoleSchema = {
type: 'object',
properties: {
type: {
type: 'string',
nullable: false, optional: false,
enum: ['roleAssignedTo'],
},
roleId: {
type: 'string',
nullable: false, optional: false,
format: 'id',
example: 'xxxxxxxxxx',
},
},
} as const;

export const packedRoleCondFormulaValueCreatedSchema = {
type: 'object',
properties: {
Expand Down Expand Up @@ -115,6 +132,9 @@ export const packedRoleCondFormulaValueSchema = {
{
ref: 'RoleCondFormulaValueIsLocalOrRemote',
},
{
ref: 'RoleCondFormulaValueAssignedRole',
},
{
ref: 'RoleCondFormulaValueCreated',
},
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/models/json-schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const notificationRecieveConfig = {
type: {
type: 'string',
nullable: false,
enum: ['all', 'following', 'follower', 'mutualFollow', 'never'],
enum: ['all', 'following', 'follower', 'mutualFollow', 'followingOrFollower', 'never'],
},
},
required: ['type'],
Expand Down Expand Up @@ -148,6 +148,9 @@ export const packedUserLiteSchema = {
emojis: {
type: 'object',
nullable: false, optional: false,
additionalProperties: {
type: 'string',
},
},
onlineStatus: {
type: 'string',
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/server/api/endpoints/admin/emoji/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ export const meta = {
},
},

ref: 'EmojiDetailed',
res: {
type: 'object',
ref: 'EmojiDetailed',
},
} as const;

export const paramDef = {
Expand Down
28 changes: 28 additions & 0 deletions packages/backend/test/unit/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,34 @@ describe('RoleService', () => {
expect(user2Policies.canManageCustomEmojis).toBe(true);
});

test('コンディショナルロール: マニュアルロールにアサイン済み', async () => {
const [user1, user2, role1] = await Promise.all([
createUser(),
createUser(),
createRole({
name: 'manual role',
}),
]);
const role2 = await createRole({
name: 'conditional role',
target: 'conditional',
condFormula: {
// idはバックエンドのロジックに必要ない?
id: 'bdc612bd-9d54-4675-ae83-0499c82ea670',
type: 'roleAssignedTo',
roleId: role1.id,
},
});
await roleService.assign(user2.id, role1.id);

const [u1role, u2role] = await Promise.all([
roleService.getUserRoles(user1.id),
roleService.getUserRoles(user2.id),
]);
expect(u1role.some(r => r.id === role2.id)).toBe(false);
expect(u2role.some(r => r.id === role2.id)).toBe(true);
});

test('expired role', async () => {
const user = await createUser();
const role = await createRole({
Expand Down
9 changes: 9 additions & 0 deletions packages/frontend/src/pages/admin/RolesEditorFormula.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSelect v-model="type" :class="$style.typeSelect">
<option value="isLocal">{{ i18n.ts._role._condition.isLocal }}</option>
<option value="isRemote">{{ i18n.ts._role._condition.isRemote }}</option>
<option value="roleAssignedTo">{{ i18n.ts._role._condition.roleAssignedTo }}</option>
<option value="createdLessThan">{{ i18n.ts._role._condition.createdLessThan }}</option>
<option value="createdMoreThan">{{ i18n.ts._role._condition.createdMoreThan }}</option>
<option value="followersLessThanOrEq">{{ i18n.ts._role._condition.followersLessThanOrEq }}</option>
Expand Down Expand Up @@ -51,6 +52,10 @@ SPDX-License-Identifier: AGPL-3.0-only

<MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq', 'notesLessThanOrEq', 'notesMoreThanOrEq'].includes(type)" v-model="v.value" type="number">
</MkInput>

<MkSelect v-else-if="type === 'roleAssignedTo'" v-model="v.roleId">
<option v-for="role in roles.filter(r => r.target === 'manual')" :key="role.id" :value="role.id">{{ role.name }}</option>
</MkSelect>
</div>
</template>

Expand All @@ -62,6 +67,7 @@ import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
import { deepClone } from '@/scripts/clone.js';
import { rolesCache } from '@/cache.js';

const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));

Expand All @@ -77,6 +83,8 @@ const props = defineProps<{

const v = ref(deepClone(props.modelValue));

const roles = await rolesCache.fetch();

watch(() => props.modelValue, () => {
if (JSON.stringify(props.modelValue) === JSON.stringify(v.value)) return;
v.value = deepClone(props.modelValue);
Expand All @@ -92,6 +100,7 @@ const type = computed({
if (t === 'and') v.value.values = [];
if (t === 'or') v.value.values = [];
if (t === 'not') v.value.value = { id: uuid(), type: 'isRemote' };
if (t === 'roleAssignedTo') v.value.roleId = '';
if (t === 'createdLessThan') v.value.sec = 86400;
if (t === 'createdMoreThan') v.value.sec = 86400;
if (t === 'followersLessThanOrEq') v.value.value = 10;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="following">{{ i18n.ts.following }}</option>
<option value="follower">{{ i18n.ts.followers }}</option>
<option value="mutualFollow">{{ i18n.ts.mutualFollow }}</option>
<option value="followingOrFollower">{{ i18n.ts.followingOrFollower }}</option>
<option value="list">{{ i18n.ts.userList }}</option>
<option value="never">{{ i18n.ts.none }}</option>
</MkSelect>
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/pages/settings/notifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
$i.notificationRecieveConfig[type]?.type === 'following' ? i18n.ts.following :
$i.notificationRecieveConfig[type]?.type === 'follower' ? i18n.ts.followers :
$i.notificationRecieveConfig[type]?.type === 'mutualFollow' ? i18n.ts.mutualFollow :
$i.notificationRecieveConfig[type]?.type === 'followingOrFollower' ? i18n.ts.followingOrFollower :
$i.notificationRecieveConfig[type]?.type === 'list' ? i18n.ts.userList :
i18n.ts.all
}}
Expand Down
8 changes: 8 additions & 0 deletions packages/misskey-js/etc/misskey-js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ type AdminEmojiAddAliasesBulkRequest = operations['admin/emoji/add-aliases-bulk'
// @public (undocumented)
type AdminEmojiAddRequest = operations['admin/emoji/add']['requestBody']['content']['application/json'];

// @public (undocumented)
type AdminEmojiAddResponse = operations['admin/emoji/add']['responses']['200']['content']['application/json'];

// @public (undocumented)
type AdminEmojiCopyRequest = operations['admin/emoji/copy']['requestBody']['content']['application/json'];

Expand Down Expand Up @@ -1154,6 +1157,7 @@ declare namespace entities {
AdminDriveShowFileResponse,
AdminEmojiAddAliasesBulkRequest,
AdminEmojiAddRequest,
AdminEmojiAddResponse,
AdminEmojiCopyRequest,
AdminEmojiCopyResponse,
AdminEmojiDeleteBulkRequest,
Expand Down Expand Up @@ -1708,6 +1712,7 @@ declare namespace entities {
RoleCondFormulaLogics,
RoleCondFormulaValueNot,
RoleCondFormulaValueIsLocalOrRemote,
RoleCondFormulaValueAssignedRole,
RoleCondFormulaValueCreated,
RoleCondFormulaFollowersOrFollowingOrNotes,
RoleCondFormulaValue,
Expand Down Expand Up @@ -2727,6 +2732,9 @@ type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics'];
// @public (undocumented)
type RoleCondFormulaValue = components['schemas']['RoleCondFormulaValue'];

// @public (undocumented)
type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole'];

// @public (undocumented)
type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated'];

Expand Down
3 changes: 2 additions & 1 deletion packages/misskey-js/src/autogen/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type {
AdminDriveShowFileResponse,
AdminEmojiAddAliasesBulkRequest,
AdminEmojiAddRequest,
AdminEmojiAddResponse,
AdminEmojiCopyRequest,
AdminEmojiCopyResponse,
AdminEmojiDeleteBulkRequest,
Expand Down Expand Up @@ -578,7 +579,7 @@ export type Endpoints = {
'admin/drive/files': { req: AdminDriveFilesRequest; res: AdminDriveFilesResponse };
'admin/drive/show-file': { req: AdminDriveShowFileRequest; res: AdminDriveShowFileResponse };
'admin/emoji/add-aliases-bulk': { req: AdminEmojiAddAliasesBulkRequest; res: EmptyResponse };
'admin/emoji/add': { req: AdminEmojiAddRequest; res: EmptyResponse };
'admin/emoji/add': { req: AdminEmojiAddRequest; res: AdminEmojiAddResponse };
'admin/emoji/copy': { req: AdminEmojiCopyRequest; res: AdminEmojiCopyResponse };
'admin/emoji/delete-bulk': { req: AdminEmojiDeleteBulkRequest; res: EmptyResponse };
'admin/emoji/delete': { req: AdminEmojiDeleteRequest; res: EmptyResponse };
Expand Down
1 change: 1 addition & 0 deletions packages/misskey-js/src/autogen/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type AdminDriveShowFileRequest = operations['admin/drive/show-file']['req
export type AdminDriveShowFileResponse = operations['admin/drive/show-file']['responses']['200']['content']['application/json'];
export type AdminEmojiAddAliasesBulkRequest = operations['admin/emoji/add-aliases-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiAddRequest = operations['admin/emoji/add']['requestBody']['content']['application/json'];
export type AdminEmojiAddResponse = operations['admin/emoji/add']['responses']['200']['content']['application/json'];
export type AdminEmojiCopyRequest = operations['admin/emoji/copy']['requestBody']['content']['application/json'];
export type AdminEmojiCopyResponse = operations['admin/emoji/copy']['responses']['200']['content']['application/json'];
export type AdminEmojiDeleteBulkRequest = operations['admin/emoji/delete-bulk']['requestBody']['content']['application/json'];
Expand Down
1 change: 1 addition & 0 deletions packages/misskey-js/src/autogen/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type Signin = components['schemas']['Signin'];
export type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics'];
export type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot'];
export type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormulaValueIsLocalOrRemote'];
export type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole'];
export type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated'];
export type RoleCondFormulaFollowersOrFollowingOrNotes = components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes'];
export type RoleCondFormulaValue = components['schemas']['RoleCondFormulaValue'];
Expand Down
Loading

0 comments on commit 66d0b71

Please sign in to comment.