First commit
commit
ed74ac1a00
|
|
@ -0,0 +1,5 @@
|
|||
module xdcc-cli
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/PuerkitoBio/goquery v1.8.0
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var registry *XdccProviderRegistry = nil
|
||||
|
||||
func init() {
|
||||
registry = NewProviderRegistry()
|
||||
registry.AddProvider(&XdccEuProvider{})
|
||||
}
|
||||
|
||||
func search(fileName string) {
|
||||
printer := NewTablePrinter([]string{"File Name", "Network", "Channel"})
|
||||
|
||||
res, _ := registry.Search(fileName)
|
||||
for _, fileInfo := range res {
|
||||
printer.AddRow(Row{fileInfo.Name, fileInfo.Network, fileInfo.Channel})
|
||||
}
|
||||
|
||||
printer.Print()
|
||||
}
|
||||
|
||||
func main() {
|
||||
searchCmd := flag.NewFlagSet("foo", flag.ExitOnError)
|
||||
fileName := searchCmd.String("f", "", "name of the file to search")
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("one of the following subcommands is expected: [search, get]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "search":
|
||||
searchCmd.Parse(os.Args[2:])
|
||||
search(*fileName)
|
||||
case "get":
|
||||
break
|
||||
default:
|
||||
fmt.Println("no such command: ", os.Args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Row []string
|
||||
|
||||
type TablePrinter struct {
|
||||
Headers []string
|
||||
Rows []Row
|
||||
MaxRows int
|
||||
}
|
||||
|
||||
func NewTablePrinter(headers []string) *TablePrinter {
|
||||
return &TablePrinter{
|
||||
Headers: headers,
|
||||
MaxRows: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func centerString(s string, width int) string {
|
||||
padSpace := width - len(s)
|
||||
leftPadding := padSpace / 2
|
||||
rightPadding := padSpace - leftPadding
|
||||
return strings.Repeat(" ", leftPadding) + s + strings.Repeat(" ", rightPadding)
|
||||
}
|
||||
|
||||
func formatStr(fileName string, maxSize int) string {
|
||||
if len(fileName) <= maxSize {
|
||||
return centerString(fileName, maxSize)
|
||||
}
|
||||
|
||||
return centerString(fileName[:maxSize-3]+"...", maxSize)
|
||||
}
|
||||
|
||||
const paddingDefault = 2
|
||||
|
||||
func (printer *TablePrinter) NumRows() int {
|
||||
if printer.Rows == nil {
|
||||
return 0
|
||||
}
|
||||
return len(printer.Rows)
|
||||
}
|
||||
|
||||
func (printer *TablePrinter) NumCols() int {
|
||||
return len(printer.Headers)
|
||||
}
|
||||
|
||||
func (printer *TablePrinter) hasRows() bool {
|
||||
return printer.Rows != nil && len(printer.Rows) > 0
|
||||
}
|
||||
|
||||
func (printer *TablePrinter) computeColumnWidthds() []int {
|
||||
widths := make([]int, printer.NumCols())
|
||||
for i := 0; i < printer.NumCols(); i++ {
|
||||
widths[i] = len(printer.Headers[i])
|
||||
}
|
||||
|
||||
if printer.hasRows() {
|
||||
for col := 0; col < printer.NumCols(); col++ {
|
||||
for row := 0; row < len(printer.Rows); row++ {
|
||||
if len(printer.Rows[row][col]) > widths[col] {
|
||||
widths[col] = len(printer.Rows[row][col])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < printer.NumCols(); i++ {
|
||||
widths[i] += paddingDefault
|
||||
}
|
||||
return widths
|
||||
}
|
||||
|
||||
func (printer *TablePrinter) renderRow(s []string, colWidths []int) string {
|
||||
content := "|"
|
||||
for i := 0; i < len(printer.Headers)-1; i++ {
|
||||
content += formatStr(s[i], colWidths[i]) + "|"
|
||||
}
|
||||
content += formatStr(s[printer.NumCols()-1], colWidths[printer.NumCols()-1]) + "|"
|
||||
return content
|
||||
}
|
||||
|
||||
func (printer *TablePrinter) renderLine(colWidths []int) string {
|
||||
content := "+"
|
||||
for i := 0; i < len(printer.Headers)-1; i++ {
|
||||
content += formatStr(strings.Repeat("-", colWidths[i]), colWidths[i]) + "-"
|
||||
}
|
||||
content += formatStr(strings.Repeat("-", colWidths[printer.NumCols()-1]), colWidths[printer.NumCols()-1]) + "+"
|
||||
return content
|
||||
}
|
||||
|
||||
func (printer *TablePrinter) renderHeader(colWidths []int) {
|
||||
fmt.Println(printer.renderLine(colWidths))
|
||||
fmt.Println(printer.renderRow(printer.Headers, colWidths))
|
||||
fmt.Println(printer.renderLine(colWidths))
|
||||
}
|
||||
|
||||
const initialRows = 100
|
||||
|
||||
func (printer *TablePrinter) AddRow(r Row) {
|
||||
if printer.Rows == nil {
|
||||
printer.Rows = make([]Row, 0, initialRows)
|
||||
}
|
||||
|
||||
if printer.MaxRows > 0 && printer.NumRows() == printer.MaxRows {
|
||||
return
|
||||
}
|
||||
printer.Rows = append(printer.Rows, r)
|
||||
|
||||
}
|
||||
|
||||
func (printer *TablePrinter) Print() {
|
||||
colWidths := printer.computeColumnWidthds()
|
||||
|
||||
printer.renderHeader(colWidths)
|
||||
if printer.hasRows() {
|
||||
for _, row := range printer.Rows {
|
||||
fmt.Println(printer.renderRow(row, colWidths))
|
||||
}
|
||||
fmt.Println(printer.renderLine(colWidths))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type XdccFileInfo struct {
|
||||
Network string
|
||||
Channel string
|
||||
BotName string
|
||||
Name string
|
||||
Slot int
|
||||
}
|
||||
|
||||
type XdccProvider interface {
|
||||
Search(fileName string) ([]XdccFileInfo, error)
|
||||
}
|
||||
|
||||
type XdccProviderRegistry struct {
|
||||
providerList []XdccProvider
|
||||
}
|
||||
|
||||
const MaxProviders = 100
|
||||
|
||||
func NewProviderRegistry() *XdccProviderRegistry {
|
||||
return &XdccProviderRegistry{
|
||||
providerList: make([]XdccProvider, 0, MaxProviders),
|
||||
}
|
||||
}
|
||||
|
||||
func (registry *XdccProviderRegistry) AddProvider(provider XdccProvider) {
|
||||
registry.providerList = append(registry.providerList, provider)
|
||||
}
|
||||
|
||||
const MaxResults = 1024
|
||||
|
||||
func (registry *XdccProviderRegistry) Search(fileName string) ([]XdccFileInfo, error) {
|
||||
allResults := make([]XdccFileInfo, 0, MaxResults)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(registry.providerList))
|
||||
for _, p := range registry.providerList {
|
||||
go func(p XdccProvider) {
|
||||
res, err := p.Search(fileName)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
allResults = append(allResults, res...)
|
||||
|
||||
wg.Done()
|
||||
}(p)
|
||||
}
|
||||
wg.Wait()
|
||||
return allResults, nil
|
||||
}
|
||||
|
||||
type XdccEuProvider struct{}
|
||||
|
||||
const XdccEuURL = "https://www.xdcc.eu/search.php"
|
||||
|
||||
func (p *XdccEuProvider) parseFields(fields []string) (*XdccFileInfo, error) {
|
||||
if len(fields) != 7 {
|
||||
return nil, errors.New("unespected number of search entry fields")
|
||||
}
|
||||
|
||||
fInfo := &XdccFileInfo{}
|
||||
fInfo.Network = fields[0]
|
||||
fInfo.Channel = fields[1]
|
||||
fInfo.BotName = fields[2]
|
||||
slot, err := strconv.Atoi(fields[3][1:])
|
||||
fInfo.Name = fields[6]
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fInfo.Slot = slot
|
||||
return fInfo, nil
|
||||
}
|
||||
|
||||
func (p *XdccEuProvider) Search(fileName string) ([]XdccFileInfo, error) {
|
||||
searchkey := strings.Join(strings.Fields(fileName), "+")
|
||||
res, err := http.Get(XdccEuURL + "?searchkey=" + searchkey)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load the HTML document
|
||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileInfos := make([]XdccFileInfo, 0)
|
||||
doc.Find("tr").Each(func(i int, s *goquery.Selection) {
|
||||
fields := make([]string, 0)
|
||||
|
||||
s.Find("td").Each(func(j int, si *goquery.Selection) {
|
||||
fields = append(fields, strings.TrimSpace(si.Text()))
|
||||
})
|
||||
|
||||
info, err := p.parseFields(fields)
|
||||
if err == nil {
|
||||
fileInfos = append(fileInfos, *info)
|
||||
}
|
||||
})
|
||||
return fileInfos, nil
|
||||
}
|
||||
Loading…
Reference in New Issue