package main import ( "database/sql" "fmt" "io" "io/ioutil" "log" "os" "path" "slices" "strings" _ "modernc.org/sqlite" ) const ( jellyDB = "jellyfin.db" libraryDB = "library.db" ) func main() { data := os.Args[1] fromU := os.Args[2] toU := os.Args[3] _ = toU workd, err := ioutil.TempDir(os.TempDir(), "jellyfin-user-clone.*") if err != nil { panic(err) } { if err := cp(path.Join(data, jellyDB), path.Join(workd, jellyDB)); err != nil { panic(err) } else if err := cp(path.Join(data, libraryDB), path.Join(workd, libraryDB)); err != nil { panic(err) } } jellyfinDB, err := sql.Open("sqlite", path.Join(workd, jellyDB)) if err != nil { panic(err) } defer jellyfinDB.Close() fromUUID, err := SelectOne[string](jellyfinDB, `SELECT Id FROM Users WHERE Username = $1`, fromU) if err != nil { panic(err) } fromID, err := SelectOne[int](jellyfinDB, `SELECT InternalId FROM Users WHERE Id = $1`, fromUUID) if err != nil { panic(err) } log.Println(fromU, fromUUID, fromID) if n, err := SelectOne[int](jellyfinDB, `SELECT COUNT(*) FROM Users WHERE Username = $1`, toU); err != nil { panic(err) } else if n == 1 { } else if err := Exec(jellyfinDB, `INSERT INTO Users () SELECT * FROM Users WHERE Id = $1`, fromUUID); err != nil { panic(err) } panic("not impl get toU, toUUID") tables, err := Tables(jellyfinDB) if err != nil { panic(err) } for _, table := range tables { columns, err := Columns(jellyfinDB, table) if err != nil { panic(err) } log.Println(table, columns) userColumns := slices.DeleteFunc(slices.Clone(columns), func(s string) bool { return !slices.Contains([]string{ "user", "userid", }, strings.ToLower(s)) }) if len(userColumns) == 0 { continue } for _, column := range userColumns { if n, err := SelectOne[int](jellyfinDB, fmt.Sprintf(`SELECT COUNT(*) FROM %q WHERE %q = $1`, table, column), fromID); err != nil { panic(err) } else if n > 0 { notThisColumn := strings.Join(slices.DeleteFunc(slices.Clone(columns), func(s string) bool { return s == column }), ", ") if err := Exec(jellyfinDB, fmt.Sprintf(`INSERT INTO %q (%q, %s) SELECT $2, %s FROM %q WHERE %q = $1`, table, column, notThisColumn, notThisColumn, table, column), fromID, toID); err != nil { panic(err) } } else if n, err := SelectOne[int](jellyfinDB, fmt.Sprintf(`SELECT COUNT(*) FROM %q WHERE %q = $1`, table, column), fromUUID); err != nil { panic(err) } else if n > 0 { panic("not impl: col is uuid") } } log.Println(table, userColumns) panic("not impl") } } func cp(from, to string) error { fromF, err := os.Open(from) if err != nil { return err } defer fromF.Close() toF, err := os.Create(to) if err != nil { return err } defer toF.Close() _, err = io.Copy(toF, fromF) return err } func Tables(db *sql.DB) ([]string, error) { return Select[string](jellyfinDB, `SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`) } func Columns(db *sql.DB, table string) ([]string, error) { return Select[string](jellyfinDB, `SELECT name FROM PRAGMA_TABLE_INFO($1)`, table) } func Exec(db *sql.DB, q string, args ...any) error { _, err := db.Exec(q, args...) return err } func SelectOne[T any](db *sql.DB, q string, args ...any) (T, error) { var some T results, err := Select[T](db, q, args...) if err != nil { return some, err } if len(results) != 1 { return some, fmt.Errorf("expected 1 result but got %d (%+v)", len(results), results) } return results[0], nil } func Select[T any](db *sql.DB, q string, args ...any) ([]T, error) { rows, err := db.Query(q, args...) if err != nil { return nil, err } defer rows.Close() results := []T{} for rows.Next() { var some T if err := rows.Scan(&some); err != nil { return nil, err } results = append(results, some) } return results, rows.Err() }