205 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| package cmdline
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/antchfx/htmlquery"
 | |
| 	"github.com/playwright-community/playwright-go"
 | |
| 	"golang.org/x/net/html"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	printDebug bool
 | |
| 	outputFile string
 | |
| 	errorLog   *log.Logger
 | |
| 	mainLog    *log.Logger
 | |
| )
 | |
| 
 | |
| func recurseNodes(top *html.Node, sb *strings.Builder) {
 | |
| 	if top.Type == html.ElementNode && top.Data == "span" {
 | |
| 		sb.WriteString(htmlquery.InnerText(top) + "\n")
 | |
| 	}
 | |
| 	for c := top.FirstChild; c != nil; c = c.NextSibling {
 | |
| 		recurseNodes(c, sb)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func searchGoogle(song string) error {
 | |
| 	runOption := &playwright.RunOptions{
 | |
| 		SkipInstallBrowsers: true,
 | |
| 	}
 | |
| 	err := playwright.Install(runOption)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not install playwright dependencies: %v", err)
 | |
| 	}
 | |
| 	pw, err := playwright.Run()
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not start playwright: %v", err)
 | |
| 	}
 | |
| 	defer func(pw *playwright.Playwright) error {
 | |
| 		err := pw.Stop()
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("could not stop Playwright: %v", err)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}(pw)
 | |
| 
 | |
| 	option := playwright.BrowserTypeLaunchOptions{
 | |
| 		Channel:  playwright.String("chrome"),
 | |
| 		Headless: playwright.Bool(false),
 | |
| 	}
 | |
| 	browser, err := pw.Chromium.Launch(option)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not launch browser: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	defer func(browser playwright.Browser) error {
 | |
| 		err = browser.Close()
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("could not close browser: %v", err)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}(browser)
 | |
| 	page, err := browser.NewPage()
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not create page: %v", err)
 | |
| 	}
 | |
| 	if _, err := page.Goto(fmt.Sprintf("https://www.google.com/search?q=%ss+lyrics", song),
 | |
| 		playwright.PageGotoOptions{
 | |
| 			WaitUntil: playwright.WaitUntilStateLoad,
 | |
| 		}); err != nil {
 | |
| 		return fmt.Errorf("could not goto: %v", err)
 | |
| 	}
 | |
| 	err = page.Locator("body").WaitFor(playwright.LocatorWaitForOptions{
 | |
| 		State: playwright.WaitForSelectorStateVisible,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not wait for body: %v", err)
 | |
| 	}
 | |
| 	html, err := page.Locator("html").InnerHTML()
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not get innerHtml: %v", err)
 | |
| 	}
 | |
| 	doc, err := htmlquery.Parse(bytes.NewReader([]byte(html)))
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not parse the innerHtml: %v", err)
 | |
| 	}
 | |
| 	nodes, err := htmlquery.QueryAll(doc, "//div[@data-lyricid]/div")
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not get the nodes: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	var sb strings.Builder
 | |
| 	for _, node := range nodes {
 | |
| 		recurseNodes(node, &sb)
 | |
| 	}
 | |
| 	if sb.Len() > 0 {
 | |
| 		if printDebug {
 | |
| 			mainLog.Println("Writing lyrics from Google...")
 | |
| 		}
 | |
| 		filename := fmt.Sprintf("%s_google.txt", outputFile)
 | |
| 		err = os.WriteFile(filename, []byte(sb.String()), os.ModePerm)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("could not write to %s: %v", filename, err)
 | |
| 		}
 | |
| 	} else {
 | |
| 		mainLog.Println("Lyrics cannot be found...")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // func searchGenius(song string, lg *log.Logger) {
 | |
| 
 | |
| // }
 | |
| 
 | |
| func Main() int {
 | |
| 	programName := os.Args[0]
 | |
| 	errorLog = log.New(os.Stderr, "", 0)
 | |
| 	mainLog = log.New(os.Stdout, "", 0)
 | |
| 
 | |
| 	flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
 | |
| 	flags.Usage = func() {
 | |
| 		out := flags.Output()
 | |
| 		fmt.Fprintf(out, "Usage: %v \n\n", programName)
 | |
| 		fmt.Fprint(out, "  This program is used to download lyrics for a song\n")
 | |
| 		fmt.Fprint(out, "  from the internet. The steps of operation are shown here: \n\n")
 | |
| 		fmt.Fprint(out, "  (a) It first opens a chrome window, searches for the lyrics \n")
 | |
| 		fmt.Fprint(out, "  , and copies the lyrics returned by Google Search to a \n")
 | |
| 		fmt.Fprint(out, "  file defined by you.\n\n")
 | |
| 		fmt.Fprint(out, "  (b) It then tries to get search for the same song using the \n")
 | |
| 		fmt.Fprint(out, "  Genius API. It then tries to compare the lyrics with the \n")
 | |
| 		fmt.Fprint(out, "  Genius one.\n\n")
 | |
| 		flags.PrintDefaults()
 | |
| 	}
 | |
| 
 | |
| 	outputFlag := flags.String("output", "", "Optional. Lyrics filename")
 | |
| 	verboseFlag := flags.Bool("verbose", false, "Optional. Turn on debug. Default is false.")
 | |
| 	searchFlag := flags.String("search", "", "Required. Name of song to search. If the name of the song is not a single word, put in quotes\"\"")
 | |
| 	helpFlag := flags.Bool("help", false, "Optional. Print Usage")
 | |
| 
 | |
| 	flags.Parse(os.Args[1:])
 | |
| 
 | |
| 	if len(flags.Args()) > 1 {
 | |
| 		errorLog.Println("Error: too many command-line arguments")
 | |
| 		flags.Usage()
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	allSetFlags := flagsSet(flags)
 | |
| 
 | |
| 	if allSetFlags["help"] && (allSetFlags["output"] || allSetFlags["search"] || allSetFlags["verbose"]) {
 | |
| 		errorLog.Println("Error: if -help is set, -output, -search and -verbose must remain unset")
 | |
| 		flags.Usage()
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	if *helpFlag {
 | |
| 		flags.Usage()
 | |
| 		return 0
 | |
| 	}
 | |
| 
 | |
| 	songToSearch := *searchFlag
 | |
| 
 | |
| 	if len(songToSearch) == 0 {
 | |
| 		errorLog.Println("Error: the song name must be provided for search")
 | |
| 		flags.Usage()
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	if allSetFlags["output"] {
 | |
| 		outputFile = *outputFlag
 | |
| 	} else {
 | |
| 		mainLog.Printf("Using %s as the name of the file(s) for downloaded lyrics..\n", songToSearch)
 | |
| 		outputFile = fmt.Sprintf("%s_lyrics", songToSearch)
 | |
| 	}
 | |
| 
 | |
| 	printDebug = *verboseFlag
 | |
| 	if printDebug {
 | |
| 		mainLog.Printf("Output flag: %s, Debug flag: %t, Search flag: %s\n", outputFile, printDebug, songToSearch)
 | |
| 	}
 | |
| 
 | |
| 	err := searchGoogle(songToSearch)
 | |
| 	if err != nil {
 | |
| 		errorLog.Printf("Err: %+v", err)
 | |
| 		return 1
 | |
| 	}
 | |
| 
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| // flagsSet returns a set of all the flags what were actually set on the
 | |
| // command line.
 | |
| func flagsSet(flags *flag.FlagSet) map[string]bool {
 | |
| 	s := make(map[string]bool)
 | |
| 	flags.Visit(func(f *flag.Flag) {
 | |
| 		s[f.Name] = true
 | |
| 	})
 | |
| 	return s
 | |
| }
 |