summaryrefslogtreecommitdiff
path: root/xmpp/pylon.lua
blob: e8f6e9e4215dcf6cccd740af65d766b730637a51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
local socket = require'cqueues.socket'
local class = require'r.class'

local Channel = require'channel'
local BasePylon = require 'pylon'
local xml = require'xmpp.xml'
local X,xmlify = xml.X, xml.xmlify
local base64 = require'xmpp.base64'
local sha1 = require'xmpp.sha1'

local Xmpp = class.extend(BasePylon)

function Xmpp.init(self)
	self:_check_fields"server component component_secret"

	self.mucs = {}
end

-- only use on local connections!
function Xmpp._connect(self)
	local sock = assert(socket.connect(self.server, 5347))
	self.sock = sock
	sock:setmode('bn','bn')
	-- yes, our component name goes in the 'to' field. don't ask me why
	local start = ([[<stream:stream to='%s' xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams'>]]):format(self.component)

	-- state of the art xml parser
	local function check_and_send(test, text)
		local x = sock:read('-2048')
		assert(x:find(test))
		if text then sock:write(text) end
	end

	sock:write(start)
	local streamhead = sock:read('-2048')
	assert(streamhead:find'accept')
	local streamid = streamhead:match"id='(.-)'"
	sock:write(xmlify(X.handshake{sha1.sha1(streamid..self.component_secret)}))
	check_and_send('<handshake/>',nil)

	for busname, bus in pairs(self.wilson.busses) do
		for _, channel in ipairs(bus) do
			if channel.pylon == self then
				self:add_channel(channel)
			end
		end
	end

	return sock
end

function Xmpp.add_channel(self, channel)
	local muc = { nick_to_user={}, user_to_nick={} }
	self.mucs[channel.descriptor] = muc
	local bot = {} ; self.bot = bot
	bot.user, bot.nick, bot.user_jid, bot.nick_jid = self:ensure_and_get_user(channel.descriptor, "wilson")
end

function Xmpp.ensure_and_get_user(self, muc_jid, nick)
	local muc = assert(self.mucs[muc_jid])
	local user = muc.nick_to_user[nick]
	if user then
		local user_jid = user..'@'..self.component
		local nick_jid = muc_jid..'/'..nick
		return user, nick, user_jid, nick_jid
	end

	-- join up new user

	user = nick:gsub("[^a-zA-Z0-9%.]","."):match("^%.*(.-)%.*$")
	while muc.user_to_nick[user] do user = user..'-' end
	muc.nick_to_user[nick] = user
	muc.user_to_nick[user] = nick

	local user_jid = user..'@'..self.component
	local nick_jid = muc_jid..'/'..nick

	self.log("Joining up new user "..user_jid.." to "..nick_jid)

	self.sock:write(xmlify(
		X.presence{from=user_jid, to=nick_jid,
			X.x{xmlns='http://jabber.org/protocol/muc',
				X.history{maxstanzas='0'}}}))

	return user, nick, user_jid, nick_jid
end

function Xmpp.recving(self)
	local function getmore()
		local function t(...)
			-- pprint(...)
			return ...
		end
		return t(self.sock:read'-2048')
	end
	for x in xml.stanzae(getmore) do
		self.log:debug(xmlify(x))
		local body = x'body' and x'body'[1]
		if x.label == 'message' then
			local from_bare_jid, from_nick
			if x.xarg.from then from_bare_jid, from_nick = x.xarg.from:match"(.*)/(.*)" end
			local muc = self.mucs[from_bare_jid] ; if muc then
				if
					x.xarg.to == 'wilson@'..self.component
					and body
					and from_nick and not muc.nick_to_user[from_nick]
				then
					self.log:proto('<',from_bare_jid,from_nick,body)
					self.wilson:deliver(Channel(self, from_bare_jid), {
						body = body,
						sender = '[x]'..from_nick
					})
				end
			end
		end
	end
end

function Xmpp.sending(self)
	for dest_channel, message in self.inbox:iter() do
		self.log:proto('>',dest_channel,message.sender,message.body)
		local user, nick, user_jid, nick_jid = self:ensure_and_get_user(dest_channel.descriptor, message.sender)
		self.sock:write(xmlify(
			X.message{to=dest_channel.descriptor, type='groupchat', from=user_jid,
				X.body{message.body}}))
	end
end

return Xmpp