Refactoring n shit

This commit is contained in:
y a t s
2026-06-06 17:30:09 -04:00
parent 412f3108e1
commit 15541fb0da
9 changed files with 428 additions and 249 deletions
+30 -123
View File
@@ -2,129 +2,52 @@ package libkiwi
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
type LoginResp struct {
Status string `json:"status"`
Message string `json:"message"`
Redirect *url.URL `json:"redirect"`
Visitor struct {
ConversationsUnread uint32 `json:"conversations_unread"`
AlertsUnviewed uint32 `json:"alerts_unviewed"`
TotalUnread uint32 `json:"total_unread"`
} `json:"visitor"`
type Credentials struct {
Username string
Password string
Remember bool
}
func (lr *LoginResp) UnmarshalJSON(b []byte) error {
var jsonMap map[string]any
err := json.Unmarshal(b, &jsonMap)
if err != nil {
return err
func loginForm(xfToken string, creds Credentials) url.Values {
rem := []byte{'0'}
if creds.Remember {
rem[0] = '1'
}
for k, v := range jsonMap {
switch k {
case "status":
lr.Status = v.(string)
case "message":
lr.Message = v.(string)
case "redirect":
u, err := url.Parse(v.(string))
if err != nil {
return err
}
lr.Redirect = u
case "visitor":
for k, v := range v.(map[string]any) {
switch k {
case "conversations_unread":
convos, err := strconv.Atoi(v.(string))
if err != nil {
return err
}
lr.Visitor.ConversationsUnread = uint32(convos)
case "alerts_unviewed":
alerts, err := strconv.Atoi(v.(string))
if err != nil {
return err
}
lr.Visitor.AlertsUnviewed = uint32(alerts)
case "total_unread":
unread, err := strconv.Atoi(v.(string))
if err != nil {
return err
}
lr.Visitor.ConversationsUnread = uint32(unread)
}
}
}
}
return nil
}
var ErrNoXFToken = errors.New("Failed to locate xfToken on page.")
func xfToken(page io.Reader) (string, error) {
z := html.NewTokenizer(page)
for i := z.Next(); i != html.ErrorToken; i = z.Next() {
tk := z.Token()
if tk.DataAtom == atom.Html {
for _, a := range tk.Attr {
switch a.Key {
case "data-csrf":
return a.Val, nil
}
}
}
}
return "", ErrNoXFToken
}
func loginURL(host *url.URL) *url.URL {
return &url.URL{
Scheme: "https",
Host: host.Hostname(),
Path: "login",
return url.Values{
"_xfToken": {xfToken},
"login": {creds.Username},
"password": {creds.Password},
"remember": {string(rem)},
}
}
func (kf *KF) newLoginRequest(ctx context.Context, user string, pass string) (*http.Request, error) {
u := loginURL(kf.domain)
func (kf *KF) newLoginRequest(ctx context.Context, creds Credentials) (*http.Request, error) {
u := kf.urlFromPath("login")
resp, err := kf.GetPage(ctx, u)
resp, err := kf.Get(ctx, u)
if err != nil {
return nil, err
}
defer resp.Body.Close()
xfToken, err := xfToken(resp.Body)
xfToken, err := XFToken(resp.Body)
if err != nil {
return nil, err
}
form := url.Values{
"_xfToken": {xfToken},
"login": {user},
"password": {pass},
"remember": {"1"},
}
form := loginForm(xfToken, creds)
// i.e. https://kiwifarms.net/login/login
postURL := fmt.Sprintf("%s/login", u.String())
req, err := http.NewRequestWithContext(ctx, "POST", postURL, strings.NewReader(form.Encode()))
reqURL := fmt.Sprintf("%s/login", u.String())
req, err := http.NewRequestWithContext(ctx, "POST", reqURL, strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
@@ -133,13 +56,13 @@ func (kf *KF) newLoginRequest(ctx context.Context, user string, pass string) (*h
return req, nil
}
func (kf *KF) Login(ctx context.Context, user string, pass string) (*http.Response, error) {
req, err := kf.newLoginRequest(ctx, user, pass)
func (kf *KF) Login(ctx context.Context, creds Credentials) (*http.Response, error) {
req, err := kf.newLoginRequest(ctx, creds)
if err != nil {
return nil, err
}
resp, err := kf.client.Do(req)
resp, err := kf.Do(req)
if err != nil {
return nil, err
}
@@ -151,7 +74,7 @@ func (kf *KF) Login(ctx context.Context, user string, pass string) (*http.Respon
return resp, nil
}
func TwoFactorForm(xfToken string, code uint32, u *url.URL) url.Values {
func twoFactorForm(xfToken string, code uint32, u *url.URL) url.Values {
return url.Values{
"code": {fmt.Sprint(code)},
"provider": {"totp"},
@@ -166,19 +89,19 @@ func TwoFactorForm(xfToken string, code uint32, u *url.URL) url.Values {
}
}
func (kf *KF) twoFactorAuthForm(ctx context.Context, resp *http.Response, code uint32) (*http.Request, error) {
func (kf *KF) newTwoFactorRequest(ctx context.Context, resp *http.Response, code uint32) (*http.Request, error) {
u := resp.Request.URL
if !strings.Contains(u.Path, "two-step") {
// TODO: move err to proper type with url included.
return nil, errors.New("Did not redirect to two-step page.")
}
xfToken, err := xfToken(resp.Body)
xfToken, err := XFToken(resp.Body)
if err != nil {
return nil, err
}
form := TwoFactorForm(xfToken, code, u)
form := twoFactorForm(xfToken, code, u)
reqURL := fmt.Sprintf("https://%s/login/two-step", u.Hostname())
req, err := http.NewRequestWithContext(ctx, "POST", reqURL, strings.NewReader(form.Encode()))
@@ -191,28 +114,12 @@ func (kf *KF) twoFactorAuthForm(ctx context.Context, resp *http.Response, code u
}
func (kf *KF) TwoFactorAuth(ctx context.Context, resp *http.Response, code uint32) (*http.Response, error) {
req, err := kf.twoFactorAuthForm(ctx, resp, code)
req, err := kf.newTwoFactorRequest(ctx, resp, code)
if err != nil {
return nil, err
}
return kf.client.Do(req)
}
func setCookie(u *url.URL, jar http.CookieJar, newCookie *http.Cookie) {
cookies := jar.Cookies(u)
for i, c := range cookies {
if c.Name == newCookie.Name {
cookies[i] = newCookie
jar.SetCookies(u, cookies)
return
}
}
// Append if not already existing.
cookies = append(cookies, newCookie)
jar.SetCookies(u, cookies)
return kf.Do(req)
}
func (kf *KF) RefreshSession(ctx context.Context) error {
@@ -222,7 +129,7 @@ func (kf *KF) RefreshSession(ctx context.Context) error {
Value: "",
})
resp, err := kf.GetPage(ctx, kf.domain)
resp, err := kf.Get(ctx, kf.domain)
if err != nil {
return err
}