feat: cli migrate to cobra with subcommands (#550)

* feat: migrate CLI to cobra with dump/list/version subcommands (#546)
* fix: remove residual duckduckgo references and add README/LICENSE to release archives
* fix: address PR review feedback from Copilot
This commit is contained in:
Roger
2026-04-05 14:25:51 +08:00
committed by GitHub
parent 068b82178f
commit 4af2ded428
15 changed files with 418 additions and 112 deletions
+29 -4
View File
@@ -57,6 +57,7 @@ func NewBrowsers(cfg types.BrowserConfig) ([]*Browser, error) {
}
func (b *Browser) BrowserName() string { return b.cfg.Name }
func (b *Browser) ProfileDir() string { return b.profileDir }
func (b *Browser) ProfileName() string {
if b.profileDir == "" {
return ""
@@ -173,8 +174,9 @@ func (b *Browser) extractCategory(data *types.BrowserData, cat types.Category, m
}
}
// discoverProfiles lists subdirectories of userDataDir that contain at least
// one known data source. Each such directory is a browser profile.
// discoverProfiles lists subdirectories of userDataDir that are valid
// Chromium profile directories. A directory is considered a profile if it
// contains a "Preferences" file, which Chromium creates for every profile.
func discoverProfiles(userDataDir string, sources map[types.Category][]sourcePath) []string {
entries, err := os.ReadDir(userDataDir)
if err != nil {
@@ -188,18 +190,41 @@ func discoverProfiles(userDataDir string, sources map[types.Category][]sourcePat
continue
}
dir := filepath.Join(userDataDir, e.Name())
if hasAnySource(sources, dir) {
if isProfileDir(dir) {
profiles = append(profiles, dir)
}
}
// Flat layout fallback (older Opera): data files directly in userDataDir
// Flat layout fallback (older Opera): data files directly in userDataDir.
// Opera stores data alongside Local State in userDataDir itself, so check
// for any known source file instead of Preferences.
if len(profiles) == 0 && hasAnySource(sources, userDataDir) {
profiles = append(profiles, userDataDir)
}
return profiles
}
// profileMarkers are filenames that identify a directory as a Chromium profile.
// Chromium creates a per-profile preferences file on first use; checking for
// its existence filters out non-profile subdirectories (Crashpad, ShaderCache, etc.).
//
// - "Preferences" — standard Chromium and all major forks (Chrome, Edge, Brave, …)
// - "Preferences_02" — Tencent-based browsers (QQ Browser, Sogou Explorer)
var profileMarkers = []string{
"Preferences",
"Preferences_02",
}
// isProfileDir reports whether dir is a valid Chromium profile directory.
func isProfileDir(dir string) bool {
for _, name := range profileMarkers {
if _, err := os.Stat(filepath.Join(dir, name)); err == nil {
return true
}
}
return false
}
// hasAnySource checks if dir contains at least one source file or directory.
func hasAnySource(sources map[types.Category][]sourcePath, dir string) bool {
for _, candidates := range sources {
+7
View File
@@ -45,6 +45,7 @@ func buildFixtures() {
fixture.chrome = filepath.Join(fixture.root, "chrome")
mkFile(fixture.chrome, "Local State")
for _, p := range []string{"Default", "Profile 1", "Profile 3"} {
mkFile(fixture.chrome, p, "Preferences")
mkFile(fixture.chrome, p, "Login Data")
mkFile(fixture.chrome, p, "History")
mkFile(fixture.chrome, p, "Bookmarks")
@@ -60,6 +61,7 @@ func buildFixtures() {
fixture.opera = filepath.Join(fixture.root, "opera")
mkFile(fixture.opera, "Local State")
mkFile(fixture.opera, "Default", "Preferences")
mkFile(fixture.opera, "Default", "Login Data")
mkFile(fixture.opera, "Default", "History")
mkFile(fixture.opera, "Default", "Bookmarks")
@@ -73,6 +75,7 @@ func buildFixtures() {
fixture.yandex = filepath.Join(fixture.root, "yandex")
mkFile(fixture.yandex, "Local State")
mkFile(fixture.yandex, "Default", "Preferences")
mkFile(fixture.yandex, "Default", "Ya Passman Data")
mkFile(fixture.yandex, "Default", "Ya Credit Cards")
mkFile(fixture.yandex, "Default", "History")
@@ -80,14 +83,17 @@ func buildFixtures() {
mkFile(fixture.yandex, "Default", "Bookmarks")
fixture.oldCookies = filepath.Join(fixture.root, "old-cookies")
mkFile(fixture.oldCookies, "Default", "Preferences")
mkFile(fixture.oldCookies, "Default", "History")
mkFile(fixture.oldCookies, "Default", "Cookies")
fixture.bothCookies = filepath.Join(fixture.root, "both-cookies")
mkFile(fixture.bothCookies, "Default", "Preferences")
mkFile(fixture.bothCookies, "Default", "Cookies")
mkFile(fixture.bothCookies, "Default", "Network", "Cookies")
fixture.leveldb = filepath.Join(fixture.root, "leveldb")
mkFile(fixture.leveldb, "Default", "Preferences")
mkFile(fixture.leveldb, "Default", "History")
mkDir(fixture.leveldb, "Default", "Local Storage", "leveldb")
mkFile(fixture.leveldb, "Default", "Local Storage", "leveldb", "000001.ldb")
@@ -95,6 +101,7 @@ func buildFixtures() {
mkFile(fixture.leveldb, "Default", "Session Storage", "000001.ldb")
fixture.leveldbOnly = filepath.Join(fixture.root, "leveldb-only")
mkFile(fixture.leveldbOnly, "Default", "Preferences")
mkDir(fixture.leveldbOnly, "Default", "Local Storage", "leveldb")
mkDir(fixture.leveldbOnly, "Default", "Session Storage")