192 lines
4.6 KiB
Go
192 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/Ullaakut/disgo/style"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
"github.com/Ullaakut/disgo"
|
|
"github.com/vbauerster/mpb"
|
|
"github.com/vbauerster/mpb/decor"
|
|
)
|
|
|
|
const dictionaryURL = "https://community.geniusvision.net/platform/cprndr/manulist"
|
|
|
|
var rtspURLsFound sync.Map
|
|
|
|
func main() {
|
|
if err := updateDictionary(); err != nil {
|
|
log.Fatalf(err.Error())
|
|
}
|
|
}
|
|
|
|
func updateDictionary() error {
|
|
disgo.SetTerminalOptions(disgo.WithColors(true), disgo.WithDebug(true))
|
|
|
|
disgo.StartStep("Fetching dictionary list")
|
|
resp, err := http.Get(dictionaryURL)
|
|
if err != nil {
|
|
return disgo.FailStepf("unable to download dictionaries: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
disgo.StartStep("Parsing dictionary list")
|
|
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
|
if err != nil {
|
|
return disgo.FailStepf("unable to read from dictionary list: %v", err)
|
|
}
|
|
|
|
var vendorURLs []string
|
|
doc.Find("td.simpletable a").Each(func(i int, s *goquery.Selection) {
|
|
url, ok := s.Attr("href")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if url != "javascript:void(0)" {
|
|
vendorURLs = append(vendorURLs, url)
|
|
}
|
|
})
|
|
|
|
disgo.StartStep("Loading current cameradar dictionary")
|
|
currentDictionary, err := ioutil.ReadFile("dictionaries/routes")
|
|
if err != nil {
|
|
return disgo.FailStepf("unable to read current dictionary: %v", err)
|
|
}
|
|
|
|
dictionaryEntries := bytes.Split(currentDictionary, []byte("\n"))
|
|
|
|
for _, rtspURL := range dictionaryEntries {
|
|
rtspURLsFound.Store(string(rtspURL), struct{}{})
|
|
}
|
|
|
|
disgo.Debugf("Current dictionary has %d entries\n", len(dictionaryEntries))
|
|
disgo.EndStep()
|
|
|
|
p := mpb.New(mpb.WithWidth(64))
|
|
name := fmt.Sprintf("Fetching default routes from %d constructors:", len(vendorURLs))
|
|
bar := p.AddBar(int64(len(vendorURLs)),
|
|
// set custom bar style, default one is "[=>-]"
|
|
mpb.BarStyle("╢▌▌░╟"),
|
|
mpb.PrependDecorators(
|
|
// display our name with one space on the right
|
|
decor.Name(name, decor.WC{W: len(name), C: decor.DidentRight}),
|
|
),
|
|
mpb.AppendDecorators(decor.Percentage()),
|
|
)
|
|
|
|
for _, url := range vendorURLs {
|
|
go loadRoutes(url, bar)
|
|
}
|
|
|
|
p.Wait()
|
|
|
|
disgo.StartStep("Converting found routes into proper data model")
|
|
|
|
var rtspURLs []string
|
|
rtspURLsFound.Range(func(rtspURL, _ interface{}) bool {
|
|
disgo.Infoln("Adding URL", rtspURL.(string))
|
|
rtspURLs = append(rtspURLs, rtspURL.(string))
|
|
return true
|
|
})
|
|
|
|
sort.Slice(rtspURLs, func(a, b int) bool {
|
|
return rtspURLs[a] < rtspURLs[b]
|
|
})
|
|
|
|
disgo.EndStep()
|
|
|
|
if len(dictionaryEntries) < len(rtspURLs) {
|
|
disgo.Infof("%s Saving them in cameradar default dictionary.\n", style.Success("Found ", len(rtspURLs)-len(dictionaryEntries), " new entries!"))
|
|
saveRoutes(rtspURLs)
|
|
} else {
|
|
disgo.Infoln(style.Success("No new entry found, dictionary up-to-date! :)"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func loadRoutes(url string, bar *mpb.Bar) {
|
|
defer bar.IncrBy(1)
|
|
|
|
var (
|
|
failureCounter int
|
|
resp *http.Response
|
|
err error
|
|
)
|
|
for failureCounter < 5 {
|
|
resp, err = http.Get(url)
|
|
if err != nil {
|
|
failureCounter++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
if failureCounter == 5 {
|
|
disgo.Errorln("Request failed 5 times in a row, giving up on this vendor")
|
|
return
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
|
if err != nil {
|
|
disgo.Errorf("unable to read from dictionary list for URL %q: %v\n", url, err)
|
|
return
|
|
}
|
|
|
|
doc.Find("tr.simpletable td.simpletable:nth-child(4) a").Each(func(i int, s *goquery.Selection) {
|
|
rtspURL := s.Text()
|
|
|
|
if strings.HasPrefix(rtspURL, "(") && strings.HasSuffix(rtspURL, ")") {
|
|
return
|
|
}
|
|
|
|
if strings.HasPrefix(rtspURL, "[") && strings.HasSuffix(rtspURL, "]") {
|
|
return
|
|
}
|
|
|
|
if strings.HasPrefix(rtspURL, "http://") {
|
|
return
|
|
}
|
|
|
|
// Skip the port and only get the route.
|
|
if strings.HasPrefix(rtspURL, "rtsp://ip-addr:") {
|
|
routeAndPort := strings.TrimSpace(strings.TrimPrefix(rtspURL, "rtsp://ip-addr:"))
|
|
route := strings.TrimLeft(routeAndPort, "0123456789/")
|
|
rtspURLsFound.Store(route, struct{}{})
|
|
return
|
|
}
|
|
|
|
switch rtspURL {
|
|
case "",
|
|
"rtsp://ip-addr/",
|
|
"rtsp://ip-addr",
|
|
"rtsp://ip-addr:pass@10.0.0.5:6667/blinkhd":
|
|
return
|
|
default:
|
|
route := strings.TrimSpace(strings.TrimPrefix(rtspURL, "rtsp://ip-addr/"))
|
|
rtspURLsFound.Store(route, struct{}{})
|
|
}
|
|
})
|
|
}
|
|
|
|
func saveRoutes(rtspURLs []string) {
|
|
contents := strings.Join(rtspURLs, "\n")
|
|
|
|
disgo.StartStep("Writing new dictionary file")
|
|
err := ioutil.WriteFile("dictionaries/routes", []byte(contents), 0644)
|
|
if err != nil {
|
|
disgo.FailStepf("unable to write dictionnary: %v", err)
|
|
}
|
|
}
|