11import { Client , Collection , EmbedBuilder , Events , GatewayIntentBits } from "discord.js" ;
22import { AppBskyFeedDefs , AtpAgent } from "@atproto/api" ;
3+ import { XRPCError } from "@atproto/xrpc" ;
34import { Database } from "@db/sqlite" ;
45import { getBlueskyPostLink , processPostText } from "../bluesky/helpers.ts" ;
56import { CommandHandler } from "./commandHandler.ts" ;
@@ -91,6 +92,43 @@ export class BlueskyDiscordBot {
9192 return trackedAccounts ;
9293 }
9394
95+ /**
96+ * Completely removes a Bluesky account from the database.
97+ * Removes all subscriptions across all channels, clears post history,
98+ * and stops tracking the account.
99+ */
100+ forceRemoveAccount ( did : string ) : boolean {
101+ try {
102+ const transaction = this . db . transaction ( ( ) => {
103+ const subsStmt = this . db . prepare ( `
104+ DELETE FROM channel_subscriptions
105+ WHERE did = ?
106+ ` ) ;
107+ const deletedSubsCount = subsStmt . run ( did ) ;
108+
109+ const postsStmt = this . db . prepare ( `
110+ DELETE FROM processed_posts
111+ WHERE did = ?
112+ ` ) ;
113+ postsStmt . run ( did ) ;
114+
115+ const trackStmt = this . db . prepare ( `
116+ DELETE FROM tracked_accounts
117+ WHERE did = ?
118+ ` ) ;
119+ trackStmt . run ( did ) ;
120+
121+ console . log ( `Force removed account ${ did } . Removed ${ deletedSubsCount } subscriptions.` ) ;
122+ } ) ;
123+
124+ transaction ( ) ;
125+ return true ;
126+ } catch ( error ) {
127+ console . error ( `Error forcing removal of account ${ did } :` , error ) ;
128+ return false ;
129+ }
130+ }
131+
94132 private async pollBlueskyAccounts ( ) {
95133 const accounts = this . getTrackedAccounts ( ) ;
96134
@@ -115,6 +153,15 @@ export class BlueskyDiscordBot {
115153 console . log ( `Data feed for ${ account . did } less than 0.` ) ;
116154 }
117155 } catch ( error ) {
156+ if ( error instanceof XRPCError ) {
157+ if ( error . status === 400 && error . message ?. includes ( "Profile not found" ) ) {
158+ console . warn ( `Profile ${ account . did } not found. Purging from database...` ) ;
159+
160+ this . forceRemoveAccount ( account . did ) ;
161+
162+ return ;
163+ }
164+ }
118165 console . log ( `Error polling account ${ account . did } :` , error ) ;
119166 }
120167 }
@@ -136,7 +183,7 @@ export class BlueskyDiscordBot {
136183 ` ;
137184 if ( dbResp . length === 0 ) {
138185 this . db . sql `
139- INSERT INTO processed_posts (post_uri, did, post_type) VALUES
186+ INSERT INTO processed_posts (post_uri, did, post_type) VALUES
140187 (${ feedItem . post . uri } , ${ authorOrReposter } , ${ postType } )
141188 ` ;
142189 // TODO: add culling of oldest posts over 100
@@ -150,7 +197,7 @@ export class BlueskyDiscordBot {
150197 private updateLastChecked ( account : { did : string } , time : Date ) {
151198 const sqliteDate = time . toISOString ( ) ;
152199 this . db . sql `
153- UPDATE tracked_accounts SET last_checked_at = ${ sqliteDate } WHERE did = ${ account . did }
200+ UPDATE tracked_accounts SET last_checked_at = ${ sqliteDate } WHERE did = ${ account . did }
154201 ` ;
155202 }
156203
0 commit comments