Skip to content

Instantly share code, notes, and snippets.

@sebnow
Created September 10, 2010 05:13
Show Gist options
  • Save sebnow/573141 to your computer and use it in GitHub Desktop.
Save sebnow/573141 to your computer and use it in GitHub Desktop.
Onet Czat API
package onet
import (
"xml"
"io"
"fmt"
"http"
"os"
"net"
"bufio"
"strings"
)
const APIUrl = "http://czat.onet.pl/include/ajaxapi.xml.php3"
const APIVersion = "1.1(20090619-1228 - R)"
type responseError struct {
XMLName xml.Name "error"
Err_code string "attr"
Err_text string "attr"
}
type responseUOKey struct {
XMLName xml.Name "root"
UoKey string
ZuoUsername string
Error responseError
}
type Error struct {
code int
text string
}
func (err Error) String() string {
return err.text
}
// A sample (successful) response looks like this:
//
// <?xml version="1.0" encoding="ISO-8859-2"?>
// <root>
// <uoKey>WRpTCGUHQZH_W4Qxumky34XNoXBMiDfx</uoKey>
// <zuoUsername>PossiblyDifferentUsername</zuoUsername>
// <error err_code="TRUE" err_text="wartość prawdziwa"></error>
// </root>
//
// This function unmarshals the response to a responseUOKey structure.
func getUOKeyResponse(source io.Reader) (r *responseUOKey, err os.Error) {
response := new(responseUOKey)
err = xml.Unmarshal(source, response)
return response, err
}
// Encode a value using the Onet Czat API encoding.
//
// The type of param will be checked to determine how to encode the
// data. If the type is a container, such as a map, the algorithm will
// recurse to encode the contained values as well.
//
// The syntax of the encoding in BNF is:
//
// <encoded> ::= <map> | <string> | <integer>
// <length> ::= DIGIT
// <number> ::= "i:" DIGITS
// <string> ::= "s:" <length> ':"' CHARACTERS '"'
// <map> ::= "a:" <length> <mapping>
// <mapping> ::= <association> | <association> <mapping>
// <association> ::= <encoded> ';' <encoded> ';'
//
// The value of <length> is the length of data structure. In the case
// of a string the <length> is the amount of characters in the
// string. In the case of a mapping, it is the amount of associations.
//
// Boolean types do not exist in this encoding. Booleans are converted
// to an integer, with 1 representing true, and 0 representing false.
func encodeParameter(param interface{}) (encoded string) {
switch value := param.(type) {
case string:
encoded = fmt.Sprintf("s:%d:\"%s\"", len(value), value)
case int:
encoded = fmt.Sprintf("i:%d", value)
case bool:
as_int := 0
if value {
as_int = 1
}
return encodeParameter(as_int)
case map[string]string:
encoded = fmt.Sprintf("a:%d:{", len(value))
for k, v := range value {
encoded += encodeParameter(k) + ";"
encoded += encodeParameter(v) + ";"
}
encoded += "}"
case map[string]int:
encoded = fmt.Sprintf("a:%d:{", len(value))
for k, v := range value {
encoded += encodeParameter(k) + ";"
encoded += encodeParameter(v) + ";"
}
encoded += "}"
case map[string]interface{}:
encoded = fmt.Sprintf("a:%d:{", len(value))
for k, v := range value {
encoded += encodeParameter(k) + ";"
encoded += encodeParameter(v) + ";"
}
encoded += "}"
}
return
}
type nopCloser struct {
io.Reader
}
func (nopCloser) Close() (err os.Error) { return }
func sendPostRequest(url, contentType, body string) (resp *http.Response, err os.Error) {
// The standard http.Post() function unfortunately always
// sends the request as chunked, which isn't supported by the
// Onet servers. We have to duplicate most of the code from the
// function, just to specify the Content-Length.
var headers = map[string]string {
"Content-Type": contentType,
"Accept": "text/html, application/xml",
}
var req = http.Request {
Method: "POST",
ContentLength: int64(len(body)),
Close: true,
Header: headers,
Body: nopCloser{strings.NewReader(body)},
ProtoMajor: 1,
ProtoMinor: 1,
}
req.URL, err = http.ParseURL(url)
addr := req.URL.Host
if strings.LastIndex(addr, ":") > strings.LastIndex(addr, "]") {
addr += "http"
}
conn, err := net.Dial("tcp", "", "czat.onet.pl:http")
if err != nil {
return
}
err = req.Write(conn)
if err != nil {
return
}
reader := bufio.NewReader(conn)
resp, err = http.ReadResponse(reader, req.Method)
if err != nil {
conn.Close()
return nil, err
}
return
}
// Request a UO for the sepified nick and password.
//
// If a password is not supplied the user will be logged in as a
// temporary user, and the new nick will be returned as newNick
// (typically nick with "~" prepended). If an error occurs during the
// request, it will be returned as err.
func GetUOKey(nick, password string) (uokey, newNick string, err os.Error) {
var args = map[string]interface{} {
"nick": nick,
"tempNick": true,
"version": APIVersion,
}
if password != "" {
args["tempNick"] = false
}
call := "api_function=getUoKey&params=" + encodeParameter(args)
resp, err := sendPostRequest(APIUrl, "application/x-www-form-urlencoded", call)
if err != nil {
return
}
response, err := getUOKeyResponse(resp.Body)
if err != nil {
return
}
uokey = response.UoKey
newNick = response.ZuoUsername
if response.Error.Err_code != "TRUE" {
err = os.ErrorString(response.Error.Err_text)
return
}
return uokey, newNick, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment