···11+import { Context, Next } from 'hono'
22+import { TakedownService } from '../services/takedown.js'
33+44+/**
55+ * Middleware that filters out taken-down content from responses
66+ * This is meant to be applied to routes that return content
77+ * that might have been taken down by admins
88+ */
99+export const takedownFilterMiddleware = async (c: Context, next: Next) => {
1010+ // Call the next middleware/route handler first
1111+ await next()
1212+1313+ // Skip filtering if not a JSON response
1414+ const contentType = c.res.headers.get('Content-Type')
1515+ if (!contentType || !contentType.includes('application/json')) {
1616+ return
1717+ }
1818+1919+ try {
2020+ // Get the takedown service from context
2121+ const takedownService = c.get('takedownService') as TakedownService
2222+2323+ // Get the response body
2424+ const body = await c.res.json()
2525+2626+ // Process different response formats
2727+ if (body.posts && Array.isArray(body.posts)) {
2828+ // For post feeds
2929+ const filteredPosts = await filterTakenDownItems(body.posts, takedownService, 'uri')
3030+ body.posts = filteredPosts
3131+ } else if (body.feed && Array.isArray(body.feed)) {
3232+ // For general feeds
3333+ const filteredFeed = await filterTakenDownItems(body.feed, takedownService, 'post.uri')
3434+ body.feed = filteredFeed
3535+ } else if (body.thread && body.thread.post) {
3636+ // For thread views
3737+ const isThreadTakenDown = await takedownService.isTakenDown(body.thread.post.uri)
3838+ if (isThreadTakenDown) {
3939+ // If the main post is taken down, return empty thread
4040+ body.thread = null
4141+ } else if (body.thread.replies) {
4242+ // Filter replies if they exist
4343+ body.thread.replies = await filterReplies(body.thread.replies, takedownService)
4444+ }
4545+ }
4646+4747+ // If there are user profiles in the response, filter out taken down repositories
4848+ if (body.profiles && Array.isArray(body.profiles)) {
4949+ const filteredProfiles = await filterTakenDownRepos(body.profiles, takedownService)
5050+ body.profiles = filteredProfiles
5151+ } else if (body.profile) {
5252+ // For single profile view
5353+ if (body.profile.did) {
5454+ const isRepoTakenDown = await takedownService.isRepoTakenDown(body.profile.did)
5555+ if (isRepoTakenDown) {
5656+ body.profile = null
5757+ }
5858+ }
5959+ }
6060+6161+ // Set the filtered response
6262+ c.res = new Response(JSON.stringify(body), {
6363+ status: c.res.status,
6464+ headers: c.res.headers,
6565+ })
6666+ } catch (error) {
6767+ // In case of error, just continue with the original response
6868+ console.error('Error in takedown filter middleware:', error)
6969+ }
7070+}
7171+7272+// Helper function to filter out taken down items
7373+async function filterTakenDownItems(
7474+ items: any[],
7575+ takedownService: TakedownService,
7676+ uriPath: string
7777+): Promise<any[]> {
7878+ if (!items || !Array.isArray(items)) return items
7979+8080+ const filteredItems: any[] = []
8181+8282+ for (const item of items) {
8383+ // Get the URI based on the specified path (handles nested objects)
8484+ const uri = uriPath.split('.').reduce((obj, key) => obj && obj[key], item)
8585+8686+ if (uri) {
8787+ const isTakenDown = await takedownService.isTakenDown(uri)
8888+8989+ // Check if author's repo is taken down
9090+ let isAuthorTakenDown = false
9191+ if (item.author?.did || (item.post?.author?.did)) {
9292+ const authorDid = item.author?.did || item.post?.author?.did
9393+ isAuthorTakenDown = await takedownService.isRepoTakenDown(authorDid)
9494+ }
9595+9696+ // Keep the item only if neither the content nor the author is taken down
9797+ if (!isTakenDown && !isAuthorTakenDown) {
9898+ // Filter out taken down images if the item has embeds
9999+ if (item.embed?.images && Array.isArray(item.embed.images)) {
100100+ item.embed.images = await filterTakenDownBlobs(item.embed.images, takedownService)
101101+ }
102102+103103+ filteredItems.push(item)
104104+ }
105105+ } else {
106106+ // If URI is not found, keep the item
107107+ filteredItems.push(item)
108108+ }
109109+ }
110110+111111+ return filteredItems
112112+}
113113+114114+// Helper function to filter out taken down repositories
115115+async function filterTakenDownRepos(
116116+ profiles: any[],
117117+ takedownService: TakedownService
118118+): Promise<any[]> {
119119+ if (!profiles || !Array.isArray(profiles)) return profiles
120120+121121+ const filteredProfiles: any[] = []
122122+123123+ for (const profile of profiles) {
124124+ if (profile.did) {
125125+ const isRepoTakenDown = await takedownService.isRepoTakenDown(profile.did)
126126+ if (!isRepoTakenDown) {
127127+ filteredProfiles.push(profile)
128128+ }
129129+ } else {
130130+ // If no DID, keep the profile
131131+ filteredProfiles.push(profile)
132132+ }
133133+ }
134134+135135+ return filteredProfiles
136136+}
137137+138138+// Helper function to filter out taken down blobs/images
139139+async function filterTakenDownBlobs(
140140+ images: any[],
141141+ takedownService: TakedownService
142142+): Promise<any[]> {
143143+ if (!images || !Array.isArray(images)) return images
144144+145145+ const filteredImages: any[] = []
146146+147147+ for (const image of images) {
148148+ // Check if the image is taken down based on blob CID
149149+ if (image.cid && image.did) {
150150+ const isBlobTakenDown = await takedownService.isBlobTakenDown(image.did, image.cid)
151151+ if (!isBlobTakenDown) {
152152+ filteredImages.push(image)
153153+ }
154154+ } else {
155155+ // If no CID or DID, keep the image
156156+ filteredImages.push(image)
157157+ }
158158+ }
159159+160160+ return filteredImages
161161+}
162162+163163+// Helper function to recursively filter replies in a thread
164164+async function filterReplies(
165165+ replies: any[],
166166+ takedownService: TakedownService
167167+): Promise<any[]> {
168168+ if (!replies || !Array.isArray(replies)) return replies
169169+170170+ const filteredReplies: any[] = []
171171+172172+ for (const reply of replies) {
173173+ if (reply.post && reply.post.uri) {
174174+ const isTakenDown = await takedownService.isTakenDown(reply.post.uri)
175175+176176+ // Check if author's repo is taken down
177177+ let isAuthorTakenDown = false
178178+ if (reply.post.author?.did) {
179179+ isAuthorTakenDown = await takedownService.isRepoTakenDown(reply.post.author.did)
180180+ }
181181+182182+ if (!isTakenDown && !isAuthorTakenDown) {
183183+ // If this reply has nested replies, filter those too
184184+ if (reply.replies && Array.isArray(reply.replies)) {
185185+ reply.replies = await filterReplies(reply.replies, takedownService)
186186+ }
187187+188188+ // Filter out taken down images in the post
189189+ if (reply.post.embed?.images && Array.isArray(reply.post.embed.images)) {
190190+ reply.post.embed.images = await filterTakenDownBlobs(reply.post.embed.images, takedownService)
191191+ }
192192+193193+ filteredReplies.push(reply)
194194+ }
195195+ } else {
196196+ // If no post or URI, keep the reply
197197+ filteredReplies.push(reply)
198198+ }
199199+ }
200200+201201+ return filteredReplies
202202+}