Merge tag '2025.4.0' into merge/2025-03-24

# Conflicts:
#	.github/workflows/storybook.yml
#	locales/index.d.ts
#	package.json
#	packages/backend/src/models/json-schema/role.ts
#	packages/frontend/src/components/MkPageWindow.vue
#	packages/frontend/src/pages/admin/roles.editor.vue
#	packages/frontend/src/pages/admin/roles.vue
#	packages/frontend/src/pages/settings/preferences.vue
#	packages/frontend/src/pages/settings/privacy.vue
#	packages/frontend/src/pages/timeline.vue
#	packages/frontend/src/pref-migrate.ts
#	packages/frontend/src/ui/_common_/common.vue
#	packages/frontend/src/ui/deck.vue
#	packages/frontend/src/ui/universal.vue
#	packages/misskey-js/src/autogen/types.ts
This commit is contained in:
Hazelnoot 2025-04-13 13:04:57 -04:00
commit 7132696285
98 changed files with 1621 additions and 2087 deletions

View file

@ -114,7 +114,7 @@ export class AntennaService implements OnApplicationShutdown {
if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') return false;
if (antenna.hideNotesInSensitiveChannel && note.channel?.isSensitive) return false;
if (antenna.excludeNotesInSensitiveChannel && note.channel?.isSensitive) return false;
if (antenna.excludeBots && noteUser.isBot) return false;

View file

@ -94,6 +94,40 @@ export class ChatService {
) {
}
@bindThis
public async getChatAvailability(userId: MiUser['id']): Promise<{ read: boolean; write: boolean; }> {
const policies = await this.roleService.getUserPolicies(userId);
switch (policies.chatAvailability) {
case 'available':
return {
read: true,
write: true,
};
case 'readonly':
return {
read: true,
write: false,
};
case 'unavailable':
return {
read: false,
write: false,
};
default:
throw new Error('invalid chat availability (unreachable)');
}
}
/** getChatAvailabilityの糖衣。主にAPI呼び出し時に走らせて、権限的に問題ない場合はそのまま続行する */
@bindThis
public async checkChatAvailability(userId: MiUser['id'], permission: 'read' | 'write') {
const policy = await this.getChatAvailability(userId);
if (policy[permission] === false) {
throw new Error('ROLE_PERMISSION_DENIED');
}
}
@bindThis
public async createMessageToUser(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toUser: MiUser, params: {
text?: string | null;
@ -140,7 +174,7 @@ export class ChatService {
}
}
if (!(await this.roleService.getUserPolicies(toUser.id)).canChat) {
if (!(await this.getChatAvailability(toUser.id)).write) {
throw new Error('recipient is cannot chat (policy)');
}

View file

@ -66,7 +66,7 @@ export type RolePolicies = {
canImportFollowing: boolean;
canImportMuting: boolean;
canImportUserLists: boolean;
canChat: boolean;
chatAvailability: 'available' | 'readonly' | 'unavailable';
};
export const DEFAULT_POLICIES: RolePolicies = {
@ -104,7 +104,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
canImportFollowing: true,
canImportMuting: true,
canImportUserLists: true,
canChat: true,
chatAvailability: 'available',
};
@Injectable()
@ -376,6 +376,12 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
return aggregate(policies.map(policy => policy.useDefault ? basePolicies[name] : policy.value));
}
function aggregateChatAvailability(vs: RolePolicies['chatAvailability'][]) {
if (vs.some(v => v === 'available')) return 'available';
if (vs.some(v => v === 'readonly')) return 'readonly';
return 'unavailable';
}
return {
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
btlAvailable: calc('btlAvailable', vs => vs.some(v => v === true)),
@ -411,7 +417,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)),
canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)),
canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)),
canChat: calc('canChat', vs => vs.some(v => v === true)),
chatAvailability: calc('chatAvailability', aggregateChatAvailability),
};
}

View file

@ -41,7 +41,7 @@ export class AntennaEntityService {
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
hideNotesInSensitiveChannel: antenna.hideNotesInSensitiveChannel,
excludeNotesInSensitiveChannel: antenna.excludeNotesInSensitiveChannel,
isActive: antenna.isActive,
hasUnreadNote: false, // TODO
notify: false, // 後方互換性のため

View file

@ -665,7 +665,7 @@ export class UserEntityService implements OnModuleInit {
followersVisibility: profile!.followersVisibility,
followingVisibility: profile!.followingVisibility,
chatScope: user.chatScope,
canChat: this.roleService.getUserPolicies(user.id).then(r => r.canChat),
canChat: this.roleService.getUserPolicies(user.id).then(r => r.chatAvailability === 'available'),
roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({
id: role.id,
name: role.name,