mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 07:24:13 +00:00 
			
		
		
		
	merge: timeline changes, new options, silence users (#97)
This commit is contained in:
		
						commit
						8debea7161
					
				
					 35 changed files with 280 additions and 14 deletions
				
			
		| 
						 | 
					@ -1101,6 +1101,10 @@ additionalEmojiDictionary: "Additional emoji dictionaries"
 | 
				
			||||||
installed: "Installed"
 | 
					installed: "Installed"
 | 
				
			||||||
branding: "Branding"
 | 
					branding: "Branding"
 | 
				
			||||||
enableServerMachineStats: "Publish server hardware stats"
 | 
					enableServerMachineStats: "Publish server hardware stats"
 | 
				
			||||||
 | 
					enableAchievements: "Enable Achievements"
 | 
				
			||||||
 | 
					turnOffAchievements: "Turning this off will disable the achievement system"
 | 
				
			||||||
 | 
					enableBotTrending: "Populate Hashtags with Bots"
 | 
				
			||||||
 | 
					turnOffBotTrending: "Turning this off will stop Bots from populating Hashtags"
 | 
				
			||||||
enableIdenticonGeneration: "Enable user identicon generation"
 | 
					enableIdenticonGeneration: "Enable user identicon generation"
 | 
				
			||||||
turnOffToImprovePerformance: "Turning this off can increase performance."
 | 
					turnOffToImprovePerformance: "Turning this off can increase performance."
 | 
				
			||||||
createInviteCode: "Generate invite"
 | 
					createInviteCode: "Generate invite"
 | 
				
			||||||
| 
						 | 
					@ -1137,6 +1141,7 @@ loadConversation: "Show conversation"
 | 
				
			||||||
pinnedList: "Pinned list"
 | 
					pinnedList: "Pinned list"
 | 
				
			||||||
keepScreenOn: "Keep screen on"
 | 
					keepScreenOn: "Keep screen on"
 | 
				
			||||||
clickToOpen: "Click to open notes"
 | 
					clickToOpen: "Click to open notes"
 | 
				
			||||||
 | 
					showBots: "Show bots in timeline"
 | 
				
			||||||
verifiedLink: "Link ownership has been verified"
 | 
					verifiedLink: "Link ownership has been verified"
 | 
				
			||||||
notifyNotes: "Notify about new notes"
 | 
					notifyNotes: "Notify about new notes"
 | 
				
			||||||
unnotifyNotes: "Stop notifying about new notes"
 | 
					unnotifyNotes: "Stop notifying about new notes"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1099,6 +1099,10 @@ export interface Locale {
 | 
				
			||||||
    "installed": string;
 | 
					    "installed": string;
 | 
				
			||||||
    "branding": string;
 | 
					    "branding": string;
 | 
				
			||||||
    "enableServerMachineStats": string;
 | 
					    "enableServerMachineStats": string;
 | 
				
			||||||
 | 
					    "enableAchievements": string;
 | 
				
			||||||
 | 
					    "turnOffAchievements": string;
 | 
				
			||||||
 | 
					    "enableBotTrending": string;
 | 
				
			||||||
 | 
					    "turnOffBotTrending": string;
 | 
				
			||||||
    "enableIdenticonGeneration": string;
 | 
					    "enableIdenticonGeneration": string;
 | 
				
			||||||
    "turnOffToImprovePerformance": string;
 | 
					    "turnOffToImprovePerformance": string;
 | 
				
			||||||
    "createInviteCode": string;
 | 
					    "createInviteCode": string;
 | 
				
			||||||
| 
						 | 
					@ -1135,6 +1139,7 @@ export interface Locale {
 | 
				
			||||||
    "pinnedList": string;
 | 
					    "pinnedList": string;
 | 
				
			||||||
    "keepScreenOn": string;
 | 
					    "keepScreenOn": string;
 | 
				
			||||||
    "clickToOpen": string;
 | 
					    "clickToOpen": string;
 | 
				
			||||||
 | 
					    "showBots": string;
 | 
				
			||||||
    "verifiedLink": string;
 | 
					    "verifiedLink": string;
 | 
				
			||||||
    "notifyNotes": string;
 | 
					    "notifyNotes": string;
 | 
				
			||||||
    "unnotifyNotes": string;
 | 
					    "unnotifyNotes": string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1096,6 +1096,10 @@ additionalEmojiDictionary: "絵文字の追加辞書"
 | 
				
			||||||
installed: "インストール済み"
 | 
					installed: "インストール済み"
 | 
				
			||||||
branding: "ブランディング"
 | 
					branding: "ブランディング"
 | 
				
			||||||
enableServerMachineStats: "サーバーのマシン情報を公開する"
 | 
					enableServerMachineStats: "サーバーのマシン情報を公開する"
 | 
				
			||||||
 | 
					enableAchievements: "実績を有効にする"
 | 
				
			||||||
 | 
					turnOffAchievements: "これをオフにすると、達成システムは無効になります。"
 | 
				
			||||||
 | 
					enableBotTrending: "ハッシュタグにボットを追加する"
 | 
				
			||||||
 | 
					turnOffBotTrending: "これをオフにすると、ボットがハッシュタグを入力しなくなります。"
 | 
				
			||||||
enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
 | 
					enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
 | 
				
			||||||
turnOffToImprovePerformance: "オフにするとパフォーマンスが向上します。"
 | 
					turnOffToImprovePerformance: "オフにするとパフォーマンスが向上します。"
 | 
				
			||||||
createInviteCode: "招待コードを作成"
 | 
					createInviteCode: "招待コードを作成"
 | 
				
			||||||
| 
						 | 
					@ -1132,6 +1136,7 @@ loadConversation: "会話を見る"
 | 
				
			||||||
pinnedList: "ピン留めされたリスト"
 | 
					pinnedList: "ピン留めされたリスト"
 | 
				
			||||||
keepScreenOn: "デバイスの画面を常にオンにする"
 | 
					keepScreenOn: "デバイスの画面を常にオンにする"
 | 
				
			||||||
clickToOpen: "クリックしてノートを開く"
 | 
					clickToOpen: "クリックしてノートを開く"
 | 
				
			||||||
 | 
					showBots: "ボットをタイムラインに表示"
 | 
				
			||||||
verifiedLink: "このリンク先の所有者であることが確認されました"
 | 
					verifiedLink: "このリンク先の所有者であることが確認されました"
 | 
				
			||||||
notifyNotes: "投稿を通知"
 | 
					notifyNotes: "投稿を通知"
 | 
				
			||||||
unnotifyNotes: "投稿の通知を解除"
 | 
					unnotifyNotes: "投稿の通知を解除"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								packages/backend/migration/1697603945000-BotTrending.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/backend/migration/1697603945000-BotTrending.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-FileCopyrightText: syuilo and other misskey contributors
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class BotTrending1697603945000 {
 | 
				
			||||||
 | 
					    name = 'BotTrending1697603945000'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async up(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "enableBotTrending" boolean NOT NULL DEFAULT true`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async down(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableBotTrending"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								packages/backend/migration/1697624010000-isSilenced.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/backend/migration/1697624010000-isSilenced.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * SPDX-FileCopyrightText: syuilo and other misskey contributors
 | 
				
			||||||
 | 
					 * SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class IsSilenced1697624010000 {
 | 
				
			||||||
 | 
					    name = 'IsSilenced1697624010000'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async up(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" ADD "isSilenced" boolean NOT NULL DEFAULT false`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async down(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isSilenced"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -511,7 +511,11 @@ export class NoteCreateService implements OnApplicationShutdown {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// ハッシュタグ更新
 | 
							// ハッシュタグ更新
 | 
				
			||||||
		if (data.visibility === 'public' || data.visibility === 'home') {
 | 
							if (data.visibility === 'public' || data.visibility === 'home') {
 | 
				
			||||||
			this.hashtagService.updateHashtags(user, tags);
 | 
								if (user.isBot && meta.enableBotTrending) {
 | 
				
			||||||
 | 
									this.hashtagService.updateHashtags(user, tags);
 | 
				
			||||||
 | 
								} else if (!user.isBot) {
 | 
				
			||||||
 | 
									this.hashtagService.updateHashtags(user, tags);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Increment notes count (user)
 | 
							// Increment notes count (user)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -368,6 +368,7 @@ export class UserEntityService implements OnModuleInit {
 | 
				
			||||||
			createdAt: this.idService.parse(user.id).date.toISOString(),
 | 
								createdAt: this.idService.parse(user.id).date.toISOString(),
 | 
				
			||||||
			isBot: user.isBot ?? falsy,
 | 
								isBot: user.isBot ?? falsy,
 | 
				
			||||||
			isCat: user.isCat ?? falsy,
 | 
								isCat: user.isCat ?? falsy,
 | 
				
			||||||
 | 
								isSilenced: user.isSilenced || this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
 | 
				
			||||||
			speakAsCat: user.speakAsCat ?? falsy,
 | 
								speakAsCat: user.speakAsCat ?? falsy,
 | 
				
			||||||
			instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
 | 
								instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
 | 
				
			||||||
				name: instance.name,
 | 
									name: instance.name,
 | 
				
			||||||
| 
						 | 
					@ -404,7 +405,6 @@ export class UserEntityService implements OnModuleInit {
 | 
				
			||||||
				backgroundUrl: user.backgroundUrl,
 | 
									backgroundUrl: user.backgroundUrl,
 | 
				
			||||||
				backgroundBlurhash: user.backgroundBlurhash,
 | 
									backgroundBlurhash: user.backgroundBlurhash,
 | 
				
			||||||
				isLocked: user.isLocked,
 | 
									isLocked: user.isLocked,
 | 
				
			||||||
				isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
 | 
					 | 
				
			||||||
				isSuspended: user.isSuspended ?? falsy,
 | 
									isSuspended: user.isSuspended ?? falsy,
 | 
				
			||||||
				location: profile!.location,
 | 
									location: profile!.location,
 | 
				
			||||||
				birthday: profile!.birthday,
 | 
									birthday: profile!.birthday,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -247,6 +247,11 @@ export class MiMeta {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public enableSensitiveMediaDetectionForVideos: boolean;
 | 
						public enableSensitiveMediaDetectionForVideos: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('boolean', {
 | 
				
			||||||
 | 
							default: true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public enableBotTrending: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 1024,
 | 
							length: 1024,
 | 
				
			||||||
		nullable: true,
 | 
							nullable: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -173,6 +173,12 @@ export class MiUser {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public isSuspended: boolean;
 | 
						public isSuspended: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('boolean', {
 | 
				
			||||||
 | 
							default: false,
 | 
				
			||||||
 | 
							comment: 'Whether the User is silenced.',
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public isSilenced: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('boolean', {
 | 
						@Column('boolean', {
 | 
				
			||||||
		default: false,
 | 
							default: false,
 | 
				
			||||||
		comment: 'Whether the User is locked.',
 | 
							comment: 'Whether the User is locked.',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,6 +47,10 @@ export const packedUserLiteSchema = {
 | 
				
			||||||
			nullable: false, optional: true,
 | 
								nullable: false, optional: true,
 | 
				
			||||||
			default: false,
 | 
								default: false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							isSilenced: {
 | 
				
			||||||
 | 
								type: 'boolean',
 | 
				
			||||||
 | 
								nullable: false, optional: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		isBot: {
 | 
							isBot: {
 | 
				
			||||||
			type: 'boolean',
 | 
								type: 'boolean',
 | 
				
			||||||
			nullable: false, optional: true,
 | 
								nullable: false, optional: true,
 | 
				
			||||||
| 
						 | 
					@ -135,10 +139,6 @@ export const packedUserDetailedNotMeOnlySchema = {
 | 
				
			||||||
			type: 'boolean',
 | 
								type: 'boolean',
 | 
				
			||||||
			nullable: false, optional: false,
 | 
								nullable: false, optional: false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		isSilenced: {
 | 
					 | 
				
			||||||
			type: 'boolean',
 | 
					 | 
				
			||||||
			nullable: false, optional: false,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		isSuspended: {
 | 
							isSuspended: {
 | 
				
			||||||
			type: 'boolean',
 | 
								type: 'boolean',
 | 
				
			||||||
			nullable: false, optional: false,
 | 
								nullable: false, optional: false,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,6 +63,8 @@ import * as ep___admin_showUser from './endpoints/admin/show-user.js';
 | 
				
			||||||
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
 | 
					import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
 | 
				
			||||||
import * as ep___admin_nsfwUser from './endpoints/admin/nsfw-user.js';
 | 
					import * as ep___admin_nsfwUser from './endpoints/admin/nsfw-user.js';
 | 
				
			||||||
import * as ep___admin_unnsfwUser from './endpoints/admin/unnsfw-user.js';
 | 
					import * as ep___admin_unnsfwUser from './endpoints/admin/unnsfw-user.js';
 | 
				
			||||||
 | 
					import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js';
 | 
				
			||||||
 | 
					import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js';
 | 
				
			||||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
 | 
					import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
 | 
				
			||||||
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
 | 
					import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
 | 
				
			||||||
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
 | 
					import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
 | 
				
			||||||
| 
						 | 
					@ -418,6 +420,8 @@ const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep_
 | 
				
			||||||
const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default };
 | 
					const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default };
 | 
				
			||||||
const $admin_nsfwUser: Provider = { provide: 'ep:admin/nsfw-user', useClass: ep___admin_nsfwUser.default };
 | 
					const $admin_nsfwUser: Provider = { provide: 'ep:admin/nsfw-user', useClass: ep___admin_nsfwUser.default };
 | 
				
			||||||
const $admin_unnsfwUser: Provider = { provide: 'ep:admin/unnsfw-user', useClass: ep___admin_unnsfwUser.default };
 | 
					const $admin_unnsfwUser: Provider = { provide: 'ep:admin/unnsfw-user', useClass: ep___admin_unnsfwUser.default };
 | 
				
			||||||
 | 
					const $admin_silenceUser: Provider = { provide: 'ep:admin/silence-user', useClass: ep___admin_silenceUser.default };
 | 
				
			||||||
 | 
					const $admin_unsilenceUser: Provider = { provide: 'ep:admin/unsilence-user', useClass: ep___admin_unsilenceUser.default };
 | 
				
			||||||
const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
 | 
					const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
 | 
				
			||||||
const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
 | 
					const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
 | 
				
			||||||
const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
 | 
					const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
 | 
				
			||||||
| 
						 | 
					@ -777,6 +781,8 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 | 
				
			||||||
		$admin_showUsers,
 | 
							$admin_showUsers,
 | 
				
			||||||
		$admin_nsfwUser,
 | 
							$admin_nsfwUser,
 | 
				
			||||||
		$admin_unnsfwUser,
 | 
							$admin_unnsfwUser,
 | 
				
			||||||
 | 
							$admin_silenceUser,
 | 
				
			||||||
 | 
							$admin_unsilenceUser,
 | 
				
			||||||
		$admin_suspendUser,
 | 
							$admin_suspendUser,
 | 
				
			||||||
		$admin_unsuspendUser,
 | 
							$admin_unsuspendUser,
 | 
				
			||||||
		$admin_updateMeta,
 | 
							$admin_updateMeta,
 | 
				
			||||||
| 
						 | 
					@ -1130,6 +1136,8 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 | 
				
			||||||
		$admin_showUsers,
 | 
							$admin_showUsers,
 | 
				
			||||||
		$admin_nsfwUser,
 | 
							$admin_nsfwUser,
 | 
				
			||||||
		$admin_unnsfwUser,
 | 
							$admin_unnsfwUser,
 | 
				
			||||||
 | 
							$admin_silenceUser,
 | 
				
			||||||
 | 
							$admin_unsilenceUser,
 | 
				
			||||||
		$admin_suspendUser,
 | 
							$admin_suspendUser,
 | 
				
			||||||
		$admin_unsuspendUser,
 | 
							$admin_unsuspendUser,
 | 
				
			||||||
		$admin_updateMeta,
 | 
							$admin_updateMeta,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,6 +63,8 @@ import * as ep___admin_showUser from './endpoints/admin/show-user.js';
 | 
				
			||||||
import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
 | 
					import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
 | 
				
			||||||
import * as ep___admin_nsfwUser from './endpoints/admin/nsfw-user.js';
 | 
					import * as ep___admin_nsfwUser from './endpoints/admin/nsfw-user.js';
 | 
				
			||||||
import * as ep___admin_unnsfwUser from './endpoints/admin/unnsfw-user.js';
 | 
					import * as ep___admin_unnsfwUser from './endpoints/admin/unnsfw-user.js';
 | 
				
			||||||
 | 
					import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js';
 | 
				
			||||||
 | 
					import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js';
 | 
				
			||||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
 | 
					import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
 | 
				
			||||||
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
 | 
					import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
 | 
				
			||||||
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
 | 
					import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
 | 
				
			||||||
| 
						 | 
					@ -416,6 +418,8 @@ const eps = [
 | 
				
			||||||
	['admin/show-users', ep___admin_showUsers],
 | 
						['admin/show-users', ep___admin_showUsers],
 | 
				
			||||||
	['admin/nsfw-user', ep___admin_nsfwUser],
 | 
						['admin/nsfw-user', ep___admin_nsfwUser],
 | 
				
			||||||
	['admin/unnsfw-user', ep___admin_unnsfwUser],
 | 
						['admin/unnsfw-user', ep___admin_unnsfwUser],
 | 
				
			||||||
 | 
						['admin/silence-user', ep___admin_silenceUser],
 | 
				
			||||||
 | 
						['admin/unsilence-user', ep___admin_unsilenceUser],
 | 
				
			||||||
	['admin/suspend-user', ep___admin_suspendUser],
 | 
						['admin/suspend-user', ep___admin_suspendUser],
 | 
				
			||||||
	['admin/unsuspend-user', ep___admin_unsuspendUser],
 | 
						['admin/unsuspend-user', ep___admin_unsuspendUser],
 | 
				
			||||||
	['admin/update-meta', ep___admin_updateMeta],
 | 
						['admin/update-meta', ep___admin_updateMeta],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -178,6 +178,10 @@ export const meta = {
 | 
				
			||||||
				type: 'boolean',
 | 
									type: 'boolean',
 | 
				
			||||||
				optional: false, nullable: false,
 | 
									optional: false, nullable: false,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
								enableBotTrending: {
 | 
				
			||||||
 | 
									type: 'boolean',
 | 
				
			||||||
 | 
									optional: false, nullable: false,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			proxyAccountId: {
 | 
								proxyAccountId: {
 | 
				
			||||||
				type: 'string',
 | 
									type: 'string',
 | 
				
			||||||
				optional: false, nullable: true,
 | 
									optional: false, nullable: true,
 | 
				
			||||||
| 
						 | 
					@ -391,6 +395,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
 | 
									sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
 | 
				
			||||||
				setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
 | 
									setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
 | 
				
			||||||
				enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
 | 
									enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
 | 
				
			||||||
 | 
									enableBotTrending: instance.enableBotTrending,
 | 
				
			||||||
				proxyAccountId: instance.proxyAccountId,
 | 
									proxyAccountId: instance.proxyAccountId,
 | 
				
			||||||
				summalyProxy: instance.summalyProxy,
 | 
									summalyProxy: instance.summalyProxy,
 | 
				
			||||||
				email: instance.email,
 | 
									email: instance.email,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const isModerator = await this.roleService.isModerator(user);
 | 
								const isModerator = await this.roleService.isModerator(user);
 | 
				
			||||||
			const isSilenced = !(await this.roleService.getUserPolicies(user.id)).canPublicNote;
 | 
								const isSilenced = user.isSilenced || !(await this.roleService.getUserPolicies(user.id)).canPublicNote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const _me = await this.usersRepository.findOneByOrFail({ id: me.id });
 | 
								const _me = await this.usersRepository.findOneByOrFail({ id: me.id });
 | 
				
			||||||
			if (!await this.roleService.isAdministrator(_me) && await this.roleService.isAdministrator(user)) {
 | 
								if (!await this.roleService.isAdministrator(_me) && await this.roleService.isAdministrator(user)) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
				
			||||||
 | 
					import type { UsersRepository } from '@/models/_.js';
 | 
				
			||||||
 | 
					import { DI } from '@/di-symbols.js';
 | 
				
			||||||
 | 
					import { RoleService } from '@/core/RoleService.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						tags: ['admin'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
						requireModerator: true,
 | 
				
			||||||
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const paramDef = {
 | 
				
			||||||
 | 
						type: 'object',
 | 
				
			||||||
 | 
						properties: {
 | 
				
			||||||
 | 
							userId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						required: ['userId'],
 | 
				
			||||||
 | 
					} 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,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private roleService: RoleService,
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super(meta, paramDef, async (ps, me) => {
 | 
				
			||||||
 | 
								const user = await this.usersRepository.findOneBy({ id: ps.userId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (user == null) {
 | 
				
			||||||
 | 
									throw new Error('user not found');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (await this.roleService.isModerator(user)) {
 | 
				
			||||||
 | 
									throw new Error('cannot silence moderator account');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								await this.usersRepository.update(user.id, {
 | 
				
			||||||
 | 
									isSilenced: true,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					import { Inject, Injectable } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
				
			||||||
 | 
					import type { UsersRepository } from '@/models/_.js';
 | 
				
			||||||
 | 
					import { DI } from '@/di-symbols.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						tags: ['admin'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
						requireModerator: true,
 | 
				
			||||||
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const paramDef = {
 | 
				
			||||||
 | 
						type: 'object',
 | 
				
			||||||
 | 
						properties: {
 | 
				
			||||||
 | 
							userId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						required: ['userId'],
 | 
				
			||||||
 | 
					} 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,
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super(meta, paramDef, async (ps, me) => {
 | 
				
			||||||
 | 
								const user = await this.usersRepository.findOneBy({ id: ps.userId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (user == null) {
 | 
				
			||||||
 | 
									throw new Error('user not found');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								await this.usersRepository.update(user.id, {
 | 
				
			||||||
 | 
									isSilenced: false,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -72,6 +72,7 @@ export const paramDef = {
 | 
				
			||||||
		sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
 | 
							sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
 | 
				
			||||||
		setSensitiveFlagAutomatically: { type: 'boolean' },
 | 
							setSensitiveFlagAutomatically: { type: 'boolean' },
 | 
				
			||||||
		enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
 | 
							enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
 | 
				
			||||||
 | 
							enableBotTrending: { type: 'boolean' },
 | 
				
			||||||
		proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
 | 
							proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
 | 
				
			||||||
		maintainerName: { type: 'string', nullable: true },
 | 
							maintainerName: { type: 'string', nullable: true },
 | 
				
			||||||
		maintainerEmail: { type: 'string', nullable: true },
 | 
							maintainerEmail: { type: 'string', nullable: true },
 | 
				
			||||||
| 
						 | 
					@ -301,6 +302,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
 | 
									set.enableSensitiveMediaDetectionForVideos = ps.enableSensitiveMediaDetectionForVideos;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (ps.enableBotTrending !== undefined) {
 | 
				
			||||||
 | 
									set.enableBotTrending = ps.enableBotTrending;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (ps.proxyAccountId !== undefined) {
 | 
								if (ps.proxyAccountId !== undefined) {
 | 
				
			||||||
				set.proxyAccountId = ps.proxyAccountId;
 | 
									set.proxyAccountId = ps.proxyAccountId;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 | 
				
			||||||
import { DI } from '@/di-symbols.js';
 | 
					import { DI } from '@/di-symbols.js';
 | 
				
			||||||
import { RoleService } from '@/core/RoleService.js';
 | 
					import { RoleService } from '@/core/RoleService.js';
 | 
				
			||||||
import { ApiError } from '../../error.js';
 | 
					import { ApiError } from '../../error.js';
 | 
				
			||||||
 | 
					import { CacheService } from '@/core/CacheService.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	tags: ['notes'],
 | 
						tags: ['notes'],
 | 
				
			||||||
| 
						 | 
					@ -40,6 +41,7 @@ export const paramDef = {
 | 
				
			||||||
	type: 'object',
 | 
						type: 'object',
 | 
				
			||||||
	properties: {
 | 
						properties: {
 | 
				
			||||||
		withFiles: { type: 'boolean', default: false },
 | 
							withFiles: { type: 'boolean', default: false },
 | 
				
			||||||
 | 
							withBots: { type: 'boolean', default: true },
 | 
				
			||||||
		withRenotes: { type: 'boolean', default: true },
 | 
							withRenotes: { type: 'boolean', default: true },
 | 
				
			||||||
		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
							limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
				
			||||||
		sinceId: { type: 'string', format: 'misskey:id' },
 | 
							sinceId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
| 
						 | 
					@ -60,6 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
		private queryService: QueryService,
 | 
							private queryService: QueryService,
 | 
				
			||||||
		private roleService: RoleService,
 | 
							private roleService: RoleService,
 | 
				
			||||||
		private activeUsersChart: ActiveUsersChart,
 | 
							private activeUsersChart: ActiveUsersChart,
 | 
				
			||||||
 | 
							private cacheService: CacheService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(meta, paramDef, async (ps, me) => {
 | 
							super(meta, paramDef, async (ps, me) => {
 | 
				
			||||||
			const policies = await this.roleService.getUserPolicies(me ? me.id : null);
 | 
								const policies = await this.roleService.getUserPolicies(me ? me.id : null);
 | 
				
			||||||
| 
						 | 
					@ -67,6 +70,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				throw new ApiError(meta.errors.gtlDisabled);
 | 
									throw new ApiError(meta.errors.gtlDisabled);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const [
 | 
				
			||||||
 | 
									followings,
 | 
				
			||||||
 | 
								] = me ? await Promise.all([
 | 
				
			||||||
 | 
									this.cacheService.userFollowingsCache.fetch(me.id),
 | 
				
			||||||
 | 
								]) : [undefined];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			//#region Construct query
 | 
								//#region Construct query
 | 
				
			||||||
			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
 | 
								const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
 | 
				
			||||||
				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
 | 
									ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
 | 
				
			||||||
| 
						 | 
					@ -87,9 +96,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			if (ps.withFiles) {
 | 
								if (ps.withFiles) {
 | 
				
			||||||
				query.andWhere('note.fileIds != \'{}\'');
 | 
									query.andWhere('note.fileIds != \'{}\'');
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!ps.withBots) query.andWhere('user.isBot = FALSE');
 | 
				
			||||||
			//#endregion
 | 
								//#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const timeline = await query.limit(ps.limit).getMany();
 | 
								let timeline = await query.limit(ps.limit).getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								timeline = timeline.filter(note => {
 | 
				
			||||||
 | 
									if (note.user?.isSilenced && me && followings && note.userId !== me.id && !followings[note.userId]) return false;
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			process.nextTick(() => {
 | 
								process.nextTick(() => {
 | 
				
			||||||
				if (me) {
 | 
									if (me) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,6 +56,7 @@ export const paramDef = {
 | 
				
			||||||
		withFiles: { type: 'boolean', default: false },
 | 
							withFiles: { type: 'boolean', default: false },
 | 
				
			||||||
		withRenotes: { type: 'boolean', default: true },
 | 
							withRenotes: { type: 'boolean', default: true },
 | 
				
			||||||
		withReplies: { type: 'boolean', default: false },
 | 
							withReplies: { type: 'boolean', default: false },
 | 
				
			||||||
 | 
							withBots: { type: 'boolean', default: true },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	required: [],
 | 
						required: [],
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
| 
						 | 
					@ -86,10 +87,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const [
 | 
								const [
 | 
				
			||||||
 | 
									followings,
 | 
				
			||||||
				userIdsWhoMeMuting,
 | 
									userIdsWhoMeMuting,
 | 
				
			||||||
				userIdsWhoMeMutingRenotes,
 | 
									userIdsWhoMeMutingRenotes,
 | 
				
			||||||
				userIdsWhoBlockingMe,
 | 
									userIdsWhoBlockingMe,
 | 
				
			||||||
			] = await Promise.all([
 | 
								] = await Promise.all([
 | 
				
			||||||
 | 
									this.cacheService.userFollowingsCache.fetch(me.id),
 | 
				
			||||||
				this.cacheService.userMutingsCache.fetch(me.id),
 | 
									this.cacheService.userMutingsCache.fetch(me.id),
 | 
				
			||||||
				this.cacheService.renoteMutingsCache.fetch(me.id),
 | 
									this.cacheService.renoteMutingsCache.fetch(me.id),
 | 
				
			||||||
				this.cacheService.userBlockedCache.fetch(me.id),
 | 
									this.cacheService.userBlockedCache.fetch(me.id),
 | 
				
			||||||
| 
						 | 
					@ -134,6 +137,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				.leftJoinAndSelect('renote.user', 'renoteUser')
 | 
									.leftJoinAndSelect('renote.user', 'renoteUser')
 | 
				
			||||||
				.leftJoinAndSelect('note.channel', 'channel');
 | 
									.leftJoinAndSelect('note.channel', 'channel');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!ps.withBots) query.andWhere('user.isBot = FALSE');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			let timeline = await query.getMany();
 | 
								let timeline = await query.getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			timeline = timeline.filter(note => {
 | 
								timeline = timeline.filter(note => {
 | 
				
			||||||
| 
						 | 
					@ -148,6 +153,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
						if (ps.withRenotes === false) return false;
 | 
											if (ps.withRenotes === false) return false;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									if (note.user?.isSilenced && note.userId !== me.id && !followings[note.userId]) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return true;
 | 
									return true;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,6 +46,7 @@ export const paramDef = {
 | 
				
			||||||
		withFiles: { type: 'boolean', default: false },
 | 
							withFiles: { type: 'boolean', default: false },
 | 
				
			||||||
		withRenotes: { type: 'boolean', default: true },
 | 
							withRenotes: { type: 'boolean', default: true },
 | 
				
			||||||
		withReplies: { type: 'boolean', default: false },
 | 
							withReplies: { type: 'boolean', default: false },
 | 
				
			||||||
 | 
							withBots: { type: 'boolean', default: true },
 | 
				
			||||||
		excludeNsfw: { type: 'boolean', default: false },
 | 
							excludeNsfw: { type: 'boolean', default: false },
 | 
				
			||||||
		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
							limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
				
			||||||
		sinceId: { type: 'string', format: 'misskey:id' },
 | 
							sinceId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
| 
						 | 
					@ -82,14 +83,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const [
 | 
								const [
 | 
				
			||||||
 | 
									followings,
 | 
				
			||||||
				userIdsWhoMeMuting,
 | 
									userIdsWhoMeMuting,
 | 
				
			||||||
				userIdsWhoMeMutingRenotes,
 | 
									userIdsWhoMeMutingRenotes,
 | 
				
			||||||
				userIdsWhoBlockingMe,
 | 
									userIdsWhoBlockingMe,
 | 
				
			||||||
			] = me ? await Promise.all([
 | 
								] = me ? await Promise.all([
 | 
				
			||||||
 | 
									this.cacheService.userFollowingsCache.fetch(me.id),
 | 
				
			||||||
				this.cacheService.userMutingsCache.fetch(me.id),
 | 
									this.cacheService.userMutingsCache.fetch(me.id),
 | 
				
			||||||
				this.cacheService.renoteMutingsCache.fetch(me.id),
 | 
									this.cacheService.renoteMutingsCache.fetch(me.id),
 | 
				
			||||||
				this.cacheService.userBlockedCache.fetch(me.id),
 | 
									this.cacheService.userBlockedCache.fetch(me.id),
 | 
				
			||||||
			]) : [new Set<string>(), new Set<string>(), new Set<string>()];
 | 
								]) : [undefined, new Set<string>(), new Set<string>(), new Set<string>()];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			let noteIds: string[];
 | 
								let noteIds: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -119,6 +122,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				.leftJoinAndSelect('renote.user', 'renoteUser')
 | 
									.leftJoinAndSelect('renote.user', 'renoteUser')
 | 
				
			||||||
				.leftJoinAndSelect('note.channel', 'channel');
 | 
									.leftJoinAndSelect('note.channel', 'channel');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!ps.withBots) query.andWhere('user.isBot = FALSE');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			let timeline = await query.getMany();
 | 
								let timeline = await query.getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			timeline = timeline.filter(note => {
 | 
								timeline = timeline.filter(note => {
 | 
				
			||||||
| 
						 | 
					@ -134,6 +139,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
						if (ps.withRenotes === false) return false;
 | 
											if (ps.withRenotes === false) return false;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									if (note.user?.isSilenced && me && followings && note.userId !== me.id && !followings[note.userId]) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return true;
 | 
									return true;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 | 
				
			||||||
import { QueryService } from '@/core/QueryService.js';
 | 
					import { QueryService } from '@/core/QueryService.js';
 | 
				
			||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 | 
					import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 | 
				
			||||||
import { DI } from '@/di-symbols.js';
 | 
					import { DI } from '@/di-symbols.js';
 | 
				
			||||||
 | 
					import { MetaService } from '@/core/MetaService.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	tags: ['notes', 'hashtags'],
 | 
						tags: ['notes', 'hashtags'],
 | 
				
			||||||
| 
						 | 
					@ -71,6 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private noteEntityService: NoteEntityService,
 | 
							private noteEntityService: NoteEntityService,
 | 
				
			||||||
		private queryService: QueryService,
 | 
							private queryService: QueryService,
 | 
				
			||||||
 | 
							private metaService: MetaService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(meta, paramDef, async (ps, me) => {
 | 
							super(meta, paramDef, async (ps, me) => {
 | 
				
			||||||
			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 | 
								const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 | 
				
			||||||
| 
						 | 
					@ -80,6 +82,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				.leftJoinAndSelect('reply.user', 'replyUser')
 | 
									.leftJoinAndSelect('reply.user', 'replyUser')
 | 
				
			||||||
				.leftJoinAndSelect('renote.user', 'renoteUser');
 | 
									.leftJoinAndSelect('renote.user', 'renoteUser');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const meta = await this.metaService.fetch(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!meta.enableBotTrending) query.andWhere('user.isBot = FALSE');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.queryService.generateVisibilityQuery(query, me);
 | 
								this.queryService.generateVisibilityQuery(query, me);
 | 
				
			||||||
			if (me) this.queryService.generateMutedUserQuery(query, me);
 | 
								if (me) this.queryService.generateMutedUserQuery(query, me);
 | 
				
			||||||
			if (me) this.queryService.generateBlockedUserQuery(query, me);
 | 
								if (me) this.queryService.generateBlockedUserQuery(query, me);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,6 +46,7 @@ export const paramDef = {
 | 
				
			||||||
		includeLocalRenotes: { type: 'boolean', default: true },
 | 
							includeLocalRenotes: { type: 'boolean', default: true },
 | 
				
			||||||
		withFiles: { type: 'boolean', default: false },
 | 
							withFiles: { type: 'boolean', default: false },
 | 
				
			||||||
		withRenotes: { type: 'boolean', default: true },
 | 
							withRenotes: { type: 'boolean', default: true },
 | 
				
			||||||
 | 
							withBots: { type: 'boolean', default: true },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	required: [],
 | 
						required: [],
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
| 
						 | 
					@ -97,6 +98,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				.leftJoinAndSelect('renote.user', 'renoteUser')
 | 
									.leftJoinAndSelect('renote.user', 'renoteUser')
 | 
				
			||||||
				.leftJoinAndSelect('note.channel', 'channel');
 | 
									.leftJoinAndSelect('note.channel', 'channel');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!ps.withBots) query.andWhere('user.isBot = FALSE');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			let timeline = await query.getMany();
 | 
								let timeline = await query.getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			timeline = timeline.filter(note => {
 | 
								timeline = timeline.filter(note => {
 | 
				
			||||||
| 
						 | 
					@ -114,6 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
				if (note.reply && note.reply.visibility === 'followers') {
 | 
									if (note.reply && note.reply.visibility === 'followers') {
 | 
				
			||||||
					if (!Object.hasOwn(followings, note.reply.userId)) return false;
 | 
										if (!Object.hasOwn(followings, note.reply.userId)) return false;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									if (note.user?.isSilenced && note.userId !== me.id && !followings[note.userId]) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return true;
 | 
									return true;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ class GlobalTimelineChannel extends Channel {
 | 
				
			||||||
	public static requireCredential = false;
 | 
						public static requireCredential = false;
 | 
				
			||||||
	private withRenotes: boolean;
 | 
						private withRenotes: boolean;
 | 
				
			||||||
	private withFiles: boolean;
 | 
						private withFiles: boolean;
 | 
				
			||||||
 | 
						private withBots: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
		private metaService: MetaService,
 | 
							private metaService: MetaService,
 | 
				
			||||||
| 
						 | 
					@ -40,6 +41,7 @@ class GlobalTimelineChannel extends Channel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.withRenotes = params.withRenotes ?? true;
 | 
							this.withRenotes = params.withRenotes ?? true;
 | 
				
			||||||
		this.withFiles = params.withFiles ?? false;
 | 
							this.withFiles = params.withFiles ?? false;
 | 
				
			||||||
 | 
							this.withBots = params.withBots ?? true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Subscribe events
 | 
							// Subscribe events
 | 
				
			||||||
		this.subscriber.on('notesStream', this.onNote);
 | 
							this.subscriber.on('notesStream', this.onNote);
 | 
				
			||||||
| 
						 | 
					@ -48,6 +50,7 @@ class GlobalTimelineChannel extends Channel {
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	private async onNote(note: Packed<'Note'>) {
 | 
						private async onNote(note: Packed<'Note'>) {
 | 
				
			||||||
		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
 | 
							if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
 | 
				
			||||||
 | 
							if (!this.withBots && note.user.isBot) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note.visibility !== 'public') return;
 | 
							if (note.visibility !== 'public') return;
 | 
				
			||||||
		if (note.channelId != null) return;
 | 
							if (note.channelId != null) return;
 | 
				
			||||||
| 
						 | 
					@ -59,6 +62,8 @@ class GlobalTimelineChannel extends Channel {
 | 
				
			||||||
			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
 | 
								if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
							if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Ignore notes from instances the user has muted
 | 
							// Ignore notes from instances the user has muted
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,6 +64,8 @@ class HomeTimelineChannel extends Channel {
 | 
				
			||||||
			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
 | 
								if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
							if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | 
							// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ class HybridTimelineChannel extends Channel {
 | 
				
			||||||
	public static requireCredential = true;
 | 
						public static requireCredential = true;
 | 
				
			||||||
	private withRenotes: boolean;
 | 
						private withRenotes: boolean;
 | 
				
			||||||
	private withReplies: boolean;
 | 
						private withReplies: boolean;
 | 
				
			||||||
 | 
						private withBots: boolean;
 | 
				
			||||||
	private withFiles: boolean;
 | 
						private withFiles: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
| 
						 | 
					@ -41,6 +42,7 @@ class HybridTimelineChannel extends Channel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.withRenotes = params.withRenotes ?? true;
 | 
							this.withRenotes = params.withRenotes ?? true;
 | 
				
			||||||
		this.withReplies = params.withReplies ?? false;
 | 
							this.withReplies = params.withReplies ?? false;
 | 
				
			||||||
 | 
							this.withBots = params.withBots ?? true;
 | 
				
			||||||
		this.withFiles = params.withFiles ?? false;
 | 
							this.withFiles = params.withFiles ?? false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Subscribe events
 | 
							// Subscribe events
 | 
				
			||||||
| 
						 | 
					@ -50,6 +52,7 @@ class HybridTimelineChannel extends Channel {
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	private async onNote(note: Packed<'Note'>) {
 | 
						private async onNote(note: Packed<'Note'>) {
 | 
				
			||||||
		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
 | 
							if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
 | 
				
			||||||
 | 
							if (!this.withBots && note.user.isBot) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// チャンネルの投稿ではなく、自分自身の投稿 または
 | 
							// チャンネルの投稿ではなく、自分自身の投稿 または
 | 
				
			||||||
		// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
 | 
							// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
 | 
				
			||||||
| 
						 | 
					@ -78,6 +81,8 @@ class HybridTimelineChannel extends Channel {
 | 
				
			||||||
			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
 | 
								if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
							if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | 
							// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,7 @@ class LocalTimelineChannel extends Channel {
 | 
				
			||||||
	public static requireCredential = false;
 | 
						public static requireCredential = false;
 | 
				
			||||||
	private withRenotes: boolean;
 | 
						private withRenotes: boolean;
 | 
				
			||||||
	private withReplies: boolean;
 | 
						private withReplies: boolean;
 | 
				
			||||||
 | 
						private withBots: boolean;
 | 
				
			||||||
	private withFiles: boolean;
 | 
						private withFiles: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
| 
						 | 
					@ -40,6 +41,7 @@ class LocalTimelineChannel extends Channel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.withRenotes = params.withRenotes ?? true;
 | 
							this.withRenotes = params.withRenotes ?? true;
 | 
				
			||||||
		this.withReplies = params.withReplies ?? false;
 | 
							this.withReplies = params.withReplies ?? false;
 | 
				
			||||||
 | 
							this.withBots = params.withBots ?? true;
 | 
				
			||||||
		this.withFiles = params.withFiles ?? false;
 | 
							this.withFiles = params.withFiles ?? false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Subscribe events
 | 
							// Subscribe events
 | 
				
			||||||
| 
						 | 
					@ -49,6 +51,7 @@ class LocalTimelineChannel extends Channel {
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	private async onNote(note: Packed<'Note'>) {
 | 
						private async onNote(note: Packed<'Note'>) {
 | 
				
			||||||
		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
 | 
							if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
 | 
				
			||||||
 | 
							if (!this.withBots && note.user.isBot) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note.user.host !== null) return;
 | 
							if (note.user.host !== null) return;
 | 
				
			||||||
		if (note.visibility !== 'public') return;
 | 
							if (note.visibility !== 'public') return;
 | 
				
			||||||
| 
						 | 
					@ -61,6 +64,8 @@ class LocalTimelineChannel extends Channel {
 | 
				
			||||||
			if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
 | 
								if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
							if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | 
							// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,11 +25,13 @@ const props = withDefaults(defineProps<{
 | 
				
			||||||
	sound?: boolean;
 | 
						sound?: boolean;
 | 
				
			||||||
	withRenotes?: boolean;
 | 
						withRenotes?: boolean;
 | 
				
			||||||
	withReplies?: boolean;
 | 
						withReplies?: boolean;
 | 
				
			||||||
 | 
						withBots?: boolean;
 | 
				
			||||||
	onlyFiles?: boolean;
 | 
						onlyFiles?: boolean;
 | 
				
			||||||
}>(), {
 | 
					}>(), {
 | 
				
			||||||
	withRenotes: true,
 | 
						withRenotes: true,
 | 
				
			||||||
	withReplies: false,
 | 
						withReplies: false,
 | 
				
			||||||
	onlyFiles: false,
 | 
						onlyFiles: false,
 | 
				
			||||||
 | 
						withBots: true,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits<{
 | 
					const emit = defineEmits<{
 | 
				
			||||||
| 
						 | 
					@ -93,11 +95,13 @@ if (props.src === 'antenna') {
 | 
				
			||||||
	query = {
 | 
						query = {
 | 
				
			||||||
		withRenotes: props.withRenotes,
 | 
							withRenotes: props.withRenotes,
 | 
				
			||||||
		withReplies: props.withReplies,
 | 
							withReplies: props.withReplies,
 | 
				
			||||||
 | 
							withBots: props.withBots,
 | 
				
			||||||
		withFiles: props.onlyFiles ? true : undefined,
 | 
							withFiles: props.onlyFiles ? true : undefined,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	connection = stream.useChannel('localTimeline', {
 | 
						connection = stream.useChannel('localTimeline', {
 | 
				
			||||||
		withRenotes: props.withRenotes,
 | 
							withRenotes: props.withRenotes,
 | 
				
			||||||
		withReplies: props.withReplies,
 | 
							withReplies: props.withReplies,
 | 
				
			||||||
 | 
							withBots: props.withBots,
 | 
				
			||||||
		withFiles: props.onlyFiles ? true : undefined,
 | 
							withFiles: props.onlyFiles ? true : undefined,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	connection.on('note', prepend);
 | 
						connection.on('note', prepend);
 | 
				
			||||||
| 
						 | 
					@ -106,11 +110,13 @@ if (props.src === 'antenna') {
 | 
				
			||||||
	query = {
 | 
						query = {
 | 
				
			||||||
		withRenotes: props.withRenotes,
 | 
							withRenotes: props.withRenotes,
 | 
				
			||||||
		withReplies: props.withReplies,
 | 
							withReplies: props.withReplies,
 | 
				
			||||||
 | 
							withBots: props.withBots,
 | 
				
			||||||
		withFiles: props.onlyFiles ? true : undefined,
 | 
							withFiles: props.onlyFiles ? true : undefined,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	connection = stream.useChannel('hybridTimeline', {
 | 
						connection = stream.useChannel('hybridTimeline', {
 | 
				
			||||||
		withRenotes: props.withRenotes,
 | 
							withRenotes: props.withRenotes,
 | 
				
			||||||
		withReplies: props.withReplies,
 | 
							withReplies: props.withReplies,
 | 
				
			||||||
 | 
							withBots: props.withBots,
 | 
				
			||||||
		withFiles: props.onlyFiles ? true : undefined,
 | 
							withFiles: props.onlyFiles ? true : undefined,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	connection.on('note', prepend);
 | 
						connection.on('note', prepend);
 | 
				
			||||||
| 
						 | 
					@ -118,10 +124,12 @@ if (props.src === 'antenna') {
 | 
				
			||||||
	endpoint = 'notes/global-timeline';
 | 
						endpoint = 'notes/global-timeline';
 | 
				
			||||||
	query = {
 | 
						query = {
 | 
				
			||||||
		withRenotes: props.withRenotes,
 | 
							withRenotes: props.withRenotes,
 | 
				
			||||||
 | 
							withBots: props.withBots,
 | 
				
			||||||
		withFiles: props.onlyFiles ? true : undefined,
 | 
							withFiles: props.onlyFiles ? true : undefined,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	connection = stream.useChannel('globalTimeline', {
 | 
						connection = stream.useChannel('globalTimeline', {
 | 
				
			||||||
		withRenotes: props.withRenotes,
 | 
							withRenotes: props.withRenotes,
 | 
				
			||||||
 | 
							withBots: props.withBots,
 | 
				
			||||||
		withFiles: props.onlyFiles ? true : undefined,
 | 
							withFiles: props.onlyFiles ? true : undefined,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	connection.on('note', prepend);
 | 
						connection.on('note', prepend);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,6 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<FormSection>
 | 
									<FormSection>
 | 
				
			||||||
					<div class="_gaps">
 | 
										<div class="_gaps">
 | 
				
			||||||
 | 
											<MkSwitch v-model="silenced" @update:modelValue="toggleSilence">{{ i18n.ts.silence }}</MkSwitch>
 | 
				
			||||||
						<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
 | 
											<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
 | 
				
			||||||
						<MkSwitch v-model="markedAsNSFW" @update:modelValue="toggleNSFW">{{ i18n.ts.markAsNSFW }}</MkSwitch>
 | 
											<MkSwitch v-model="markedAsNSFW" @update:modelValue="toggleNSFW">{{ i18n.ts.markAsNSFW }}</MkSwitch>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -306,6 +307,19 @@ async function toggleNSFW(v) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function toggleSilence(v) {
 | 
				
			||||||
 | 
						const confirm = await os.confirm({
 | 
				
			||||||
 | 
							type: 'warning',
 | 
				
			||||||
 | 
							text: v ? i18n.ts.silenceConfirm : i18n.ts.unsilenceConfirm,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						if (confirm.canceled) {
 | 
				
			||||||
 | 
							silenced = !v;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							await os.api(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: user.id });
 | 
				
			||||||
 | 
							await refreshUser();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function toggleSuspend(v) {
 | 
					async function toggleSuspend(v) {
 | 
				
			||||||
	const confirm = await os.confirm({
 | 
						const confirm = await os.confirm({
 | 
				
			||||||
		type: 'warning',
 | 
							type: 'warning',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,8 +18,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<div class="_panel" style="padding: 16px;">
 | 
									<div class="_panel" style="padding: 16px;">
 | 
				
			||||||
					<MkSwitch v-model="enableAchievements">
 | 
										<MkSwitch v-model="enableAchievements">
 | 
				
			||||||
						<template #label>Enable Achievements</template>
 | 
											<template #label>{{ i18n.ts.enableAchievements }}</template>
 | 
				
			||||||
						<template #caption>Turning this off will disable the achievement system</template>
 | 
											<template #caption>{{ i18n.ts.turnOffAchievements}}</template>
 | 
				
			||||||
 | 
										</MkSwitch>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									<div class="_panel" style="padding: 16px;">
 | 
				
			||||||
 | 
										<MkSwitch v-model="enableBotTrending">
 | 
				
			||||||
 | 
											<template #label>{{ i18n.ts.enableBotTrending }}</template>
 | 
				
			||||||
 | 
											<template #caption>{{ i18n.ts.turnOffBotTrending }}</template>
 | 
				
			||||||
					</MkSwitch>
 | 
										</MkSwitch>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,6 +68,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let enableServerMachineStats: boolean = $ref(false);
 | 
					let enableServerMachineStats: boolean = $ref(false);
 | 
				
			||||||
let enableAchievements: boolean = $ref(false);
 | 
					let enableAchievements: boolean = $ref(false);
 | 
				
			||||||
 | 
					let enableBotTrending: boolean = $ref(false);
 | 
				
			||||||
let enableIdenticonGeneration: boolean = $ref(false);
 | 
					let enableIdenticonGeneration: boolean = $ref(false);
 | 
				
			||||||
let enableChartsForRemoteUser: boolean = $ref(false);
 | 
					let enableChartsForRemoteUser: boolean = $ref(false);
 | 
				
			||||||
let enableChartsForFederatedInstances: boolean = $ref(false);
 | 
					let enableChartsForFederatedInstances: boolean = $ref(false);
 | 
				
			||||||
| 
						 | 
					@ -69,6 +77,7 @@ async function init() {
 | 
				
			||||||
	const meta = await os.api('admin/meta');
 | 
						const meta = await os.api('admin/meta');
 | 
				
			||||||
	enableServerMachineStats = meta.enableServerMachineStats;
 | 
						enableServerMachineStats = meta.enableServerMachineStats;
 | 
				
			||||||
	enableAchievements = meta.enableAchievements;
 | 
						enableAchievements = meta.enableAchievements;
 | 
				
			||||||
 | 
						enableBotTrending = meta.enableBotTrending;
 | 
				
			||||||
	enableIdenticonGeneration = meta.enableIdenticonGeneration;
 | 
						enableIdenticonGeneration = meta.enableIdenticonGeneration;
 | 
				
			||||||
	enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
 | 
						enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
 | 
				
			||||||
	enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
 | 
						enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
 | 
				
			||||||
| 
						 | 
					@ -78,6 +87,7 @@ function save() {
 | 
				
			||||||
	os.apiWithDialog('admin/update-meta', {
 | 
						os.apiWithDialog('admin/update-meta', {
 | 
				
			||||||
		enableServerMachineStats,
 | 
							enableServerMachineStats,
 | 
				
			||||||
		enableAchievements,
 | 
							enableAchievements,
 | 
				
			||||||
 | 
							enableBotTrending,
 | 
				
			||||||
		enableIdenticonGeneration,
 | 
							enableIdenticonGeneration,
 | 
				
			||||||
		enableChartsForRemoteUser,
 | 
							enableChartsForRemoteUser,
 | 
				
			||||||
		enableChartsForFederatedInstances,
 | 
							enableChartsForFederatedInstances,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -152,6 +152,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
 | 
									<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
 | 
				
			||||||
				<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
 | 
									<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
 | 
				
			||||||
				<MkSwitch v-model="clickToOpen">{{ i18n.ts.clickToOpen }}</MkSwitch>
 | 
									<MkSwitch v-model="clickToOpen">{{ i18n.ts.clickToOpen }}</MkSwitch>
 | 
				
			||||||
 | 
									<MkSwitch v-model="showBots">{{ i18n.ts.showBots }}</MkSwitch>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<MkSelect v-model="serverDisconnectedBehavior">
 | 
								<MkSelect v-model="serverDisconnectedBehavior">
 | 
				
			||||||
				<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
 | 
									<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
 | 
				
			||||||
| 
						 | 
					@ -227,6 +228,7 @@ const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showC
 | 
				
			||||||
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
 | 
					const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
 | 
				
			||||||
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
 | 
					const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
 | 
				
			||||||
const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
 | 
					const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
 | 
				
			||||||
 | 
					const showBots = computed(defaultStore.makeGetterSetter('tlWithBots'));
 | 
				
			||||||
const collapseFiles = computed(defaultStore.makeGetterSetter('collapseFiles'));
 | 
					const collapseFiles = computed(defaultStore.makeGetterSetter('collapseFiles'));
 | 
				
			||||||
const autoloadConversation = computed(defaultStore.makeGetterSetter('autoloadConversation'));
 | 
					const autoloadConversation = computed(defaultStore.makeGetterSetter('autoloadConversation'));
 | 
				
			||||||
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
 | 
					const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
					:withRenotes="withRenotes"
 | 
										:withRenotes="withRenotes"
 | 
				
			||||||
					:withReplies="withReplies"
 | 
										:withReplies="withReplies"
 | 
				
			||||||
					:onlyFiles="onlyFiles"
 | 
										:onlyFiles="onlyFiles"
 | 
				
			||||||
 | 
										:withBots="withBots"
 | 
				
			||||||
					:sound="true"
 | 
										:sound="true"
 | 
				
			||||||
					@queue="queueUpdated"
 | 
										@queue="queueUpdated"
 | 
				
			||||||
				/>
 | 
									/>
 | 
				
			||||||
| 
						 | 
					@ -63,6 +64,7 @@ let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global');
 | 
				
			||||||
const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
 | 
					const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
 | 
				
			||||||
const withRenotes = $ref(true);
 | 
					const withRenotes = $ref(true);
 | 
				
			||||||
const withReplies = $ref($i ? defaultStore.state.tlWithReplies : false);
 | 
					const withReplies = $ref($i ? defaultStore.state.tlWithReplies : false);
 | 
				
			||||||
 | 
					const withBots = $ref($i ? defaultStore.state.tlWithBots : true);
 | 
				
			||||||
const onlyFiles = $ref(false);
 | 
					const onlyFiles = $ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch($$(src), () => queue = 0);
 | 
					watch($$(src), () => queue = 0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,9 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
<MkSpacer :contentMax="narrow ? 800 : 1100" :style="background">
 | 
					<MkSpacer :contentMax="narrow ? 800 : 1100" :style="background">
 | 
				
			||||||
	<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
 | 
						<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
 | 
				
			||||||
		<div class="main _gaps">
 | 
							<div class="main _gaps">
 | 
				
			||||||
			<!-- TODO -->
 | 
								<MkInfo v-if="user.isSuspended" :warn="true">{{ i18n.ts.userSuspended }}</MkInfo>
 | 
				
			||||||
			<!-- <div class="punished" v-if="user.isSuspended"><i class="ph-warning ph-bold ph-lg" style="margin-right: 8px;"></i> {{ i18n.ts.userSuspended }}</div> -->
 | 
								<MkInfo v-if="user.isSilenced" :warn="true">{{ i18n.ts.userSilenced }}</MkInfo>
 | 
				
			||||||
			<!-- <div class="punished" v-if="user.isSilenced"><i class="ph-warning ph-bold ph-lg" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> -->
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div class="profile _gaps">
 | 
								<div class="profile _gaps">
 | 
				
			||||||
				<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
 | 
									<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -373,6 +373,10 @@ export const defaultStore = markRaw(new Storage('base', {
 | 
				
			||||||
		where: 'device',
 | 
							where: 'device',
 | 
				
			||||||
		default: false,
 | 
							default: false,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						tlWithBots: {
 | 
				
			||||||
 | 
							where: 'device',
 | 
				
			||||||
 | 
							default: true,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: 他のタブと永続化されたstateを同期
 | 
					// TODO: 他のタブと永続化されたstateを同期
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,8 @@ export type Endpoints = {
 | 
				
			||||||
	'admin/show-users': { req: TODO; res: TODO; };
 | 
						'admin/show-users': { req: TODO; res: TODO; };
 | 
				
			||||||
	'admin/silence-user': { req: TODO; res: TODO; };
 | 
						'admin/silence-user': { req: TODO; res: TODO; };
 | 
				
			||||||
	'admin/suspend-user': { req: TODO; res: TODO; };
 | 
						'admin/suspend-user': { req: TODO; res: TODO; };
 | 
				
			||||||
 | 
						'admin/nsfw-user': { req: TODO; res: TODO; };
 | 
				
			||||||
 | 
						'admin/unnsfw-user': { req: TODO; res: TODO; };
 | 
				
			||||||
	'admin/unsilence-user': { req: TODO; res: TODO; };
 | 
						'admin/unsilence-user': { req: TODO; res: TODO; };
 | 
				
			||||||
	'admin/unsuspend-user': { req: TODO; res: TODO; };
 | 
						'admin/unsuspend-user': { req: TODO; res: TODO; };
 | 
				
			||||||
	'admin/update-meta': { req: TODO; res: TODO; };
 | 
						'admin/update-meta': { req: TODO; res: TODO; };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -405,6 +405,7 @@ export type AdminInstanceMetadata = DetailedInstanceMetadata & {
 | 
				
			||||||
	app192IconUrl: string | null;
 | 
						app192IconUrl: string | null;
 | 
				
			||||||
	app512IconUrl: string | null;
 | 
						app512IconUrl: string | null;
 | 
				
			||||||
	manifestJsonOverride: string;
 | 
						manifestJsonOverride: string;
 | 
				
			||||||
 | 
						enableBotTrending: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ServerInfo = {
 | 
					export type ServerInfo = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue