author | Kim Alvefur <zash@zash.se> |
Fri, 04 Sep 2015 00:23:46 +0200 | |
changeset 1831 | 9376e870f0e1 |
parent 983 | ac9bf3fcbcfe |
child 2891 | 65082d91950e |
permissions | -rw-r--r-- |
249 | 1 |
-- for Prosody |
2 |
-- via dersd |
|
3 |
||
4 |
if module:get_host_type() ~= "component" then |
|
5 |
error(module.name.." should be loaded as a component, check out http://prosody.im/doc/components", 0); |
|
6 |
end |
|
7 |
||
8 |
local jid_split = require "util.jid".split; |
|
9 |
local st = require "util.stanza"; |
|
10 |
local componentmanager = require "core.componentmanager"; |
|
11 |
local datamanager = require "util.datamanager"; |
|
12 |
local timer = require "util.timer"; |
|
13 |
local http = require "net.http"; |
|
790
4f9cd19c4658
mod_twitter: fixed to depend on Prosody's internal util.json. TODO: Discuss (MattJ, Zash, Waqas, Maranda) about migration all json content to use cjson ( http://www.kyne.com.au/~mark/software/lua-cjson-manual.html ) library instead
Vadim Misbakh-Soloviov <mva@mva.name>
parents:
249
diff
changeset
|
14 |
local json = require "util.json"; |
249 | 15 |
local base64 = require "util.encodings".base64; |
16 |
||
17 |
local component_host = module:get_host(); |
|
18 |
local component_name = module.name; |
|
19 |
local data_cache = {}; |
|
20 |
||
21 |
function print_r(obj) |
|
22 |
return require("util.serialization").serialize(obj); |
|
23 |
end |
|
24 |
||
25 |
function dmsg(jid, msg) |
|
26 |
module:log("debug", msg or "nil"); |
|
27 |
if jid ~= nil then |
|
803 | 28 |
module:send(st.message({to=jid, from=component_host, type='chat'}):tag("body"):text(msg or "nil"):up()); |
249 | 29 |
end |
30 |
end |
|
31 |
||
32 |
function substring(string, start_string, ending_string) |
|
33 |
local s_value_start, s_value_finish = nil, nil; |
|
34 |
if start_string ~= nil then |
|
35 |
_, s_value_start = string:find(start_string); |
|
36 |
if s_value_start == nil then |
|
37 |
-- error |
|
38 |
return nil; |
|
39 |
end |
|
40 |
else |
|
41 |
return nil; |
|
42 |
end |
|
43 |
if ending_string ~= nil then |
|
44 |
_, s_value_finish = string:find(ending_string, s_value_start+1); |
|
45 |
if s_value_finish == nil then |
|
46 |
-- error |
|
47 |
return nil; |
|
48 |
end |
|
49 |
else |
|
50 |
s_value_finish = string:len()+1; |
|
51 |
end |
|
52 |
return string:sub(s_value_start+1, s_value_finish-1); |
|
53 |
end |
|
54 |
||
55 |
local http_timeout = 30; |
|
56 |
local http_queue = setmetatable({}, { __mode = "k" }); -- auto-cleaning nil elements |
|
57 |
data_cache['prosody_os'] = prosody.platform; |
|
58 |
data_cache['prosody_version'] = prosody.version; |
|
59 |
local http_headers = { |
|
60 |
["User-Agent"] = "Prosody ("..data_cache['prosody_version'].."; "..data_cache['prosody_os']..")" --"ELinks (0.4pre5; Linux 2.4.27 i686; 80x25)", |
|
61 |
}; |
|
62 |
||
63 |
function http_action_callback(response, code, request, xcallback) |
|
64 |
if http_queue == nil or http_queue[request] == nil then return; end |
|
65 |
local id = http_queue[request]; |
|
66 |
http_queue[request] = nil; |
|
67 |
if xcallback == nil then |
|
68 |
dmsg(nil, "http_action_callback reports that xcallback is nil"); |
|
69 |
else |
|
70 |
xcallback(id, response, request); |
|
71 |
end |
|
72 |
return true; |
|
73 |
end |
|
74 |
||
75 |
function http_add_action(tid, url, method, post, fcallback) |
|
983
ac9bf3fcbcfe
mod_pubsub_feeds, mod_sms_clickatell, mod_twitter: Update for net.http API change in prosody:e3b9dc9dd940
Matthew Wild <mwild1@gmail.com>
parents:
803
diff
changeset
|
76 |
local request = http.request(url, { headers = http_headers or {}, body = http.formencode(post or {}), method = method or "GET" }, function(response_body, code, response, request) http_action_callback(response_body, code, request, fcallback) end); |
249 | 77 |
http_queue[request] = tid; |
78 |
timer.add_task(http_timeout, function() http.destroy_request(request); end); |
|
79 |
return true; |
|
80 |
end |
|
81 |
||
82 |
local users = setmetatable({}, {__mode="k"}); |
|
83 |
local user = {}; |
|
84 |
user.__index = user; |
|
85 |
user.dosync = false; |
|
86 |
user.valid = false; |
|
87 |
user.data = {}; |
|
88 |
||
89 |
function user:login() |
|
90 |
userdata = datamanager.load(self.jid, component_host, "data"); |
|
91 |
if userdata ~= nil then |
|
92 |
self.data = userdata; |
|
93 |
if self.data['_twitter_sess'] ~= nil then |
|
94 |
http_headers['Cookie'] = "_twitter_sess="..self.data['_twitter_sess']..";"; |
|
95 |
end |
|
803 | 96 |
module:send(st.presence({to=self.jid, from=component_host})); |
249 | 97 |
self:twitterAction("VerifyCredentials"); |
98 |
if self.data.dosync == 1 then |
|
99 |
self.dosync = true; |
|
100 |
timer.add_task(self.data.refreshrate, function() return users[self.jid]:sync(); end) |
|
101 |
end |
|
102 |
else |
|
803 | 103 |
module:send(st.message({to=self.jid, from=component_host, type='chat'}):tag("body"):text("You are not signed in.")); |
249 | 104 |
end |
105 |
end |
|
106 |
||
107 |
function user:logout() |
|
108 |
datamanager.store(self.jid, component_host, "data", self.data); |
|
109 |
self.dosync = false; |
|
803 | 110 |
module:send(st.presence({to=self.jid, from=component_host, type='unavailable'})); |
249 | 111 |
end |
112 |
||
113 |
function user:sync() |
|
114 |
if self.dosync then |
|
115 |
table.foreach(self.data.synclines, function(ind, line) self:twitterAction(line.name, {sinceid=line.sinceid}) end); |
|
116 |
return self.data.refreshrate; |
|
117 |
end |
|
118 |
end |
|
119 |
||
120 |
function user:signin() |
|
121 |
if datamanager.load(self.jid, component_host, "data") == nil then |
|
122 |
datamanager.store(self.jid, component_host, "data", {login=self.data.login, password=self.data.password, refreshrate=60, dosync=1, synclines={{name='HomeTimeline', sinceid=0}}, syncstatus=0}) |
|
803 | 123 |
module:send(st.presence{to=self.jid, from=component_host, type='subscribe'}); |
124 |
module:send(st.presence{to=self.jid, from=component_host, type='subscribed'}); |
|
249 | 125 |
end |
126 |
end |
|
127 |
||
128 |
function user:signout() |
|
129 |
if datamanager.load(self.jid, component_host, "data") ~= nil then |
|
130 |
datamanager.store(self.jid, component_host, "data", nil); |
|
803 | 131 |
module:send(st.presence({to=self.jid, from=component_host, type='unavailable'})); |
132 |
module:send(st.presence({to=self.jid, from=component_host, type='unsubscribe'})); |
|
133 |
module:send(st.presence({to=self.jid, from=component_host, type='unsubscribed'})); |
|
249 | 134 |
end |
135 |
end |
|
136 |
||
137 |
local twitterApiUrl = "http://api.twitter.com"; |
|
138 |
local twitterApiVersion = "1"; |
|
139 |
local twitterApiDataType = "json"; |
|
140 |
local twitterActionUrl = function(action) return twitterApiUrl.."/"..twitterApiVersion.."/"..action.."."..twitterApiDataType end; |
|
141 |
local twitterActionMap = { |
|
142 |
PublicTimeline = { |
|
143 |
url = twitterActionUrl("statuses/public_timeline"), |
|
144 |
method = "GET", |
|
145 |
needauth = false, |
|
146 |
}, |
|
147 |
HomeTimeline = { |
|
148 |
url = twitterActionUrl("statuses/home_timeline"), |
|
149 |
method = "GET", |
|
150 |
needauth = true, |
|
151 |
}, |
|
152 |
FriendsTimeline = { |
|
153 |
url = twitterActionUrl("statuses/friends_timeline"), |
|
154 |
method = "GET", |
|
155 |
needauth = true, |
|
156 |
}, |
|
157 |
UserTimeline = { |
|
158 |
url = twitterActionUrl("statuses/friends_timeline"), |
|
159 |
method = "GET", |
|
160 |
needauth = true, |
|
161 |
}, |
|
162 |
VerifyCredentials = { |
|
163 |
url = twitterActionUrl("account/verify_credentials"), |
|
164 |
method = "GET", |
|
165 |
needauth = true, |
|
166 |
}, |
|
167 |
UpdateStatus = { |
|
168 |
url = twitterActionUrl("statuses/update"), |
|
169 |
method = "POST", |
|
170 |
needauth = true, |
|
171 |
}, |
|
172 |
Retweet = { |
|
173 |
url = twitterActionUrl("statuses/retweet/%tweetid"), |
|
174 |
method = "POST", |
|
175 |
needauth = true, |
|
176 |
} |
|
177 |
} |
|
178 |
||
179 |
function user:twitterAction(line, params) |
|
180 |
local action = twitterActionMap[line]; |
|
181 |
if action then |
|
182 |
local url = action.url; |
|
183 |
local post = {}; |
|
184 |
--if action.needauth and not self.valid and line ~= "VerifyCredentials" then |
|
185 |
-- return |
|
186 |
--end |
|
187 |
if action.needauth then |
|
188 |
http_headers['Authorization'] = "Basic "..base64.encode(self.data.login..":"..self.data.password); |
|
189 |
--url = string.gsub(url, "http\:\/\/", string.format("http://%s:%s@", self.data.login, self.data.password)); |
|
190 |
end |
|
191 |
if params and type(params) == "table" then |
|
192 |
post = params; |
|
193 |
end |
|
194 |
if action.method == "GET" and post ~= {} then |
|
195 |
url = url.."?"..http.formencode(post); |
|
196 |
end |
|
197 |
http_add_action(line, url, action.method, post, function(...) self:twitterActionResult(...) end); |
|
198 |
else |
|
803 | 199 |
module:send(st.message({to=self.jid, from=component_host, type='chat'}):tag("body"):text("Wrong twitter action!"):up()); |
249 | 200 |
end |
201 |
end |
|
202 |
||
203 |
local twitterActionResultMap = { |
|
204 |
PublicTimeline = {exec=function(jid, response) |
|
803 | 205 |
--module:send(st.message({to=jid, from=component_host, type='chat'}):tag("body"):text(print_r(response)):up()); |
249 | 206 |
return |
207 |
end}, |
|
208 |
HomeTimeline = {exec=function(jid, response) |
|
803 | 209 |
--module:send(st.message({to=jid, from=component_host, type='chat'}):tag("body"):text(print_r(response)):up()); |
249 | 210 |
return |
211 |
end}, |
|
212 |
FriendsTimeline = {function(jid, response) |
|
213 |
return |
|
214 |
end}, |
|
215 |
UserTimeline = {exec=function(jid, response) |
|
216 |
return |
|
217 |
end}, |
|
218 |
VerifyCredentials = {exec=function(jid, response) |
|
219 |
if response ~= nil and response.id ~= nil then |
|
220 |
users[jid].valid = true; |
|
221 |
users[jid].id = response.id; |
|
222 |
end |
|
223 |
return |
|
224 |
end}, |
|
225 |
UpdateStatus = {exec=function(jid, response) |
|
226 |
return |
|
227 |
end}, |
|
228 |
Retweet = {exec=function(jid, response) |
|
229 |
return |
|
230 |
end} |
|
231 |
} |
|
232 |
||
233 |
function user:twitterActionResult(id, response, request) |
|
234 |
if request ~= nil and request.responseheaders['set-cookie'] ~= nil and request.responseheaders['location'] ~= nil then |
|
235 |
--self.data['_twitter_sess'] = substring(request.responseheaders['set-cookie'], "_twitter_sess=", ";"); |
|
236 |
--http_add_action(id, request.responseheaders['location'], "GET", {}, function(...) self:twitterActionResult(...) end); |
|
237 |
return true; |
|
238 |
end |
|
239 |
local result, tmp_json = pcall(function() json.decode(response or "{}") end); |
|
240 |
if result and id ~= nil then |
|
241 |
twitterActionResultMap[id]:exec(self.jid, tmp_json); |
|
242 |
end |
|
243 |
return true; |
|
244 |
end |
|
245 |
||
246 |
function iq_success(event) |
|
247 |
local origin, stanza = event.origin, event.stanza; |
|
248 |
local reply = data_cache.success; |
|
249 |
if reply == nil then |
|
250 |
reply = st.iq({type='result', from=stanza.attr.to or component_host}); |
|
251 |
data_cache.success = reply; |
|
252 |
end |
|
253 |
reply.attr.id = stanza.attr.id; |
|
254 |
reply.attr.to = stanza.attr.from; |
|
255 |
origin.send(reply); |
|
256 |
return true; |
|
257 |
end |
|
258 |
||
259 |
function iq_disco_info(event) |
|
260 |
local origin, stanza = event.origin, event.stanza; |
|
261 |
local from = {}; |
|
262 |
from.node, from.host, from.resource = jid_split(stanza.attr.from); |
|
263 |
local bjid = from.node.."@"..from.host; |
|
264 |
local reply = data_cache.disco_info; |
|
265 |
if reply == nil then |
|
266 |
reply = st.iq({type='result', from=stanza.attr.to or component_host}):query("http://jabber.org/protocol/disco#info") |
|
267 |
:tag("identity", {category='gateway', type='chat', name=component_name}):up(); |
|
268 |
reply = reply:tag("feature", {var="urn:xmpp:receipts"}):up(); |
|
269 |
reply = reply:tag("feature", {var="http://jabber.org/protocol/commands"}):up(); |
|
270 |
reply = reply:tag("feature", {var="jabber:iq:register"}):up(); |
|
271 |
--reply = reply:tag("feature", {var="jabber:iq:time"}):up(); |
|
272 |
--reply = reply:tag("feature", {var="jabber:iq:version"}):up(); |
|
273 |
--reply = reply:tag("feature", {var="http://jabber.org/protocol/stats"}):up(); |
|
274 |
data_cache.disco_info = reply; |
|
275 |
end |
|
276 |
reply.attr.id = stanza.attr.id; |
|
277 |
reply.attr.to = stanza.attr.from; |
|
278 |
origin.send(reply); |
|
279 |
return true; |
|
280 |
end |
|
281 |
||
282 |
function iq_disco_items(event) |
|
283 |
local origin, stanza = event.origin, event.stanza; |
|
284 |
local reply = data_cache.disco_items; |
|
285 |
if reply == nil then |
|
286 |
reply = st.iq({type='result', from=stanza.attr.to or component_host}):query("http://jabber.org/protocol/disco#items"); |
|
287 |
data_cache.disco_items = reply; |
|
288 |
end |
|
289 |
reply.attr.id = stanza.attr.id; |
|
290 |
reply.attr.to = stanza.attr.from; |
|
291 |
origin.send(reply); |
|
292 |
return true; |
|
293 |
end |
|
294 |
||
295 |
function iq_register(event) |
|
296 |
local origin, stanza = event.origin, event.stanza; |
|
297 |
if stanza.attr.type == "get" then |
|
298 |
local reply = data_cache.registration_form; |
|
299 |
if reply == nil then |
|
300 |
reply = st.iq({type='result', from=stanza.attr.to or component_host}) |
|
301 |
:tag("query", { xmlns="jabber:iq:register" }) |
|
302 |
:tag("instructions"):text("Enter your twitter data"):up() |
|
303 |
:tag("username"):up() |
|
304 |
:tag("password"):up(); |
|
305 |
data_cache.registration_form = reply |
|
306 |
end |
|
307 |
reply.attr.id = stanza.attr.id; |
|
308 |
reply.attr.to = stanza.attr.from; |
|
309 |
origin.send(reply); |
|
310 |
elseif stanza.attr.type == "set" then |
|
311 |
local from = {}; |
|
312 |
from.node, from.host, from.resource = jid_split(stanza.attr.from); |
|
313 |
local bjid = from.node.."@"..from.host; |
|
314 |
local username, password = "", ""; |
|
315 |
local reply; |
|
316 |
for _, tag in ipairs(stanza.tags[1].tags) do |
|
317 |
if tag.name == "remove" then |
|
318 |
users[bjid]:signout(); |
|
319 |
iq_success(event); |
|
320 |
return true; |
|
321 |
end |
|
322 |
if tag.name == "username" then |
|
323 |
username = tag[1]; |
|
324 |
end |
|
325 |
if tag.name == "password" then |
|
326 |
password = tag[1]; |
|
327 |
end |
|
328 |
end |
|
329 |
if username ~= nil and password ~= nil then |
|
330 |
users[bjid] = setmetatable({}, user); |
|
331 |
users[bjid].jid = bjid; |
|
332 |
users[bjid].data.login = username; |
|
333 |
users[bjid].data.password = password; |
|
334 |
users[bjid]:signin(); |
|
335 |
users[bjid]:login(); |
|
336 |
end |
|
337 |
iq_success(event); |
|
338 |
return true; |
|
339 |
end |
|
340 |
end |
|
341 |
||
342 |
function presence_stanza_handler(event) |
|
343 |
local origin, stanza = event.origin, event.stanza; |
|
344 |
local to = {}; |
|
345 |
local from = {}; |
|
346 |
local pres = {}; |
|
347 |
to.node, to.host, to.resource = jid_split(stanza.attr.to); |
|
348 |
from.node, from.host, from.resource = jid_split(stanza.attr.from); |
|
349 |
pres.type = stanza.attr.type; |
|
350 |
for _, tag in ipairs(stanza.tags) do pres[tag.name] = tag[1]; end |
|
351 |
local from_bjid = nil; |
|
352 |
if from.node ~= nil and from.host ~= nil then |
|
353 |
from_bjid = from.node.."@"..from.host; |
|
354 |
elseif from.host ~= nil then |
|
355 |
from_bjid = from.host; |
|
356 |
end |
|
357 |
if pres.type == nil then |
|
358 |
if users[from_bjid] ~= nil then |
|
359 |
-- Status change |
|
360 |
if pres['status'] ~= nil and users[from_bjid]['data']['sync_status'] then |
|
361 |
users[from_bjid]:twitterAction("UpdateStatus", {status=pres['status']}); |
|
362 |
end |
|
363 |
else |
|
364 |
-- User login request |
|
365 |
users[from_bjid] = setmetatable({}, user); |
|
366 |
users[from_bjid].jid = from_bjid; |
|
367 |
users[from_bjid]:login(); |
|
368 |
end |
|
369 |
origin.send(st.presence({to=from_bjid, from=component_host})); |
|
370 |
elseif pres.type == 'subscribe' and users[from_bjid] ~= nil then |
|
371 |
origin.send(st.presence{to=from_bjid, from=component_host, type='subscribed'}); |
|
372 |
elseif pres.type == 'unsubscribed' and users[from_bjid] ~= nil then |
|
373 |
users[from_bjid]:logout(); |
|
374 |
users[from_bjid]:signout(); |
|
375 |
users[from_bjid] = nil; |
|
376 |
elseif pres.type == 'unavailable' and users[from_bjid] ~= nil then |
|
377 |
users[from_bjid]:logout(); |
|
378 |
users[from_bjid] = nil; |
|
379 |
end |
|
380 |
return true; |
|
381 |
end |
|
382 |
||
383 |
function confirm_message_delivery(event) |
|
384 |
local reply = st.message({id=event.stanza.attr.id, to=event.stanza.attr.from, from=event.stanza.attr.to or component_host}):tag("received", {xmlns = "urn:xmpp:receipts"}); |
|
385 |
origin.send(reply); |
|
386 |
return true; |
|
387 |
end |
|
388 |
||
389 |
function message_stanza_handler(event) |
|
390 |
local origin, stanza = event.origin, event.stanza; |
|
391 |
local to = {}; |
|
392 |
local from = {}; |
|
393 |
local msg = {}; |
|
394 |
to.node, to.host, to.resource = jid_split(stanza.attr.to); |
|
395 |
from.node, from.host, from.resource = jid_split(stanza.attr.from); |
|
396 |
local bjid = nil; |
|
397 |
if from.node ~= nil and from.host ~= nil then |
|
398 |
from_bjid = from.node.."@"..from.host; |
|
399 |
elseif from.host ~= nil then |
|
400 |
from_bjid = from.host; |
|
401 |
end |
|
402 |
local to_bjid = nil; |
|
403 |
if to.node ~= nil and to.host ~= nil then |
|
404 |
to_bjid = to.node.."@"..to.host; |
|
405 |
elseif to.host ~= nil then |
|
406 |
to_bjid = to.host; |
|
407 |
end |
|
408 |
for _, tag in ipairs(stanza.tags) do |
|
409 |
msg[tag.name] = tag[1]; |
|
410 |
if tag.attr.xmlns == "urn:xmpp:receipts" then |
|
411 |
confirm_message_delivery({origin=origin, stanza=stanza}); |
|
412 |
end |
|
413 |
-- can handle more xmlns |
|
414 |
end |
|
415 |
-- Now parse the message |
|
416 |
if stanza.attr.to == component_host then |
|
417 |
if msg.body == "!myinfo" then |
|
418 |
if users[from_bjid] ~= nil then |
|
419 |
origin.send(st.message({to=stanza.attr.from, from=component_host, type='chat'}):tag("body"):text(print_r(users[from_bjid])):up()); |
|
420 |
end |
|
421 |
end |
|
422 |
-- Other messages go to twitter |
|
423 |
user:twitterAction("UpdateStatus", {status=msg.body}); |
|
424 |
else |
|
425 |
-- Message to uid@host/resource |
|
426 |
end |
|
427 |
return true; |
|
428 |
end |
|
429 |
||
430 |
module:hook("presence/host", presence_stanza_handler); |
|
431 |
module:hook("message/host", message_stanza_handler); |
|
432 |
||
433 |
module:hook("iq/host/jabber:iq:register:query", iq_register); |
|
434 |
module:hook("iq/host/http://jabber.org/protocol/disco#info:query", iq_disco_info); |
|
435 |
module:hook("iq/host/http://jabber.org/protocol/disco#items:query", iq_disco_items); |
|
436 |
module:hook("iq/host", function(data) |
|
437 |
-- IQ to a local host recieved |
|
438 |
local origin, stanza = data.origin, data.stanza; |
|
439 |
if stanza.attr.type == "get" or stanza.attr.type == "set" then |
|
440 |
return module:fire_event("iq/host/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name, data); |
|
441 |
else |
|
442 |
module:fire_event("iq/host/"..stanza.attr.id, data); |
|
443 |
return true; |
|
444 |
end |
|
445 |
end); |
|
446 |
||
447 |
module.unload = function() |
|
448 |
componentmanager.deregister_component(component_host); |
|
449 |
end |
|
450 |
component = componentmanager.register_component(component_host, function() return; end); |