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);
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";
14 local json
= require
"util.json";
15 local base64
= require
"util.encodings".base64
;
17 local component_host
= module
:get_host();
18 local component_name
= module
.name
;
19 local data_cache
= {};
22 return require("util.serialization").serialize(obj
);
25 function dmsg(jid
, msg
)
26 module
:log("debug", msg
or "nil");
28 module
:send(st
.message({to
=jid
, from
=component_host
, type='chat'}, msg
or "nil"));
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
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
50 s_value_finish
= string:len()+1;
52 return string:sub(s_value_start
+1, s_value_finish
-1);
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)",
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");
70 xcallback(id
, response
, request
);
75 function http_add_action(tid
, url
, method
, post
, fcallback
)
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);
77 http_queue
[request
] = tid
;
78 timer
.add_task(http_timeout
, function() http
.destroy_request(request
); end);
82 local users
= setmetatable({}, {__mode
="k"});
90 userdata = datamanager
.load(self
.jid
, component_host
, "data");
91 if userdata ~= nil then
93 if self
.data
['_twitter_sess'] ~= nil then
94 http_headers
['Cookie'] = "_twitter_sess="..self
.data
['_twitter_sess']..";";
96 module
:send(st
.presence({to
=self
.jid
, from
=component_host
}));
97 self
:twitterAction("VerifyCredentials");
98 if self
.data
.dosync
== 1 then
100 timer
.add_task(self
.data
.refreshrate
, function() return users
[self
.jid
]:sync(); end)
103 module
:send(st
.message({to
=self
.jid
, from
=component_host
, type='chat'}, "You are not signed in."));
107 function user
:logout()
108 datamanager
.store(self
.jid
, component_host
, "data", self
.data
);
110 module
:send(st
.presence({to
=self
.jid
, from
=component_host
, type='unavailable'}));
115 table.foreach(self
.data
.synclines
, function(ind
, line
) self
:twitterAction(line
.name
, {sinceid
=line
.sinceid
}) end);
116 return self
.data
.refreshrate
;
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})
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'});
128 function user
:signout()
129 if datamanager
.load(self
.jid
, component_host
, "data") ~= nil then
130 datamanager
.store(self
.jid
, component_host
, "data", nil);
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'}));
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
= {
143 url
= twitterActionUrl("statuses/public_timeline"),
148 url
= twitterActionUrl("statuses/home_timeline"),
153 url
= twitterActionUrl("statuses/friends_timeline"),
158 url
= twitterActionUrl("statuses/friends_timeline"),
162 VerifyCredentials
= {
163 url
= twitterActionUrl("account/verify_credentials"),
168 url
= twitterActionUrl("statuses/update"),
173 url
= twitterActionUrl("statuses/retweet/%tweetid"),
179 function user
:twitterAction(line
, params
)
180 local action
= twitterActionMap
[line
];
182 local url
= action
.url
;
184 --if action.needauth and not self.valid and line ~= "VerifyCredentials" then
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));
191 if params
and type(params
) == "table" then
194 if action
.method
== "GET" and post
~= {} then
195 url
= url
.."?"..http
.formencode(post
);
197 http_add_action(line
, url
, action
.method
, post
, function(...) self
:twitterActionResult(...) end);
199 module
:send(st
.message({to
=self
.jid
, from
=component_host
, type='chat'}, "Wrong twitter action!"));
203 local twitterActionResultMap
= {
204 PublicTimeline
= {exec
=function(jid
, response
)
205 --module:send(st.message({to=jid, from=component_host, type='chat'}, print_r(response)));
208 HomeTimeline
= {exec
=function(jid
, response
)
209 --module:send(st.message({to=jid, from=component_host, type='chat'}, print_r(response)));
212 FriendsTimeline
= {function(jid
, response
)
215 UserTimeline
= {exec
=function(jid
, response
)
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
;
225 UpdateStatus
= {exec
=function(jid
, response
)
228 Retweet
= {exec
=function(jid
, response
)
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);
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
);
246 function iq_success(event
)
247 local origin
, stanza
= event
.origin
, event
.stanza
;
248 local reply
= data_cache
.success
;
250 reply
= st
.iq({type='result', from
=stanza
.attr
.to
or component_host
});
251 data_cache
.success
= reply
;
253 reply
.attr
.id
= stanza
.attr
.id
;
254 reply
.attr
.to
= stanza
.attr
.from
;
259 function iq_disco_info(event
)
260 local origin
, stanza
= event
.origin
, event
.stanza
;
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
;
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
;
276 reply
.attr
.id
= stanza
.attr
.id
;
277 reply
.attr
.to
= stanza
.attr
.from
;
282 function iq_disco_items(event
)
283 local origin
, stanza
= event
.origin
, event
.stanza
;
284 local reply
= data_cache
.disco_items
;
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
;
289 reply
.attr
.id
= stanza
.attr
.id
;
290 reply
.attr
.to
= stanza
.attr
.from
;
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
;
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
307 reply
.attr
.id
= stanza
.attr
.id
;
308 reply
.attr
.to
= stanza
.attr
.from
;
310 elseif stanza
.attr
.type == "set" then
312 from
.node
, from
.host
, from
.resource
= jid_split(stanza
.attr
.from
);
313 local bjid
= from
.node
.."@"..from
.host
;
314 local username
, password
= "", "";
316 for _
, tag in ipairs(stanza
.tags
[1].tags
) do
317 if tag.name
== "remove" then
318 users
[bjid
]:signout();
322 if tag.name
== "username" then
325 if tag.name
== "password" then
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();
342 function presence_stanza_handler(event
)
343 local origin
, stanza
= event
.origin
, event
.stanza
;
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
;
357 if pres
.type == nil then
358 if users
[from_bjid
] ~= nil then
360 if pres
['status'] ~= nil and users
[from_bjid
]['data']['sync_status'] then
361 users
[from_bjid
]:twitterAction("UpdateStatus", {status
=pres
['status']});
364 -- User login request
365 users
[from_bjid
] = setmetatable({}, user
);
366 users
[from_bjid
].jid
= from_bjid
;
367 users
[from_bjid
]:login();
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;
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"});
389 function message_stanza_handler(event
)
390 local origin
, stanza
= event
.origin
, event
.stanza
;
394 to
.node
, to
.host
, to
.resource
= jid_split(stanza
.attr
.to
);
395 from
.node
, from
.host
, from
.resource
= jid_split(stanza
.attr
.from
);
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
;
403 if to
.node
~= nil and to
.host
~= nil then
404 to_bjid
= to
.node
.."@"..to
.host
;
405 elseif to
.host
~= nil then
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
});
413 -- can handle more xmlns
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'}, print_r(users
[from_bjid
])));
422 -- Other messages go to twitter
423 user
:twitterAction("UpdateStatus", {status
=msg
.body
});
425 -- Message to uid@host/resource
430 module
:hook("presence/host", presence_stanza_handler
);
431 module
:hook("message/host", message_stanza_handler
);
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
);
442 module
:fire_event("iq/host/"..stanza
.attr
.id
, data
);
447 module
.unload
= function()
448 componentmanager
.deregister_component(component_host
);
450 component
= componentmanager
.register_component(component_host
, function() return; end);