From 463b9ac59def86dd1b9065cbe7382325c1e5824e Mon Sep 17 00:00:00 2001
From: Hazel K <acomputerdog@gmail.com>
Date: Wed, 9 Oct 2024 15:09:55 -0400
Subject: add filters for following feed

---
 .../frontend/src/components/SkUserRecentNotes.vue   | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

(limited to 'packages/frontend/src/components/SkUserRecentNotes.vue')

diff --git a/packages/frontend/src/components/SkUserRecentNotes.vue b/packages/frontend/src/components/SkUserRecentNotes.vue
index 1d124b4932..31580075ef 100644
--- a/packages/frontend/src/components/SkUserRecentNotes.vue
+++ b/packages/frontend/src/components/SkUserRecentNotes.vue
@@ -24,16 +24,13 @@ import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 import { Paging } from '@/components/MkPagination.vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 
-const props = withDefaults(defineProps<{
+const props = defineProps<{
 	userId: string;
-	withRenotes?: boolean;
-	withReplies?: boolean;
-	onlyFiles?: boolean;
-}>(), {
-	withRenotes: false,
-	withReplies: true,
-	onlyFiles: false,
-});
+	withNonPublic: boolean;
+	withQuotes: boolean;
+	withReplies: boolean;
+	onlyFiles: boolean;
+}>();
 
 const loadError: Ref<string | null> = ref(null);
 const user: Ref<Misskey.entities.UserDetailed | null> = ref(null);
@@ -43,9 +40,13 @@ const pagination: Paging<'users/notes'> = {
 	limit: 10,
 	params: computed(() => ({
 		userId: props.userId,
-		withRenotes: props.withRenotes,
+		withNonPublic: props.withNonPublic,
+		withRenotes: false,
+		withQuotes: props.withQuotes,
 		withReplies: props.withReplies,
+		withRepliesToSelf: props.withReplies,
 		withFiles: props.onlyFiles,
+		allowPartial: true,
 	})),
 };
 
-- 
cgit v1.2.3-freya


From 9b1bae653d96fd276bbebde7c9dab3adf6236a77 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Sat, 12 Oct 2024 14:56:04 -0400
Subject: add "show bots" toggle to following feed

---
 .../src/server/api/endpoints/notes/following.ts     |  6 ++++++
 .../backend/src/server/api/endpoints/users/notes.ts |  9 +++++++++
 .../frontend/src/components/SkUserRecentNotes.vue   |  1 +
 packages/frontend/src/pages/following-feed.vue      | 21 ++++++++++++++-------
 packages/frontend/src/store.ts                      |  1 +
 packages/misskey-js/src/autogen/types.ts            |  4 ++++
 6 files changed, 35 insertions(+), 7 deletions(-)

(limited to 'packages/frontend/src/components/SkUserRecentNotes.vue')

diff --git a/packages/backend/src/server/api/endpoints/notes/following.ts b/packages/backend/src/server/api/endpoints/notes/following.ts
index a75a928009..83e8f404e9 100644
--- a/packages/backend/src/server/api/endpoints/notes/following.ts
+++ b/packages/backend/src/server/api/endpoints/notes/following.ts
@@ -37,6 +37,7 @@ export const paramDef = {
 		includeNonPublic: { type: 'boolean', default: false },
 		includeReplies: { type: 'boolean', default: false },
 		includeQuotes: { type: 'boolean', default: false },
+		includeBots: { type: 'boolean', default: true },
 
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
@@ -97,6 +98,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				query.andWhere('latest.is_quote = false');
 			}
 
+			// Match selected user types.
+			if (!ps.includeBots) {
+				query.andWhere('"user"."isBot" = false');
+			}
+
 			// Respect blocks and mutes
 			this.queryService.generateBlockedUserQuery(query, me);
 			this.queryService.generateMutedUserQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index 884760a88f..efea15ca80 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -55,6 +55,7 @@ export const paramDef = {
 		withRepliesToSelf: { type: 'boolean', default: true },
 		withQuotes: { type: 'boolean', default: true },
 		withRenotes: { type: 'boolean', default: true },
+		withBots: { type: 'boolean', default: true },
 		withNonPublic: { type: 'boolean', default: true },
 		withChannelNotes: { type: 'boolean', default: false },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
@@ -108,6 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					withFiles: ps.withFiles,
 					withRenotes: ps.withRenotes,
 					withQuotes: ps.withQuotes,
+					withBots: ps.withBots,
 					withNonPublic: ps.withNonPublic,
 					withRepliesToOthers: ps.withReplies,
 					withRepliesToSelf: ps.withRepliesToSelf,
@@ -135,6 +137,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				excludeReplies: ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies
 				excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files
 				excludePureRenotes: !ps.withRenotes,
+				excludeBots: !ps.withBots,
 				noteFilter: note => {
 					if (note.channel?.isSensitive && !isSelf) return false;
 					if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false;
@@ -156,6 +159,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					withFiles: ps.withFiles,
 					withRenotes: ps.withRenotes,
 					withQuotes: ps.withQuotes,
+					withBots: ps.withBots,
 					withNonPublic: ps.withNonPublic,
 					withRepliesToOthers: ps.withReplies,
 					withRepliesToSelf: ps.withRepliesToSelf,
@@ -175,6 +179,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		withFiles: boolean,
 		withRenotes: boolean,
 		withQuotes: boolean,
+		withBots: boolean,
 		withNonPublic: boolean,
 		withRepliesToOthers: boolean,
 		withRepliesToSelf: boolean,
@@ -246,6 +251,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			query.andWhere('note.visibility = \'public\'');
 		}
 
+		if (!ps.withBots) {
+			query.andWhere('"user"."isBot" = false');
+		}
+
 		return await query.limit(ps.limit).getMany();
 	}
 }
