Node.js implementation of xat client. It's not an application, it's a library. xat-client can be used to build bot or chat client.
const XatUser = require('xat-client').XatUser
const user = new XatUser({
todo: {
w_userno: 123456,
w_k1: 'deadbeef777',
w_name: 'nickname',
w_avatar: '42',
w_useroom: 123,
},
})
user.on('data', (data) => {
console.log(data)
if (data.done !== undefined) {
user.send('<m t="hello!" u="123456" />')
}
}).on('send', (data) => {
console.log(data.xml)
})
const fromSol = require('xat-client').fromSol
fromSol('./docs/sol/unregistered-new', (user) => {
user.connect()
})
Compare it to low-level example.
It's preferable to use user-actions instead of core-api send method in most cases.
const XatUser = require('xat-client').XatUser
const user = new XatUser({
todo: {
w_userno: 123456,
w_k1: 'deadbeef777',
w_name: 'nickname',
w_avatar: '42',
w_useroom: 123,
},
})
user.addExtension('user-actions')
user.on('data', (data) => {
console.log(data)
if (data.done !== undefined) {
user.sendTextMessage('hello!')
}
}).on('send', (data) => {
console.log(data.xml)
})
user.addExtension('/path/to/ext.js') // .js is significant
require('/path/to/another/ext').bind(user)
Core implements basic behavior for interaction with xat. Core designed to be flexible and fast-patchable.
Flexible: you can easily customize core (see more below).
Fast-patchable: sometimes xat may update it's protocol. Futhermore, new version of protocol may be incompatible with previous. Core can be easily patched, for some reasons. Pray the EcmaScript compatibility/portability!
Details of implemented behavior listed below.
When user connects to chat:
- First, client have to determine ip and port of a server.
- It have to send
<y>
packet with basic authentication info: user id, room, main-owner flag. - Server sends response to sent
<y>
packet. Response contains some information required to process authentication. - User responds to server's
<y>
packet with<j2>
packet.<j2>
packet contains quite full auth information, such as: nickname, avatar, homepage, k1, k3 (security keys), d4, d5,... (info about powers) and so on. - Server sends current state of chat: settings, last 20 messages, last active users.
So, core implements first, second and fourth steps.
By default, xat-client uses tcp-socket, provided by node's net.connect. You can use your own, in case to use TOR/http proxy/something else.
A long time ago, servers ip addresses was hardcoded in xat's client. Now, it download ips (or even domain naimes) from external address (information is actual at 16 March 2016). Actual scheme implemented in file src/core/ippick.js. You can use your own scheme (for example, if xat change ip picking scheme or if you would like to use xat-client for ixat server.
<j2>
packet contains all user's authenication data. Client should send <j2>
in order to pass authentication on server.
Original xat's client preprocesses content of chat.sol and form <j2>
.
Format of <j2>
packet is not strict, but sensitive in some cases (especially when we form <j2>
packet for registered/subscriber user). See more.
The process described above is customizable.
todo
and global
objects are similar to swf client's ones. This section contains as much options as possible, even if they are not implemented in xat-client or event should be ignored.
todo
: complex object, describing user. All values below haveString
type.w_userno
: idw_name
: nicknamew_avatar
: avatar. Possible values, acceptable by swf clients: number from [0, 1758], link started with http:// or https://, without 'xat' substring and ended by .jpg, .png or .jpeg., smile code (example: "(cool)").w_homepage
: homepage. Swf client ignores homepage if it's length < 6.w_registered
: user's account name,if user registered, otherwise null/undefined.w_userrev
: revision, or version of user's profile. In the xat swf client, initially,w_userrev
is 0, and increments every name/avatar/homepage. Actually, you may ignore this parameter. Moreover, you even may pass arbitrary string tow_userrev
, server only filters dangerous characters from it.Macros
:w_pool
: prefered pool.pass
: main owner's password from chat.php?id=chatid&pw=passw_autologin
: bitmask of profile dialog box checkboxes.w_k1
: primary auth key (hex value).w_k2
: web auth key, used for auth in web interactions such as: creating new account, buying xats etc.w_k3
: registered auth key.w_xats
: amount of xats.w_Powers
: which powers does user have (array of masks). If we concat every number of this array in a single binary number, then set N'th bit means user have power with number N. Example: [0x00000001, 0x0, ..., 0x0] means that user has power (topman).w_PowerO
: how much powers of each type does user have. Format: "1=105|2=10|3=|4=4|7=2". Explaination: this user has powers with id 1, 2, 3, 4, 7. It has 105 copies of 1, 10 copies of 2, 1 copy of 3, 4 copies of 4 and 2 copies of 7. You should specify all powers user has. You may omit amount, if amount is 1.w_d0
: bitmask with some settings. You can't affect on this settings by changing d0 manually.- +1: adds shadow to avatar. Shadow appears once user has edited it's xat.me page.
- +2^21: user is celebrity (cyan pawn).
- +2^24: user has gifts.
w_d1
: when your days are gone (UNIX timestamp)w_d2
: BFF/bride id.w_d3
: unknownw_sn
: unknownw_cb
: unknownw_dt
: last login time (UNIX timestamp).
global
:ippicker
: object which choose ip (hostname) and port for client connection. Object should have methodpickIp
, that takes client and returning thenable promise. On success, promise should be resolved with{ ip: ipaddress, port: port }
. Default: picker for xat.com.createSocket
: function creates socket for connection. Default: function creates nodejs net.Socket.perlinNoise
: function receives perlin noise parameters, sent by server, and returns perlin noise value.
connect
: connects client.send
: send packet to server. Takes string or buffer which should be sent. If string taken, it sent with zero byte at the end (according to XMLSocket protocol). If buffer taken, it sent without changes.end
: disconnects client.
connect
: raised when inner socket connected to server.close
:error
.data
: raised when data from server received. Passes parsed xml to listeners. Example: if<m t="hello, world!' u="42" />
received, then{ m: { attributes: { t: "Hello, world!", u: "42" }}}
will be passed.send
: raised when something was sent by client.captcha
: raised when client received captcha request.
xat-client mixin modifies lightweight plain xat-client with additional functionality. For instance, "user-actions" mixin adds methods sendTextMessage
, sendPCMessage
and others to xat-client object.
Every mixin should be representet as node.js module and exports method bind(client)
.
Example of mixin:
module.exports.bind = (client) => {
client.on('data', (data) => {
if (data.m && data.m.attributes.t === 'bang bang!') {
console.error("I'm calling the police!")
}
})
}
To apply mixin to client instance, pass client to bind method of mixin's package.
require('/path/to/my/custom/mixin.js').bind(client)
Adds several methods for interaction with chat.
sendTextMessage
: sends message to main chat.message
options
:asLink
: when user sends link via swl client, client adds 'l' attribute to<m>
, which prevents message from storing. This options adds 'l' attribute as well. Default:false
.
sendPMMessage
: sends private message to user.options
message
receiver
asLocal
: sends<p>
message instead of<z>
.<p>
message reach it's destination only if sender and receiver are in same chat. Probably, it's easier for server to route<p>
packet rather than<z>
, but in case sender and receiver are in same chat, both packages routes in the same way. Default:false
.
sendPCMessage
:options
: same as for sendPMMessage
sendLocate
: sends locate (/l) request to user.userno
: destination.
sendResponseToLocate
: sends at (/a) message, that usually responds to locate.userno
: destination.options
: note: nofollow option is stronger that isFriend. It means, if you set nofollow to true, no matter what is isFriend.isFriend
: should response reveal client's location to requester. Default:false
.nofollow
: should response state that client has activated (nofollow) power. Works even without power. Default:false
.
sendGetFriendStatus
: sends list of friends whose status (online/offline) we want to know.friends
: list of friend's ids.
makeUser
:makeGuest
: makes user specified by id a guest.userno
: id
makeMember
:userno
makeModerator
:makeOwner
:gagUser
:banUser
: ban user.options
:userno
: id of user.duration
: duration of ban in seconds. 0 for forever ban.reason
puzzle
: id of ban game. Default: no puzzle.
muteUser
:unbanUser
: unban user.userno
kickUser
: kicks user.userno
reason
sendKeepAlive
: sends /KEEPALIVE message. Learn more.setPool
: sets pool. Sends<wPOOLID />
packet.poolId
: id of pool from<w 0 0 1 2 ... />
packet.
sendK2
:
Prevents user from being kicked because of inactivity.
According to xat protocol, every active user sends <c t="/KEEPALIVE" />
packet sometimes.
This mixin forces client to send KEEPALIVE periodically.
- interval - how often client sends KEEPALIVE. Default: 9 minutes. That's enougth.
Makes client emitting more high-level events compared to core data
event.
Makes client responding to locate requests.
nofollow
: if true, user responds NOFOLLOW to any locate request. Default: false.isFriend
: function decides is sender friend or not. Takes client, sender's userno, packet. Returns Boolean. Default: function returns false.
This section contains knowledge about protocol. First subsection contains events, actions and corresponding behavior. Second section contains packet descriptions.
Initiated when client set "f" attribute of <j2>
to 1.
Client receives all data like in sign in case, but receives <logout e="E12" />
instead of <done />
.
Other clients don't receive <u>
, but receive <l>
Locate procedure used by clients source to fetch other client's (target) data.
Locate initiated when:
- Source clicks on target.
- Target sends PM/PC message and source's user list doesn't contain target.
Source sends locate packet:
<z u="sourceid" d="targetid" t="/l" />
Target receives this packet and sends at packet.
<z u="targetid" d ="sourceid" t="{response}"/>
- /a_: when source is not friend of target.
- /a: when source is friend of target.
- /a_NF: when target activated (nofollow)
Then source receives packet:
<z u="targetid" d ="sourceid" t="/a{location}" n="target_name" a="target_avatar" ... />
At packet contains target's profile data: w_name
, w_avatar
, w_homepage
, w_registered
, w_d0
, w_Powers
, w_PowerO
, q
location can have three values:
- {chat-url}: when sourse is friend of target, location contains url of chat, where target was active the last time.
- _: when source is not friend of target.
- _NF: when target activated (nofollow).
<y r="w_useroom" m="{pass !== undefined ? 1 : 0}" v="attempt" u="w_userno" [s="domain" p="port"] />
v
: number of attempt to connect, optionals
: seems to be ignored.p
: seems to be ignored.
<j2 Y="code" l5="perlinNoise" l4="jt3" l3="jt2" l2="jt1" q="onXat" y="" k="w_k1" k3="w_k3" p="w_pool" c="w_useroom" f="autologin" u="w_userno" n="w_name" a="w_avatar" h="w_homepage" v="w_userrev" b="GetGagTime" cb="w_cb" [e="1" sn="w_sn" N="w_registered" dt="w_dt" d0="w_d0" d1="w_d1" d2="w_d2" d3="w_d3" dx="w_xats" d0="w_PowerO" d[4-MAX_PWR_INDEX]="w_Powers[i-4]" />
perlinNoise
: see morecode
:jt1
: always 0jt2
: time passed between first attempt to connect and receiving<y>
jt3
: time passed between receiving<y>
and sending<j2>
GetGagTime
:autologin
: bitmask. Ifautologin
= 1, will not sign in automatically.- +1: set if user haven't set "Don't sign in automatically".
- +2: set if Login button pressed.
- +4: set if pass was defined in flashvars.
e
: ?
u
: optional, if specified in<y>
c
: optional, if specified in<y r="w_useroom">
f
: optionalv
: optionalk
: if not specified, chat sends invalid<i>
, sends<u u="0".. />
and<v e="60" />
<i b="{background-url};={related-group-name};={related-group-room};={language};={radio-url};={buttoncolor}" f="{FlagBits}" cb="{?}" [r="{rank}" B="{bot_userno}"] />
B
: id of bot, assigned to this group. Optional.r
: rank. Optional.
<gp p="group-powers-mask" gN1="settings of power N1" gN2="settings of power N2" />
- g74: (gline) list of smiles separated by commas:
smile1,smile2,..,smile12
- g80: (gcontrol) chat gcontrol settings distinct with default ones.
- g90: (bad) list of bad words separated by commas.
- g96: (winter) winter flix.
- g100: (link) list of keywords followed by bit.ly short links. e.g. "foo,1q2w3e,bar,2w3e4r". Example of bitly link: http://bit.ly/*1bdDlXc*
- g106: (gscol) code of color, appended to every smile in chat, e.g. "#clear"
- g112: (announce) welcome string.
- g114: (rankpool)
m
: main pool namet
: private pool namernk
: private pool required rank. Type:Rank.No
b
: ban pool name (banpool). Type:String
brk
: ban pool required rank of not banned user. Type:Rank.No
v
: ?
- g180: (gsound) names of corresponding sounds
m
: sound when someone sends message.d
: user sign in soundt
: tab sound
- g188: (doodlerace)
st
: ?o
: ?v
: ?
- game: (g192), (g246), (g256) (g238). All possible fields listed below. Some games don't have some of this fields. Fields with default value are omited.
rnk
: required rank to start game. Type:Rank.No
dt
: default play time. Type:Seconds
.lv
: default level. Type:Integer
.rt
: default result display time. Type:Seconds
.rc
: game type - race (not sure). Value: 1tg
: target scorepz
: prize (rank).v
: ?
- g252: (redirect)
id
: groupname of locationv
: ?
<l u="w_userno" />
<m [u="{w_userno}_{w_userrev}"] t="message" [l="1"]/>
<m u="w_userno" d="w_userno" t="/m" p="rank" />
<logout e="error-code" />
- E03: incorrect
<j2 y="value" />
/ userno or room wasn't specified both in<y>
and<j2>
- E07:
<j2 k="w_k1" />
omitted /<j2 k="w_k1" u="w_userno" />
incorrect (with<v e="60" />
) - E12: don't sign me in automatically
- E25: incorrect
<j2 l5="{perlin-noise}" k3="w_k3" />
- E36-38: protect activated
<idle e="error-code" />
- I04: guest user idle. Comes after 1 minune from connection
- I01: regular user idle.