forked from hswaw/hscloud
216 lines
5.2 KiB
Go
216 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/golang/glog"
|
|
)
|
|
|
|
type Envelope struct {
|
|
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
|
Body *Body
|
|
}
|
|
|
|
type Body struct {
|
|
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
|
Request *Request
|
|
Response *Response
|
|
Fault *Fault
|
|
}
|
|
|
|
type Request struct {
|
|
XMLName xml.Name `xml:"urn:AC executeCommand"`
|
|
Command string `xml:"command"`
|
|
}
|
|
type Response struct {
|
|
XMLName xml.Name `xml:"urn:AC executeCommandResponse"`
|
|
Result string `xml:"result"`
|
|
}
|
|
|
|
type Fault struct {
|
|
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"`
|
|
Code string `xml:"faultcode"`
|
|
String string `xml:"faultstring"`
|
|
}
|
|
|
|
type commandRes struct {
|
|
result string
|
|
fault string
|
|
}
|
|
|
|
var (
|
|
reAccount = regexp.MustCompile(`^[a-z0-9\-_\.]{3,16}$`)
|
|
rePassword = regexp.MustCompile(`^[a-zA-Z0-9\-_\.]{3,16}$`)
|
|
)
|
|
|
|
func createAccount(ctx context.Context, name, password string) error {
|
|
if !reAccount.MatchString(name) {
|
|
return fmt.Errorf("invalid account name")
|
|
}
|
|
if !rePassword.MatchString(password) {
|
|
return fmt.Errorf("invalid password name")
|
|
}
|
|
res, err := runCommand(ctx, fmt.Sprintf("account create %s %s", name, password))
|
|
if err != nil {
|
|
glog.Errorf("Account create: %v", err)
|
|
return fmt.Errorf("server unavailable")
|
|
}
|
|
if res.result == fmt.Sprintf("Account created: %s", name) {
|
|
glog.Infof("Created account %q", name)
|
|
return nil
|
|
}
|
|
glog.Errorf("Account create fault: %q/%q", res.fault, res.result)
|
|
return fmt.Errorf("server error")
|
|
}
|
|
|
|
func ensureAccount(ctx context.Context, name, password string) error {
|
|
if !reAccount.MatchString(name) {
|
|
return fmt.Errorf("invalid account name")
|
|
}
|
|
if !rePassword.MatchString(password) {
|
|
return fmt.Errorf("invalid password name")
|
|
}
|
|
res, err := runCommand(ctx, fmt.Sprintf("account create %s %s", name, password))
|
|
if err != nil {
|
|
glog.Errorf("Account create: %v", err)
|
|
return fmt.Errorf("server unavailable")
|
|
}
|
|
if res.result == fmt.Sprintf("Account created: %s", name) {
|
|
glog.Infof("Created account %q", name)
|
|
return nil
|
|
}
|
|
if res.fault != "Account with this name already exist!" {
|
|
glog.Errorf("Account create fault: %q/%q", res.fault, res.result)
|
|
return fmt.Errorf("server error")
|
|
}
|
|
|
|
res, err = runCommand(ctx, fmt.Sprintf("account set password %s %s %s", name, password, password))
|
|
if res.result == "The password was changed" {
|
|
glog.Infof("Updated password for account %q", name)
|
|
return nil
|
|
}
|
|
glog.Infof("password update fault: %q/%q", res.fault, res.result)
|
|
return fmt.Errorf("server error")
|
|
}
|
|
|
|
func runCommand(ctx context.Context, cmd string) (*commandRes, error) {
|
|
data, err := xml.Marshal(&Envelope{
|
|
Body: &Body{
|
|
Request: &Request{
|
|
Command: cmd,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal: %w", err)
|
|
}
|
|
buf := bytes.NewBuffer(data)
|
|
req, err := http.NewRequestWithContext(ctx, "POST", flagSOAPAddress, buf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("NewRequest(POST, %q): %w", flagSOAPAddress, err)
|
|
}
|
|
|
|
req.SetBasicAuth(flagSOAPUsername, flagSOAPPassword)
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("req.Do: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ReadAll response: %w", err)
|
|
}
|
|
|
|
respEnvelope := Envelope{}
|
|
err = xml.Unmarshal(respBytes, &respEnvelope)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unmarshal: %w", err)
|
|
}
|
|
|
|
if respEnvelope.Body == nil {
|
|
return nil, fmt.Errorf("no body returned")
|
|
}
|
|
|
|
if respEnvelope.Body.Fault != nil {
|
|
fault := respEnvelope.Body.Fault
|
|
if fault.Code == "SOAP-ENV:Client" {
|
|
return &commandRes{
|
|
fault: strings.TrimSpace(fault.String),
|
|
}, nil
|
|
}
|
|
return nil, fmt.Errorf("SOAP error %q: %v", fault.Code, fault.String)
|
|
}
|
|
|
|
result := ""
|
|
if respEnvelope.Body.Response != nil {
|
|
result = respEnvelope.Body.Response.Result
|
|
}
|
|
|
|
return &commandRes{
|
|
result: strings.TrimSpace(result),
|
|
}, nil
|
|
}
|
|
|
|
type playerinfo struct {
|
|
Account string
|
|
Character string
|
|
}
|
|
|
|
func onlinelist(ctx context.Context) ([]playerinfo, error) {
|
|
res, err := runCommand(ctx, "account onlinelist")
|
|
if err != nil {
|
|
glog.Errorf("onlinelist: %v", err)
|
|
return nil, fmt.Errorf("server unavailable")
|
|
}
|
|
if res.fault != "" {
|
|
glog.Errorf("onlinelist fault: %q", res.fault)
|
|
return nil, fmt.Errorf("server unavailable")
|
|
}
|
|
|
|
lines := strings.Split(res.result, "\n")
|
|
header := false
|
|
var pi []playerinfo
|
|
for _, line := range lines {
|
|
switch {
|
|
case strings.HasPrefix(line, "-="):
|
|
continue
|
|
case strings.HasPrefix(line, "-["):
|
|
default:
|
|
glog.Warningf("unparseable line %q", line)
|
|
continue
|
|
}
|
|
if !header {
|
|
header = true
|
|
continue
|
|
}
|
|
if len(line) != 69 {
|
|
glog.Warningf("wrong line length: %q", line)
|
|
continue
|
|
}
|
|
account := strings.ToLower(strings.TrimSpace(line[2:18]))
|
|
if line[18:20] != "][" {
|
|
glog.Warningf("unparseable line %q (wrong sep1)", line)
|
|
continue
|
|
}
|
|
character := strings.TrimSpace(line[20:32])
|
|
if line[32:34] != "][" {
|
|
glog.Warningf("unparseable line %q (wrong sep2)", line)
|
|
continue
|
|
}
|
|
pi = append(pi, playerinfo{
|
|
Account: account,
|
|
Character: character,
|
|
})
|
|
}
|
|
glog.Infof("Onlinelist: %v", pi)
|
|
return pi, nil
|
|
}
|