@@ -215,6 +215,161 @@ function getUserDataDir() {
215215 return resolveDataDir ( ) ;
216216}
217217
218+ function sanitizeAccountName ( account ) {
219+ const name = String ( account || "" ) . trim ( ) ;
220+ if ( ! name ) throw new Error ( "缺少账号参数" ) ;
221+ if ( name === "." || name === ".." ) throw new Error ( "账号参数非法" ) ;
222+ if ( name . includes ( "/" ) || name . includes ( "\\" ) ) throw new Error ( "账号参数非法" ) ;
223+ return name ;
224+ }
225+
226+ function listDecryptedAccountsOnDisk ( databasesDir ) {
227+ try {
228+ if ( ! fs . existsSync ( databasesDir ) ) return [ ] ;
229+ } catch {
230+ return [ ] ;
231+ }
232+
233+ let entries = [ ] ;
234+ try {
235+ entries = fs . readdirSync ( databasesDir , { withFileTypes : true } ) ;
236+ } catch {
237+ return [ ] ;
238+ }
239+
240+ const accounts = [ ] ;
241+ for ( const entry of entries ) {
242+ try {
243+ if ( ! entry || ! entry . isDirectory ( ) ) continue ;
244+ const accountDir = path . join ( databasesDir , entry . name ) ;
245+ const hasSession = fs . existsSync ( path . join ( accountDir , "session.db" ) ) ;
246+ const hasContact = fs . existsSync ( path . join ( accountDir , "contact.db" ) ) ;
247+ if ( hasSession && hasContact ) accounts . push ( String ( entry . name || "" ) ) ;
248+ } catch { }
249+ }
250+ accounts . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
251+ return accounts ;
252+ }
253+
254+ function resolveAccountDirInOutput ( account ) {
255+ const dataDir = resolveDataDir ( ) ;
256+ if ( ! dataDir ) throw new Error ( "无法定位数据目录" ) ;
257+
258+ const outputDir = path . join ( dataDir , "output" ) ;
259+ const databasesDir = path . join ( outputDir , "databases" ) ;
260+ const accountName = sanitizeAccountName ( account ) ;
261+
262+ const base = path . resolve ( databasesDir ) ;
263+ const accountDir = path . resolve ( path . join ( databasesDir , accountName ) ) ;
264+ if ( accountDir !== base && ! accountDir . startsWith ( base + path . sep ) ) {
265+ throw new Error ( "账号路径非法" ) ;
266+ }
267+
268+ return {
269+ dataDir,
270+ outputDir,
271+ databasesDir,
272+ accountName,
273+ accountDir,
274+ } ;
275+ }
276+
277+ function getAccountInfoFromDisk ( account ) {
278+ const { accountName, accountDir } = resolveAccountDirInOutput ( account ) ;
279+ if ( ! fs . existsSync ( accountDir ) || ! fs . statSync ( accountDir ) . isDirectory ( ) ) {
280+ throw new Error ( "账号数据不存在" ) ;
281+ }
282+
283+ let entries = [ ] ;
284+ try {
285+ entries = fs . readdirSync ( accountDir , { withFileTypes : true } ) ;
286+ } catch { }
287+ const dbFiles = entries
288+ . filter ( ( e ) => ! ! e && e . isFile ( ) && String ( e . name || "" ) . toLowerCase ( ) . endsWith ( ".db" ) )
289+ . map ( ( e ) => String ( e . name || "" ) )
290+ . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
291+
292+ let sessionUpdatedAt = 0 ;
293+ try {
294+ const st = fs . statSync ( path . join ( accountDir , "session.db" ) ) ;
295+ sessionUpdatedAt = Math . floor ( Number ( st ?. mtimeMs || 0 ) / 1000 ) ;
296+ } catch { }
297+
298+ return {
299+ status : "success" ,
300+ account : accountName ,
301+ path : accountDir ,
302+ database_count : dbFiles . length ,
303+ databases : dbFiles ,
304+ session_updated_at : sessionUpdatedAt ,
305+ } ;
306+ }
307+
308+ function removeAccountFromKeyStore ( dataDir , accountName ) {
309+ const keyStorePath = path . join ( dataDir , "output" , "account_keys.json" ) ;
310+ try {
311+ if ( ! fs . existsSync ( keyStorePath ) ) return false ;
312+ const raw = fs . readFileSync ( keyStorePath , { encoding : "utf8" } ) ;
313+ const parsed = JSON . parse ( raw || "{}" ) ;
314+ if ( ! parsed || typeof parsed !== "object" || Array . isArray ( parsed ) ) return false ;
315+ if ( ! Object . prototype . hasOwnProperty . call ( parsed , accountName ) ) return false ;
316+ delete parsed [ accountName ] ;
317+ fs . writeFileSync ( keyStorePath , JSON . stringify ( parsed , null , 2 ) , { encoding : "utf8" } ) ;
318+ return true ;
319+ } catch {
320+ return false ;
321+ }
322+ }
323+
324+ async function deleteAccountDataFromDisk ( account ) {
325+ const { dataDir, outputDir, databasesDir, accountName, accountDir } = resolveAccountDirInOutput ( account ) ;
326+ if ( ! fs . existsSync ( accountDir ) || ! fs . statSync ( accountDir ) . isDirectory ( ) ) {
327+ throw new Error ( "账号数据不存在" ) ;
328+ }
329+
330+ const wasBackendRunning = ! ! backendProc ;
331+ let restartError = null ;
332+ let result = null ;
333+
334+ if ( wasBackendRunning ) {
335+ await stopBackendAndWait ( { timeoutMs : 10_000 } ) ;
336+ }
337+
338+ try {
339+ const exportsDir = path . join ( outputDir , "exports" , accountName ) ;
340+ try {
341+ fs . rmSync ( exportsDir , { recursive : true , force : true } ) ;
342+ } catch { }
343+
344+ fs . rmSync ( accountDir , { recursive : true , force : true } ) ;
345+ const removedKeyCache = removeAccountFromKeyStore ( dataDir , accountName ) ;
346+ const accounts = listDecryptedAccountsOnDisk ( databasesDir ) ;
347+ result = {
348+ status : "success" ,
349+ deleted_account : accountName ,
350+ accounts,
351+ default_account : accounts . length ? accounts [ 0 ] : null ,
352+ removed_key_cache : removedKeyCache ,
353+ } ;
354+ } finally {
355+ if ( wasBackendRunning ) {
356+ try {
357+ startBackend ( ) ;
358+ await waitForBackend ( { timeoutMs : 30_000 } ) ;
359+ } catch ( err ) {
360+ restartError = err ;
361+ logMain ( `[main] failed to restart backend after deleteAccountData: ${ err ?. message || err } ` ) ;
362+ }
363+ }
364+ }
365+
366+ if ( restartError ) {
367+ throw new Error ( `删除完成,但后端重启失败:${ restartError ?. message || restartError } ` ) ;
368+ }
369+ if ( ! result ) throw new Error ( "删除账号数据失败" ) ;
370+ return result ;
371+ }
372+
218373function getExeDir ( ) {
219374 try {
220375 return path . dirname ( process . execPath ) ;
@@ -1384,6 +1539,22 @@ function registerWindowIpc() {
13841539 }
13851540 } ) ;
13861541
1542+ ipcMain . handle ( "app:getAccountInfo" , async ( _event , account ) => {
1543+ try {
1544+ return getAccountInfoFromDisk ( account ) ;
1545+ } catch ( e ) {
1546+ throw new Error ( e ?. message || String ( e ) ) ;
1547+ }
1548+ } ) ;
1549+
1550+ ipcMain . handle ( "app:deleteAccountData" , async ( _event , account ) => {
1551+ try {
1552+ return await deleteAccountDataFromDisk ( account ) ;
1553+ } catch ( e ) {
1554+ throw new Error ( e ?. message || String ( e ) ) ;
1555+ }
1556+ } ) ;
1557+
13871558 ipcMain . handle ( "app:checkForUpdates" , async ( ) => {
13881559 return await checkForUpdatesInternal ( ) ;
13891560 } ) ;
0 commit comments