init
This commit is contained in:
231
internal/ncore/client.go
Normal file
231
internal/ncore/client.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package ncore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
||||
"ncore-hnr/internal/model"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
loginURL string
|
||||
hitRunURL string
|
||||
}
|
||||
|
||||
func New(httpClient *http.Client, loginURL string, hitRunURL string) *Client {
|
||||
if httpClient == nil {
|
||||
jar, _ := cookiejar.New(nil)
|
||||
httpClient = &http.Client{Jar: jar}
|
||||
}
|
||||
return &Client{httpClient: httpClient, loginURL: loginURL, hitRunURL: hitRunURL}
|
||||
}
|
||||
|
||||
func (c *Client) Login(username string, password string) error {
|
||||
resp, err := c.httpClient.Get(c.loginURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("login page returned %s", resp.Status)
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
form := findLoginForm(doc)
|
||||
if form.Length() == 0 {
|
||||
return fmt.Errorf("could not find nCore login form")
|
||||
}
|
||||
|
||||
values := formValues(form)
|
||||
values.Set(inputName(form, "text", "nev"), username)
|
||||
values.Set(inputName(form, "password", "pass"), password)
|
||||
if values.Get("submitted") == "" {
|
||||
values.Set("submitted", "1")
|
||||
}
|
||||
if values.Get("set_lang") == "" {
|
||||
values.Set("set_lang", "hu")
|
||||
}
|
||||
values.Set("ne_leptessen_ki", "1")
|
||||
|
||||
action, ok := form.Attr("action")
|
||||
if !ok || strings.TrimSpace(action) == "" {
|
||||
action = c.loginURL
|
||||
}
|
||||
target, err := url.Parse(c.loginURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actionURL, err := target.Parse(action)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
postResp, err := c.httpClient.PostForm(actionURL.String(), values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postResp.Body.Close()
|
||||
if postResp.StatusCode < 200 || postResp.StatusCode >= 300 {
|
||||
return fmt.Errorf("login returned %s", postResp.Status)
|
||||
}
|
||||
|
||||
loginDoc, err := goquery.NewDocumentFromReader(postResp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if findLoginForm(loginDoc).Length() > 0 {
|
||||
return fmt.Errorf("login failed; check credentials or browser-only checks")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) FetchHitRun() (model.HitRunPage, error) {
|
||||
reqURL, err := url.Parse(c.hitRunURL)
|
||||
if err != nil {
|
||||
return model.HitRunPage{}, err
|
||||
}
|
||||
query := reqURL.Query()
|
||||
query.Set("showall", "false")
|
||||
reqURL.RawQuery = query.Encode()
|
||||
|
||||
resp, err := c.httpClient.Get(reqURL.String())
|
||||
if err != nil {
|
||||
return model.HitRunPage{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return model.HitRunPage{}, fmt.Errorf("hitnrun page returned %s", resp.Status)
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return model.HitRunPage{}, err
|
||||
}
|
||||
if findLoginForm(doc).Length() > 0 {
|
||||
return model.HitRunPage{}, fmt.Errorf("nCore session is not authenticated")
|
||||
}
|
||||
|
||||
return parseHitRunPage(doc), nil
|
||||
}
|
||||
|
||||
func parseHitRunPage(doc *goquery.Document) model.HitRunPage {
|
||||
page := model.HitRunPage{Stats: map[string]string{}}
|
||||
|
||||
alert := cleanText(doc.Find("#hnrAlert .fobox_tartalom").Clone().ChildrenFiltered("script").Remove().End())
|
||||
page.Alert = alert
|
||||
|
||||
doc.Find(".dt").Each(func(_ int, label *goquery.Selection) {
|
||||
value := label.NextFiltered(".dd")
|
||||
key := strings.TrimSuffix(cleanText(label), ":")
|
||||
val := cleanText(value)
|
||||
if key != "" && val != "" {
|
||||
page.Stats[key] = val
|
||||
}
|
||||
})
|
||||
|
||||
doc.Find(".hnr_torrents > div").Each(func(_ int, row *goquery.Selection) {
|
||||
link := row.Find(".hnr_tname a").First()
|
||||
href, _ := link.Attr("href")
|
||||
title, _ := link.Attr("title")
|
||||
if title == "" {
|
||||
title = cleanText(row.Find(".hnr_tname"))
|
||||
}
|
||||
|
||||
page.Torrents = append(page.Torrents, model.Torrent{
|
||||
ID: torrentID(href),
|
||||
Name: title,
|
||||
URL: absoluteURL(href),
|
||||
Start: cleanText(row.Find(".hnr_tstart")),
|
||||
Updated: cleanText(row.Find(".hnr_tlastactive")),
|
||||
Status: cleanText(row.Find(".hnr_tseed")),
|
||||
Uploaded: cleanText(row.Find(".hnr_tup")),
|
||||
Downloaded: cleanText(row.Find(".hnr_tdown")),
|
||||
Remaining: cleanText(row.Find(".hnr_ttimespent")),
|
||||
Ratio: cleanText(row.Find(".hnr_tratio")),
|
||||
HnRMarked: row.Find(".hnr_tstart .stopped").Length() > 0,
|
||||
})
|
||||
})
|
||||
|
||||
return page
|
||||
}
|
||||
|
||||
func findLoginForm(doc *goquery.Document) *goquery.Selection {
|
||||
return doc.Find("form").FilterFunction(func(_ int, form *goquery.Selection) bool {
|
||||
return form.Find("input[type='password']").Length() > 0
|
||||
}).First()
|
||||
}
|
||||
|
||||
func formValues(form *goquery.Selection) url.Values {
|
||||
values := url.Values{}
|
||||
form.Find("input").Each(func(_ int, input *goquery.Selection) {
|
||||
name, ok := input.Attr("name")
|
||||
if !ok || name == "" {
|
||||
return
|
||||
}
|
||||
inputType := strings.ToLower(attr(input, "type", "text"))
|
||||
if inputType == "submit" || inputType == "button" || inputType == "image" || inputType == "file" || inputType == "text" || inputType == "password" {
|
||||
return
|
||||
}
|
||||
if (inputType == "checkbox" || inputType == "radio") && attr(input, "checked", "") == "" {
|
||||
return
|
||||
}
|
||||
values.Set(name, attr(input, "value", ""))
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
func inputName(form *goquery.Selection, inputType string, preferred string) string {
|
||||
if form.Find("input[name='"+preferred+"']").Length() > 0 {
|
||||
return preferred
|
||||
}
|
||||
if name, ok := form.Find("input[type='" + inputType + "']").First().Attr("name"); ok && name != "" {
|
||||
return name
|
||||
}
|
||||
return preferred
|
||||
}
|
||||
|
||||
func torrentID(href string) int64 {
|
||||
parsed, err := url.Parse(href)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
id, err := strconv.ParseInt(parsed.Query().Get("id"), 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func absoluteURL(href string) string {
|
||||
if href == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(href, "http://") || strings.HasPrefix(href, "https://") {
|
||||
return href
|
||||
}
|
||||
return "https://ncore.pro/" + strings.TrimPrefix(href, "/")
|
||||
}
|
||||
|
||||
func cleanText(selection *goquery.Selection) string {
|
||||
return strings.Join(strings.Fields(selection.Text()), " ")
|
||||
}
|
||||
|
||||
func attr(selection *goquery.Selection, name string, fallback string) string {
|
||||
value, ok := selection.Attr(name)
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user