128 lines
2.7 KiB
Go
128 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
|
|
"bargain-scraper/internal/exporter"
|
|
"bargain-scraper/internal/models"
|
|
"bargain-scraper/internal/scraper"
|
|
"bargain-scraper/internal/tui"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
func main() {
|
|
log.SetFlags(0)
|
|
log.SetOutput(os.Stderr)
|
|
|
|
if len(os.Args) > 1 && os.Args[1] == "--help" {
|
|
printHelp()
|
|
return
|
|
}
|
|
|
|
app := newApp()
|
|
if err := app.run(); err != nil {
|
|
log.Fatalf("Error: %v", err)
|
|
}
|
|
}
|
|
|
|
func printHelp() {
|
|
fmt.Println("Bargain Hardware Storage Scraper")
|
|
fmt.Println("")
|
|
fmt.Println("Usage:")
|
|
fmt.Println(" bargain-scraper Start the interactive TUI")
|
|
fmt.Println(" bargain-scraper --help Show this help message")
|
|
fmt.Println("")
|
|
fmt.Println("In the TUI:")
|
|
fmt.Println(" ↑/↓ or j/k Navigate")
|
|
fmt.Println(" Enter Select/scrape category")
|
|
fmt.Println(" / Start search")
|
|
fmt.Println(" E Export data")
|
|
fmt.Println(" R Rescrape current category")
|
|
fmt.Println(" Esc Go back")
|
|
}
|
|
|
|
type app struct {
|
|
model *tui.Model
|
|
scraper *scraper.Scraper
|
|
exp *exporter.Exporter
|
|
progressChan chan scraper.ScrapeProgress
|
|
doneChan chan struct{}
|
|
}
|
|
|
|
func newApp() *app {
|
|
return &app{
|
|
model: tui.New(),
|
|
exp: exporter.New(),
|
|
progressChan: make(chan scraper.ScrapeProgress, 100),
|
|
doneChan: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (a *app) run() error {
|
|
s, err := scraper.New()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create scraper: %w", err)
|
|
}
|
|
defer s.Close()
|
|
|
|
a.scraper = s
|
|
|
|
a.model.ScrapeCallback = func(category models.Category) {
|
|
go a.scrapeCategory(category)
|
|
}
|
|
|
|
a.model.ExportCallback = func(format string) error {
|
|
switch format {
|
|
case "json":
|
|
return a.exp.ExportToJSON(a.model.GetFilteredProducts(), "")
|
|
case "csv":
|
|
return a.exp.ExportToCSV(a.model.GetFilteredProducts(), "")
|
|
case "print":
|
|
fmt.Print(a.exp.PrintToConsole(a.model.GetFilteredProducts()))
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
p := tea.NewProgram(a.model, tea.WithAltScreen())
|
|
|
|
go a.handleProgress(p)
|
|
|
|
if _, err := p.Run(); err != nil {
|
|
return fmt.Errorf("error running TUI: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *app) handleProgress(p *tea.Program) {
|
|
for {
|
|
select {
|
|
case progress := <-a.progressChan:
|
|
a.model.SetProgress(progress)
|
|
p.Send(progress)
|
|
case <-a.doneChan:
|
|
p.Send(ScrapingDoneMsg{})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
type ScrapingDoneMsg struct{}
|
|
|
|
func (a *app) scrapeCategory(category models.Category) {
|
|
products, err := a.scraper.ScrapeCategory(a.model.GetContext(), category, a.progressChan)
|
|
|
|
a.model.SetScraping(false)
|
|
|
|
if err != nil {
|
|
a.model.SetError(err)
|
|
}
|
|
|
|
a.model.SetProducts(products)
|
|
a.doneChan <- struct{}{}
|
|
}
|