3 * centerim livejournal protocol handling class (sick)
4 * $Id: ljhook.cc,v 1.28 2004/11/11 13:42:05 konst Exp $
6 * Copyright (C) 2003-2004 by Konstantin Klyagin <k@thekonst.net>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
25 #include "icqcommon.h"
33 #include "eventmanager.h"
34 #include "icqcontacts.h"
36 #include <sys/utsname.h>
40 #define PERIOD_FRIENDS 3600
44 ljhook::ljhook(): abstracthook(livejournal
), fonline(false), sdest(0) {
45 fcapabs
.insert(hookcapab::nochat
);
52 manualstatus
= conf
->getstatus(proto
);
53 httpcli
.messageack
.connect(this, &ljhook::messageack_cb
);
54 httpcli
.socket
.connect(this, &ljhook::socket_cb
);
57 httpcli
.logger
.connect(this, &ljhook::logger_cb
);
59 journals
= vector
<string
>(1, conf
->getourid(proto
).nickname
);
62 void ljhook::connect() {
63 icqconf::imaccount acc
= conf
->getourid(proto
);
65 baseurl
= acc
.server
+ ":" + i2str(acc
.port
) + "/interface/flat";
66 md5pass
= getmd5(acc
.password
);
70 httpcli
.setProxyServerHost(conf
->gethttpproxyhost());
71 httpcli
.setProxyServerPort(conf
->gethttpproxyport());
73 if(!conf
->gethttpproxyuser().empty()) {
74 httpcli
.setProxyServerUser(conf
->gethttpproxyuser());
75 httpcli
.setProxyServerPasswd(conf
->gethttpproxypasswd());
78 HTTPRequestEvent
*ev
= new HTTPRequestEvent(baseurl
, HTTPRequestEvent::POST
);
79 ev
->addParam("mode", "login");
80 ev
->addParam("user", username
= acc
.nickname
);
81 ev
->addParam("hpassword", md5pass
);
82 ev
->addParam("getmoods", "1");
83 ev
->addParam("getpickws", "1");
88 if(!uname(&un
)) clientver
= un
.sysname
;
89 else clientver
= "GNU";
91 clientver
+= string("-") + PACKAGE
+ "/" + centerim::version
;
93 ev
->addParam("clientversion", clientver
);
95 self
= imcontact(username
+ "@lj", livejournal
);
100 httpcli
.SendEvent(ev
);
102 moods
= vector
<string
>(1, "");
103 pictures
= vector
<string
>(1, "");
106 void ljhook::disconnect() {
109 log(logDisconnected
);
112 icqcontact
*c
= clist
.get(self
);
113 if(c
) c
->setstatus(offline
);
118 void ljhook::exectimers() {
120 if(timer_current
-timer_getfriends
>= PERIOD_FRIENDS
) {
122 timer_getfriends
= timer_current
;
126 void ljhook::main() {
127 vector
<int>::iterator i
;
136 tv
.tv_sec
= tv
.tv_usec
= 0;
139 for(i
= rfds
.begin(); i
!= rfds
.end(); ++i
) {
141 hsock
= max(hsock
, *i
);
144 for(i
= wfds
.begin(); i
!= wfds
.end(); ++i
) {
146 hsock
= max(hsock
, *i
);
149 for(i
= efds
.begin(); i
!= efds
.end(); ++i
) {
151 hsock
= max(hsock
, *i
);
154 if(select(hsock
+1, &rs
, &ws
, &es
, &tv
) > 0) {
155 for(i
= rfds
.begin(); i
!= rfds
.end(); ++i
) {
156 if(FD_ISSET(*i
, &rs
)) {
157 httpcli
.socket_cb(*i
, SocketEvent::READ
);
162 for(i
= wfds
.begin(); i
!= wfds
.end(); ++i
) {
163 if(FD_ISSET(*i
, &ws
)) {
164 httpcli
.socket_cb(*i
, SocketEvent::WRITE
);
169 for(i
= efds
.begin(); i
!= efds
.end(); ++i
) {
170 if(FD_ISSET(*i
, &es
)) {
171 httpcli
.socket_cb(*i
, SocketEvent::EXCEPTION
);
178 void ljhook::getsockets(fd_set
&rs
, fd_set
&ws
, fd_set
&es
, int &hsocket
) const {
179 vector
<int>::const_iterator i
;
181 for(i
= rfds
.begin(); i
!= rfds
.end(); ++i
) {
182 hsocket
= max(hsocket
, *i
);
186 for(i
= wfds
.begin(); i
!= wfds
.end(); ++i
) {
187 hsocket
= max(hsocket
, *i
);
191 for(i
= efds
.begin(); i
!= efds
.end(); ++i
) {
192 hsocket
= max(hsocket
, *i
);
197 bool ljhook::isoursocket(fd_set
&rs
, fd_set
&ws
, fd_set
&es
) const {
198 vector
<int>::const_iterator i
;
200 for(i
= rfds
.begin(); i
!= rfds
.end(); ++i
)
201 if(FD_ISSET(*i
, &rs
))
204 for(i
= wfds
.begin(); i
!= wfds
.end(); ++i
)
205 if(FD_ISSET(*i
, &ws
))
208 for(i
= efds
.begin(); i
!= efds
.end(); ++i
)
209 if(FD_ISSET(*i
, &es
))
215 bool ljhook::online() const {
219 bool ljhook::logged() const {
220 return fonline
&& flogged
;
223 bool ljhook::isconnecting() const {
224 return fonline
&& !flogged
;
227 bool ljhook::enabled() const {
231 bool ljhook::send(const imevent
&asev
) {
232 if(!logged()) return false;
237 if(asev
.gettype() == imevent::xml
) sev
= asev
.getevent(); else {
238 sev
= new imxmlevent(asev
.getcontact(), asev
.getdirection(), asev
.gettext());
239 static_cast<imxmlevent
*>(sev
)->setfield("_eventkind", "posting");
243 if(sev
->gettype() == imevent::xml
) {
244 const imxmlevent
*m
= static_cast<const imxmlevent
*> (sev
);
245 HTTPRequestEvent
*ev
= 0;
247 if(m
->getfield("_eventkind") == "posting") {
248 ev
= new HTTPRequestEvent(baseurl
, HTTPRequestEvent::POST
);
250 ev
->addParam("mode", "postevent");
251 ev
->addParam("user", username
);
252 ev
->addParam("hpassword", md5pass
);
253 ev
->addParam("event", rusconv("ku", m
->gettext()));
254 ev
->addParam("ver", "1");
256 time_t t
= m
->gettimestamp();
257 struct tm
*tm
= localtime(&t
);
259 ev
->addParam("year", i2str(tm
->tm_year
+1900));
260 ev
->addParam("mon", i2str(tm
->tm_mon
+1));
261 ev
->addParam("day", i2str(tm
->tm_mday
));
262 ev
->addParam("hour", i2str(tm
->tm_hour
));
263 ev
->addParam("min", i2str(tm
->tm_min
));
265 ev
->addParam("lineendings", "unix");
266 ev
->addParam("security", m
->getfield("security"));
267 ev
->addParam("allowmask", "0");
269 if(!m
->field_empty("journal")) ev
->addParam("usejournal", m
->getfield("journal"));
270 if(!m
->field_empty("subject")) ev
->addParam("subject", rusconv("ku", m
->getfield("subject")));
271 if(!m
->field_empty("mood")) ev
->addParam("prop_current_mood", rusconv("ku", m
->getfield("mood")));
272 if(!m
->field_empty("music")) ev
->addParam("prop_current_music", rusconv("ku", m
->getfield("music")));
273 if(!m
->field_empty("taglist")) ev
->addParam("prop_taglist", rusconv("ku", m
->getfield("taglist")));
274 if(!m
->field_empty("picture")) ev
->addParam("prop_picture_keyword", m
->getfield("picture"));
276 if(!m
->field_empty("preformatted")) ev
->addParam("prop_opt_preformatted", "1");
277 if(!m
->field_empty("nocomments")) ev
->addParam("prop_opt_nocomments", "1");
278 if(!m
->field_empty("backdated")) ev
->addParam("prop_opt_backdated", "1");
279 if(!m
->field_empty("noemail")) ev
->addParam("prop_opt_noemail", "1");
283 } else if(m
->getfield("_eventkind") == "comment") {
284 icqconf::imaccount acc
= conf
->getourid(proto
);
286 string journal
= clist
.get(sev
->getcontact())->getnick();
287 journal
.erase(journal
.find("@"));
289 ev
= new HTTPRequestEvent(acc
.server
+ ":" + i2str(acc
.port
) + "/talkpost_do.bml", HTTPRequestEvent::POST
);
291 ev
->addParam("parenttalkid", "0");
292 ev
->addParam("itemid", m
->getfield("replyto"));
293 ev
->addParam("journal", journal
);
294 ev
->addParam("usertype", "user");
295 ev
->addParam("userpost", acc
.nickname
);
296 ev
->addParam("password", acc
.password
);
297 ev
->addParam("do_login", "0");
298 ev
->addParam("subject", "");
299 ev
->addParam("body", rusconv("ku", sev
->gettext()));
300 ev
->addParam("subjecticon", "none");
301 ev
->addParam("prop_opt_preformatted", "1");
302 ev
->addParam("submitpost", "1");
303 ev
->addParam("do_spellcheck", "0");
305 sent
[ev
] = reqPostComment
;
309 httpcli
.SendEvent(ev
);
318 void ljhook::sendnewuser(const imcontact
&ic
) {
324 if(c
= clist
.get(ic
))
325 if((npos
= c
->getnick().find("@lj")) != -1) {
326 HTTPRequestEvent
*ev
= new HTTPRequestEvent(baseurl
, HTTPRequestEvent::POST
);
328 ev
->addParam("mode", "editfriends");
329 ev
->addParam("user", username
);
330 ev
->addParam("hpassword", md5pass
);
332 ev
->addParam("editfriend_add_1_user", c
->getnick().substr(0, npos
));
334 sent
[ev
] = reqAddFriend
;
335 httpcli
.SendEvent(ev
);
339 void ljhook::removeuser(const imcontact
&ic
) {
344 if(conf
->getourid(proto
).additional
["importfriends"] == "1")
345 if(c
= clist
.get(ic
))
346 if((npos
= c
->getnick().find("@lj")) != -1) {
347 string nick
= c
->getnick().substr(0, npos
);
349 if(c
->getworkinfo().homepage
== getfeedurl(nick
)) {
350 HTTPRequestEvent
*ev
= new HTTPRequestEvent(baseurl
, HTTPRequestEvent::POST
);
352 ev
->addParam("mode", "editfriends");
353 ev
->addParam("user", username
);
354 ev
->addParam("hpassword", md5pass
);
356 ev
->addParam((string
) "editfriend_delete_" + nick
, "1");
358 sent
[ev
] = reqDelFriend
;
359 httpcli
.SendEvent(ev
);
364 void ljhook::setautostatus(imstatus st
) {
366 if(getstatus() != offline
) disconnect();
369 if(getstatus() == offline
) connect();
374 imstatus
ljhook::getstatus() const {
375 return flogged
? available
: offline
;
378 void ljhook::requestinfo(const imcontact
&ic
) {
380 icqcontact
*c
= clist
.get(ic
);
383 icqcontact::moreinfo m
= c
->getmoreinfo();
384 if((npos
= c
->getnick().find("@lj")) != -1) {
385 m
.homepage
= getfeedurl(c
->getnick().substr(0, npos
));
391 void ljhook::lookup(const imsearchparams
¶ms
, verticalmenu
&dest
) {
392 while(!foundguys
.empty()) {
393 delete foundguys
.back();
394 foundguys
.pop_back();
398 icqcontact
*c
= clist
.get(self
);
401 string lst
= c
->getbasicinfo().zip
, buf
;
402 while(!(buf
= getword(lst
)).empty()) {
403 imcontact
ic(0, rss
);
404 ic
.nickname
= getfeedurl(buf
);
405 c
= new icqcontact(ic
);
407 c
->setnick(buf
+ "@lj");
408 c
->setdispnick(c
->getnick());
409 c
->excludefromlist();
411 dest
.additem(conf
->getcolor(cp_clist_rss
), c
, (string
) " " + c
->getnick());
412 foundguys
.push_back(c
);
416 face
.log(_("+ [lj] user lookup finished"));
421 HTTPRequestEvent
*ev
= new HTTPRequestEvent(getfeedurl(params
.nick
));
423 sent
[ev
] = reqLookup
;
424 httpcli
.SendEvent(ev
);
427 lookfor
= params
.nick
;
431 void ljhook::stoplookup() {
435 void ljhook::requestawaymsg(const imcontact
&c
) {
438 void ljhook::requestversion(const imcontact
&c
) {
441 void ljhook::ping(const imcontact
&c
) {
444 void ljhook::ouridchanged(const icqconf::imaccount
&ia
) {
447 void ljhook::updatecontact(icqcontact
*c
) {
450 // ----------------------------------------------------------------------------
452 void ljhook::requestfriends() {
453 HTTPRequestEvent
*ev
= new HTTPRequestEvent(baseurl
, HTTPRequestEvent::POST
);
455 ev
->addParam("mode", "getfriends");
456 ev
->addParam("user", username
);
457 ev
->addParam("hpassword", md5pass
);
458 ev
->addParam("includefriendof", "1");
459 ev
->addParam("includebdays", "1");
461 sent
[ev
] = reqGetFriends
;
462 httpcli
.SendEvent(ev
);
465 // ----------------------------------------------------------------------------
467 void ljhook::socket_cb(SocketEvent
*ev
) {
468 vector
<int>::iterator i
;
470 if(dynamic_cast<AddSocketHandleEvent
*>(ev
)) {
471 AddSocketHandleEvent
*cev
= dynamic_cast<AddSocketHandleEvent
*>(ev
);
473 if(cev
->isRead()) rfds
.push_back(cev
->getSocketHandle());
474 if(cev
->isWrite()) wfds
.push_back(cev
->getSocketHandle());
475 if(cev
->isException()) efds
.push_back(cev
->getSocketHandle());
477 } else if(dynamic_cast<RemoveSocketHandleEvent
*>(ev
)) {
478 RemoveSocketHandleEvent
*cev
= dynamic_cast<RemoveSocketHandleEvent
*>(ev
);
480 i
= find(rfds
.begin(), rfds
.end(), cev
->getSocketHandle());
484 i
= find(wfds
.begin(), wfds
.end(), cev
->getSocketHandle());
488 i
= find(efds
.begin(), efds
.end(), cev
->getSocketHandle());
494 void ljhook::messageack_cb(MessageEvent
*ev
) {
495 HTTPRequestEvent
*rev
= dynamic_cast<HTTPRequestEvent
*>(ev
);
499 map
<HTTPRequestEvent
*, RequestType
>::iterator ie
= sent
.find(rev
);
501 if(ie
== sent
.end()) return;
503 int npos
, count
, i
, k
;
504 string content
= rev
->getContent(), pname
;
505 map
<string
, string
> params
;
508 if(ie
->second
!= reqLookup
) {
509 if(!ev
->isDelivered() || content
.empty()) {
510 pname
= rev
->getHTTPResp();
511 if(pname
.empty()) pname
= _("cannot connect");
512 face
.log(_("+ [lj] HTTP failed: %s"), pname
.c_str());
517 while((npos
= content
.find("\r")) != -1)
518 content
.erase(npos
, 1);
520 while((npos
= content
.find("\n")) != -1) {
521 pname
= content
.substr(0, npos
);
522 content
.erase(0, npos
+1);
524 if((npos
= content
.find("\n")) == -1)
525 npos
= content
.size();
527 params
[pname
] = content
.substr(0, npos
);
529 if(npos
!= content
.size()) content
.erase(0, npos
+1);
534 if(isconnecting() && ie
->second
== reqLogin
) {
537 if(params
["success"] == "OK") {
541 setautostatus(manualstatus
);
543 if(!(c
= clist
.get(self
))) {
544 c
= clist
.addnew(self
, false);
548 count
= atoi(params
["mood_count"].c_str());
549 for(i
= 1; i
< count
; i
++)
550 moods
.push_back(params
[(string
) "mood_" + i2str(i
) + "_name"]);
552 count
= atoi(params
["pickw_count"].c_str());
553 for(i
= 1; i
< count
; i
++)
554 pictures
.push_back(params
[(string
) "pickw_" + i2str(i
)]);
556 sort(moods
.begin()+1, moods
.end());
558 icqcontact::basicinfo bi
= c
->getbasicinfo();
559 c
->setstatus(available
);
563 while(!(buf
= getword(bi
.zip
)).empty())
564 friendof
.push_back(buf
);
566 if(!params
["message"].empty())
567 em
.store(imnotification(self
, _("Message from the server: ") + params
["message"]));
569 timer_getfriends
= 0;
572 face
.log(_("+ [lj] login failed: %s"), params
["errmsg"].c_str());
574 } else if(ie
->second
== reqPost
) {
575 if(params
["success"] == "OK") {
576 face
.log(_("+ [lj] posted successully, the id is %s"), params
["itemid"].c_str());
578 face
.log(_("+ [lj] post error: %s"), params
["errmsg"].c_str());
581 } else if(ie
->second
== reqPostComment
) {
583 } else if(ie
->second
== reqGetFriends
) {
584 if(params
["success"] == "OK") {
585 journals
= vector
<string
>(1, username
);
587 if(conf
->getourid(proto
).additional
["importfriends"] == "1") {
588 count
= atoi(params
["friend_count"].c_str());
593 string username
, name
, birthday
, bd
;
594 icqcontact::moreinfo mi
;
595 icqcontact::basicinfo bi
;
597 for(i
= 1; i
<= count
; i
++) {
598 username
= params
[(string
) "friend_" + i2str(i
) + "_user"];
599 name
= rusconv("uk", params
[(string
) "friend_" + i2str(i
) + "_name"]);
600 birthday
= params
[(string
) "friend_" + i2str(i
) + "_birthday"];
604 for(k
= 0; k
< clist
.count
&& !found
; k
++) {
605 c
= (icqcontact
*) clist
.at(k
);
606 if(c
->getdesc().pname
== rss
) {
607 icqcontact::workinfo wi
= c
->getworkinfo();
609 if(wi
.homepage
== getoldfeedurl(username
)) {
610 wi
.homepage
= getfeedurl(username
);
614 found
= (wi
.homepage
== getfeedurl(username
));
619 if(c
= clist
.addnew(imcontact(0, rss
), false)) {
620 icqcontact::workinfo wi
= c
->getworkinfo();
621 wi
.homepage
= getfeedurl(username
);
624 icqcontact::moreinfo mi
= c
->getmoreinfo();
628 c
->setnick(username
+ "@lj");
629 c
->setdispnick(c
->getnick());
633 bi
= c
->getbasicinfo();
634 mi
= c
->getmoreinfo();
637 if(!birthday
.empty()) {
640 while(!(bd
= getrword(birthday
, "-")).empty())
642 case 0: mi
.birth_day
= atoi(bd
.c_str()); break;
643 case 1: mi
.birth_month
= atoi(bd
.c_str()); break;
644 case 2: mi
.birth_year
= atoi(bd
.c_str()); break;
652 if(params
[(string
) "friend_" + i2str(i
) + "_type"] == "community")
653 journals
.push_back(username
);
656 count
= atoi(params
["friendof_count"].c_str());
657 bool foempty
= friendof
.empty();
659 map
<string
, string
> nfriendof
;
660 map
<string
, string
>::const_iterator in
;
661 vector
<string
>::iterator il
;
664 for(i
= 1; i
<= count
; i
++) {
665 username
= params
[(string
) "friendof_" + i2str(i
) + "_user"];
666 name
= rusconv("uk", params
[(string
) "friendof_" + i2str(i
) + "_name"]);
667 nfriendof
[username
] = name
;
670 for(in
= nfriendof
.begin(); in
!= nfriendof
.end(); ++in
)
671 if(find(friendof
.begin(), friendof
.end(), in
->first
) == friendof
.end()) {
672 friendof
.push_back(in
->first
);
675 bd
= (string
) "http://" + conf
->getourid(proto
).server
+ "/users/" + in
->first
;
677 snprintf(buf
, NOTIFBUF
, _("The user %s (%s) has added you to his/her friend list\n\nJournal address: %s"),
678 in
->first
.c_str(), in
->second
.c_str(), bd
.c_str());
680 em
.store(imnotification(self
, buf
));
684 for(il
= friendof
.begin(); il
!= friendof
.end(); ) {
685 if(nfriendof
.find(*il
) == nfriendof
.end()) {
686 bd
= (string
) "http://" + conf
->getourid(proto
).server
+ "/users/" + *il
;
687 snprintf(buf
, NOTIFBUF
, _("The user %s has removed you from his/her friend list\n\nJournal address: %s"),
688 il
->c_str(), bd
.c_str());
689 em
.store(imnotification(self
, buf
));
691 il
= friendof
.begin();
697 if(c
= clist
.get(self
)) {
698 icqcontact::basicinfo bi
= c
->getbasicinfo();
699 vector
<string
>::const_iterator ifo
= friendof
.begin();
702 while(ifo
!= friendof
.end()) {
703 bi
.zip
+= *ifo
+ " ";
711 } else if(ie
->second
== reqDelFriend
) {
712 if(params
["success"] != "OK") {
713 face
.log(_("+ [lj] error deleting friend"));
715 face
.log(_("+ [lj] the user has been removed from your friend list"));
718 } else if(ie
->second
== reqAddFriend
) {
719 if(params
["success"] != "OK" || !atoi(params
["friends_added"].c_str())) {
720 face
.log(_("+ [lj] couldn't add friend"));
722 face
.log(_("+ [lj] %s was added to friends"), params
["friend_1_user"].c_str());
725 } else if(ie
->second
== reqLookup
&& sdest
) {
726 if(ev
->isDelivered() && !content
.empty()) {
727 imcontact
ic(0, rss
);
728 ic
.nickname
= rev
->getURL();
729 c
= new icqcontact(ic
);
731 c
->setnick(lookfor
+ "@lj");
732 c
->setdispnick(c
->getnick());
733 c
->excludefromlist();
734 rsshook::parsedocument(rev
, c
);
736 sdest
->additem(conf
->getcolor(cp_clist_rss
), c
, (string
) " " + c
->getnick());
737 foundguys
.push_back(c
);
741 face
.log(_("+ [lj] user lookup finished"));
747 if(ie
!= sent
.end()) sent
.erase(ie
);
750 void ljhook::logger_cb(LogEvent
*ev
) {
751 switch(ev
->getType()) {
752 case LogEvent::PACKET
:
753 case LogEvent::DIRECTPACKET
:
754 face
.log(ev
->getMessage());
758 face
.log(ev
->getMessage());
763 string
ljhook::getfeedurl(const string
&nick
) const {
764 return (string
) "http://" + conf
->getourid(proto
).server
+ "/users/" + nick
+ "/data/rss?auth=digest";
767 string
ljhook::getoldfeedurl(const string
&nick
) const {
768 return (string
) "http://" + conf
->getourid(proto
).server
+ "/users/" + nick
+ "/rss/";