diff --git a/packages/frontend/src/components/SkUserRecentNotes.vue b/packages/frontend/src/components/SkUserRecentNotes.vue
index 31580075ef..2cdb4b6586 100644
--- a/packages/frontend/src/components/SkUserRecentNotes.vue
+++ b/packages/frontend/src/components/SkUserRecentNotes.vue
@@ -29,6 +29,7 @@ const props = defineProps<{
 	withNonPublic: boolean;
 	withQuotes: boolean;
 	withReplies: boolean;
+	withBots: boolean;
 	onlyFiles: boolean;
 }>();
 
diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue
index 1b3f303dfe..7b90b563e6 100644
--- a/packages/frontend/src/pages/following-feed.vue
+++ b/packages/frontend/src/pages/following-feed.vue
@@ -30,18 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 	<div v-if="isWideViewport" ref="userScroll" :class="$style.user">
 		<MkHorizontalSwipe v-if="selectedUserId" v-model:tab="currentTab" :tabs="headerTabs">
-			<SkUserRecentNotes ref="userRecentNotes" :userId="selectedUserId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withReplies="withReplies" :onlyFiles="onlyFiles"/>
+			<SkUserRecentNotes ref="userRecentNotes" :userId="selectedUserId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withBots="withBots" :withReplies="withReplies" :onlyFiles="onlyFiles"/>
 		</MkHorizontalSwipe>
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-export type FollowingFeedTab = typeof followingTab | typeof mutualsTab;
-export const followingTab = 'following' as const;
-export const mutualsTab = 'mutuals' as const;
-</script>
-
 <script lang="ts" setup>
 import { computed, Ref, ref, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
@@ -74,6 +68,10 @@ const withQuotes = computed({
 	get: () => defaultStore.reactiveState.followingFeed.value.withQuotes,
 	set: value => saveFollowingFilter('withQuotes', value),
 });
+const withBots = computed({
+	get: () => defaultStore.reactiveState.followingFeed.value.withBots,
+	set: value => saveFollowingFilter('withBots', value),
+});
 const withReplies = computed({
 	get: () => defaultStore.reactiveState.followingFeed.value.withReplies,
 	set: value => saveFollowingFilter('withReplies', value),
@@ -95,10 +93,13 @@ function saveFollowingFilter(key: keyof typeof defaultStore.state.followingFeed,
 
 const router = useRouter();
 
+const followingTab = 'following' as const;
+const mutualsTab = 'mutuals' as const;
 const currentTab = computed({
 	get: () => onlyMutuals.value ? mutualsTab : followingTab,
 	set: value => onlyMutuals.value = (value === mutualsTab),
 });
+
 const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>();
 const userScroll = shallowRef<HTMLElement>();
 const noteScroll = shallowRef<HTMLElement>();
@@ -188,6 +189,7 @@ const latestNotesPagination: Paging<'notes/following'> = {
 		includeNonPublic: withNonPublic.value,
 		includeReplies: withReplies.value,
 		includeQuotes: withQuotes.value,
+		includeBots: withBots.value,
 	})),
 };
 
@@ -212,6 +214,11 @@ const headerActions: PageHeaderItem[] = [
 					text: i18n.ts.showQuotes,
 					ref: withQuotes,
 				},
+				{
+					type: 'switch',
+					text: i18n.ts.showBots,
+					ref: withBots,
+				},
 				{
 					type: 'switch',
 					text: i18n.ts.showReplies,
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 5f78330147..9f4003f88e 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -244,6 +244,7 @@ export const defaultStore = markRaw(new Storage('base', {
 		default: {
 			withNonPublic: false,
 			withQuotes: false,
+			withBots: true,
 			withReplies: false,
 			onlyFiles: false,
 			onlyMutuals: false,
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 98f09c047e..f27e9a9f4c 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -22304,6 +22304,8 @@ export type operations = {
           includeReplies?: boolean;
           /** @default false */
           includeQuotes?: boolean;
+          /** @default true */
+          includeBots?: boolean;
           /** @default 10 */
           limit?: number;
           /** Format: misskey:id */
@@ -27242,6 +27244,8 @@ export type operations = {
           /** @default true */
           withRenotes?: boolean;
           /** @default true */
+          withBots?: boolean;
+          /** @default true */
           withNonPublic?: boolean;
           /** @default false */
           withChannelNotes?: boolean;
-- 
cgit v1.2.3-freya

