package cmd import ( "fmt" "net" "net/http" fp "path/filepath" "strconv" "strings" "time" "github.com/go-shiori/shiori/internal/database" "github.com/go-shiori/warc" "github.com/julienschmidt/httprouter" "github.com/spf13/cobra" ) func openCmd() *cobra.Command { cmd := &cobra.Command{ Use: "open [indices]", Short: "Open the saved bookmarks", Long: "Open bookmarks in browser. " + "Accepts space-separated list of indices (e.g. 5 6 23 4 110 45), " + "hyphenated range (e.g. 100-200) or both (e.g. 1-3 7 9). " + "If no arguments, ALL bookmarks will be opened.", Run: openHandler, } cmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and open ALL bookmarks") cmd.Flags().BoolP("archive", "a", false, "Open the bookmark's archived content") cmd.Flags().IntP("archive-port", "p", 0, "Port number that used to serve archive") cmd.Flags().BoolP("text-cache", "t", false, "Open the bookmark's text cache in terminal") return cmd } func openHandler(cmd *cobra.Command, args []string) { // Parse flags skipConfirm, _ := cmd.Flags().GetBool("yes") archiveMode, _ := cmd.Flags().GetBool("archive") archivePort, _ := cmd.Flags().GetInt("archive-port") textCacheMode, _ := cmd.Flags().GetBool("text-cache") // Convert args to ids ids, err := parseStrIndices(args) if err != nil { cError.Println(err) return } // If in archive mode, only one bookmark allowed if len(ids) > 1 && archiveMode { cError.Println("In archive mode, only one bookmark allowed") return } // If no arguments (i.e all bookmarks will be opened), // confirm to user if len(args) == 0 && !skipConfirm { confirmOpen := "" fmt.Print("Open ALL bookmarks? (y/N): ") fmt.Scanln(&confirmOpen) if confirmOpen != "y" { return } } // Read bookmarks from database getOptions := database.GetBookmarksOptions{ IDs: ids, WithContent: true, } bookmarks, err := db.GetBookmarks(getOptions) if err != nil { cError.Printf("Failed to get bookmarks: %v\n", err) return } if len(bookmarks) == 0 { if len(ids) > 0 { cError.Println("No matching index found") } else { cError.Println("No bookmarks saved yet") } return } // If not text cache mode nor archive mode, open bookmarks in browser if !textCacheMode && !archiveMode { for _, book := range bookmarks { err = openBrowser(book.URL) if err != nil { cError.Printf("Failed to open %s: %v\n", book.URL, err) } } return } // Show bookmarks content in terminal if textCacheMode { termWidth := getTerminalWidth() for _, book := range bookmarks { cIndex.Printf("%d. ", book.ID) cTitle.Println(book.Title) fmt.Println() if book.Content == "" { cError.Println("This bookmark doesn't have any cached content") } else { book.Content = strings.Join(strings.Fields(book.Content), " ") fmt.Println(book.Content) } fmt.Println() cSymbol.Println(strings.Repeat("=", termWidth)) fmt.Println() } return } // Open archive id := strconv.Itoa(bookmarks[0].ID) archivePath := fp.Join(dataDir, "archive", id) archive, err := warc.Open(archivePath) if err != nil { cError.Printf("Failed to open archive: %v\n", err) return } defer archive.Close() // Create simple server router := httprouter.New() router.GET("/*filename", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { filename := ps.ByName("filename") resourceName := fp.Base(filename) if resourceName == "/" { resourceName = "" } content, contentType, err := archive.Read(resourceName) if err != nil { panic(err) } w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Encoding", "gzip") if _, err = w.Write(content); err != nil { panic(err) } }) router.PanicHandler = func(w http.ResponseWriter, r *http.Request, arg interface{}) { http.Error(w, fmt.Sprint(arg), 500) } // Choose random port listenerAddr := fmt.Sprintf(":%d", archivePort) listener, err := net.Listen("tcp", listenerAddr) if err != nil { cError.Printf("Failed to serve archive: %v\n", err) return } portNumber := listener.Addr().(*net.TCPAddr).Port localhostAddr := fmt.Sprintf("http://localhost:%d", portNumber) cInfo.Printf("Archive served in %s\n", localhostAddr) // Open browser go func() { time.Sleep(time.Second) err := openBrowser(localhostAddr) if err != nil { cError.Printf("Failed to open browser: %v\n", err) } }() // Serve archive err = http.Serve(listener, router) if err != nil { cError.Printf("Failed to serve archive: %v\n", err) } }