Files
2024-07-02 06:03:35 +00:00

166 lines
3.3 KiB
Go
Executable File

package eaclient
import (
"bytes"
"fmt"
"net/http"
"unicode"
"github.com/YoshihikoAbe/avsproperty"
)
type clientError string
func (err clientError) Error() string {
return "eaclient: " + string(err)
}
type CompressType int
const (
CompressDisable CompressType = iota
CompressNone
CompressLZ
)
func (c *CompressType) UnmarshalText(b []byte) error {
switch s := string(bytes.ToLower(b)); s {
case "":
fallthrough
case "disable":
*c = CompressDisable
case "none":
*c = CompressNone
case "lz":
fallthrough
case "lz77":
*c = CompressLZ
default:
return clientError("invalid compress type: " + s)
}
return nil
}
type FormatType int
const (
FormatBinary FormatType = iota
FormatXML
)
func (f *FormatType) UnmarshalText(b []byte) error {
switch s := string(bytes.ToLower(b)); s {
case "":
fallthrough
case "binary":
*f = FormatBinary
case "xml":
*f = FormatXML
default:
return clientError("invalid format type: " + s)
}
return nil
}
type Service struct {
URL string `yaml:"url"`
Host string `yaml:"host"`
Obfuscate bool `yaml:"obfuscate"`
Compress CompressType `yaml:"compress"`
Format FormatType `yaml:"format"`
Encoding string `yaml:"encoding"`
}
type Client struct {
Model string `yaml:"model"`
Srcid string `yaml:"srcid"`
UserAgent string `yaml:"useragent"`
DisableQuery bool `yaml:"disable_query"`
HTTP http.Client `yaml:"-"`
}
func (client *Client) Send(svc Service, call *avsproperty.Node) (*avsproperty.Property, error) {
if !validModel(client.Model) {
return nil, clientError("invalid character in model")
}
if call == nil {
return nil, clientError("call node is nil")
}
if call.Name().String() != "call" {
return nil, clientError("root node's name is not \"call\"")
}
if len(call.Children()) != 1 {
return nil, clientError("call node has an invalid number of children")
}
module := call.Children()[0]
method := module.AttributeValue("method")
if method == "" {
return nil, clientError("module node does not contain a method attribute")
}
if !client.DisableQuery {
// this isn't used for routing on real e-amusement,
// but it makes the logs look more authentic
svc.URL += fmt.Sprintf("?model=%s&f=%s.%s", client.Model, module.Name(), method)
}
call.SetAttribute("srcid", client.Srcid)
call.SetAttribute("model", client.Model)
prop := &avsproperty.Property{
Root: call,
}
err := client.do(prop, svc)
if err != nil {
return nil, err
}
return prop, nil
}
func (client *Client) do(prop *avsproperty.Property, svc Service) error {
req, err := EncodeRequest(prop, svc)
if err != nil {
return err
}
if s := client.UserAgent; s != "" {
req.Header.Set("User-Agent", s)
} else {
req.Header.Set("User-Agent", "EAMUSE.XRPC/1.0")
}
resp, err := client.HTTP.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := DecodeResponse(prop, resp); err != nil {
return err
}
if prop.Root == nil {
return clientError("empty response property")
}
if prop.Root.Name().String() != "response" {
return clientError("name of root node in response property is not \"response\"")
}
return nil
}
func validModel(s string) bool {
for _, r := range s {
if !unicode.In(r, unicode.Number, unicode.Letter) && r != ':' {
return false
}
}
return true
}