summaryrefslogtreecommitdiff
path: root/xmpp/pylon.lua
blob: 07568f93f14eb83d635bc9c73214539d54aef3f5 (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
local socket = require'cqueues.socket'
local class = require'r.class'

local Thing = require'thing'
local BasePylon = require 'pylon'
local Message = require 'message'
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.channels = {}
	self.pending_sent = {}
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)

	return sock
end

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

function Xmpp.ensure_and_get_user(self, muc_jid, nick, source_pylon)
	local muc = assert(self.channels[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("^%.*(.-)%.*$")
	if source_pylon then
		while muc.user_to_nick[user.."."..source_pylon.name] do user = user..'-' end
		user = user.."."..source_pylon.name
	else
		while muc.user_to_nick[user] do user = user..'-' end
	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))
		if type(x) == 'string' then
			self.log:warn("x was a string: ("..x..")")
			goto next
		end
		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 stanza_id
			for _,c in ipairs(x) do
				if c.label == 'stanza-id' and c.by == from_bare_jid
					then stanza_id = c.id end end
			assert(stanza_id)
			local sent_message = self.pending_sent[x.xarg.id]
			if sent_message then
				sent_message.pylon_ids[self] = stanza_id
				self.pending_sent[x.xarg.id] = nil
			else
				local muc = self.channels[from_bare_jid]
				local body = x'body' and x'body'[1]
				if
					x.xarg.to == 'wilson@'..self.component
					and muc
					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(Message {
						pylon = self,
						channel = Thing(self, from_bare_jid),
						author = Thing(self, x.xarg.from, from_nick),
						body = body,
						pylon_ids = { [self]=stanza_id },
					})
				end
			end
		end
		::next::
	end
end

function Xmpp.sending(self)
	for dest_channel, message in self.inbox:iter() do
		self.log:proto('>',dest_channel,message.author,message.body)
		local user, nick, user_jid, nick_jid = self:ensure_and_get_user(
				dest_channel.id, message.author:name_with_shortname(), message.pylon)
		local temp_id = tostring(math.random()) -- TODO: Better id generation method
		self.pending_sent[temp_id] = message
		self.sock:write(xmlify(
			X.message{to=dest_channel.id, type='groupchat', from=user_jid, id=temp_id,
				X.body{message.body}}))
	end
end

return Xmpp