3 * centerim gadu-gadu protocol handling class
4 * $Id: gaduhook.cc,v 1.14 2005/07/08 09:49:17 konst Exp $
6 * Copyright (C) 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"
29 #include "eventmanager.h"
33 #include "icqcontacts.h"
35 #include "libgadu-config.h"
39 #include <arpa/inet.h>
40 #include <netinet/in.h>
49 #define PERIOD_PING 50
51 static imstatus
gg2imstatus(int st
) {
55 case GG_STATUS_INVISIBLE
:
56 case GG_STATUS_INVISIBLE_DESCR
:
61 case GG_STATUS_BUSY_DESCR
:
65 case GG_STATUS_NOT_AVAIL
:
66 case GG_STATUS_NOT_AVAIL_DESCR
:
78 static int imstatus2gg(imstatus st
, const string
&desc
= "") {
84 GG_STATUS_INVISIBLE
: GG_STATUS_INVISIBLE_DESCR
;
93 GG_STATUS_BUSY
: GG_STATUS_BUSY_DESCR
;
100 GG_STATUS_AVAIL
: GG_STATUS_AVAIL_DESCR
;
107 // ----------------------------------------------------------------------------
111 gg_pubdir50_t lreq
= 0, ireq
= 0;
113 gaduhook::gaduhook(): abstracthook(gadu
), flogged(false), sess(0) {
114 fcapabs
.insert(hookcapab::changepassword
);
115 fcapabs
.insert(hookcapab::changenick
);
116 fcapabs
.insert(hookcapab::changedetails
);
117 fcapabs
.insert(hookcapab::setaway
);
118 fcapabs
.insert(hookcapab::fetchaway
);
119 fcapabs
.insert(hookcapab::ssl
);
122 gaduhook::~gaduhook() {
125 void gaduhook::init() {
126 manualstatus
= conf
->getstatus(proto
);
129 void gaduhook::connect() {
130 icqconf::imaccount acc
= conf
->getourid(proto
);
131 static struct gg_login_params lp
;
132 /* TODO investigate this, auto_ptr will free with delete, but allocated by malloc */
133 static auto_ptr
<char> pass(strdup(acc
.password
.c_str()));
135 memset(&lp
, 0, sizeof(lp
));
137 struct hostent
*he
= gethostbyname(acc
.server
.c_str());
141 memcpy((char *) &addr
, he
->h_addr
, sizeof(addr
));
142 lp
.server_addr
= addr
.s_addr
;
143 lp
.server_port
= acc
.port
;
146 lp
.password
= pass
.get();
149 /* TODO investigate this, auto_ptr will free with delete, but allocated by malloc */
150 static auto_ptr
<char> descr(strdup(rusconv("kw", conf
->getawaymsg(proto
)).c_str()));
152 lp
.status_descr
= descr
.get();
153 lp
.status
= imstatus2gg(manualstatus
, descr
.get());
155 lp
.tls
= acc
.additional
["ssl"] == "1" ? 1 : 0;
158 sess
= gg_login(&lp
);
161 face
.log(_("+ [gg] connection failed"));
164 face
.log(_("+ [gg] cannot resolve %s"), acc
.server
.c_str());
169 void gaduhook::cutoff() {
171 gg_change_status(sess
, GG_STATUS_NOT_AVAIL
);
173 gg_free_session(sess
);
177 clist
.setoffline(proto
);
180 void gaduhook::disconnect() {
182 log(logDisconnected
);
183 logger
.putourstatus(proto
, getstatus(), offline
);
186 void gaduhook::exectimers() {
188 if(timer_current
-timer_ping
> PERIOD_PING
) {
190 timer_ping
= timer_current
;
195 void gaduhook::main() {
201 struct gg_notify_reply
*nr
;
204 e
= gg_watch_fd(sess
);
208 case GG_EVENT_CONN_SUCCESS
:
216 case GG_EVENT_CONN_FAILED
:
218 face
.log(_("+ [gg] connection to the server failed"));
221 case GG_EVENT_DISCONNECT
:
226 if(e
->event
.msg
.sender
&& e
->event
.msg
.message
) {
227 text
= rusconv("wk", (const char *) e
->event
.msg
.message
);
228 em
.store(immessage(imcontact(e
->event
.msg
.sender
, gadu
),
229 imevent::incoming
, text
, e
->event
.msg
.time
));
233 case GG_EVENT_NOTIFY
:
234 case GG_EVENT_NOTIFY_DESCR
:
235 nr
= (e
->type
== GG_EVENT_NOTIFY
) ? e
->event
.notify
: e
->event
.notify_descr
.notify
;
237 for(; nr
->uin
; nr
++) {
238 char *desc
= (e
->type
== GG_EVENT_NOTIFY_DESCR
) ? e
->event
.notify_descr
.descr
: 0;
240 usernotify(nr
->uin
, nr
->status
, desc
,
241 nr
->remote_ip
, nr
->remote_port
,
246 case GG_EVENT_NOTIFY60
:
247 for(i
= 0; e
->event
.notify60
[i
].uin
; i
++)
248 usernotify(e
->event
.notify60
[i
].uin
,
249 e
->event
.notify60
[i
].status
,
250 e
->event
.notify60
[i
].descr
,
251 e
->event
.notify60
[i
].remote_ip
,
252 e
->event
.notify60
[i
].remote_port
,
253 e
->event
.notify60
[i
].version
);
256 case GG_EVENT_STATUS
:
257 userstatuschange(e
->event
.status
.uin
, e
->event
.status
.status
, e
->event
.status
.descr
);
260 case GG_EVENT_STATUS60
:
261 userstatuschange(e
->event
.status60
.uin
, e
->event
.status60
.status
, e
->event
.status60
.descr
);
270 case GG_EVENT_PUBDIR50_SEARCH_REPLY
:
271 searchdone(e
->event
.pubdir50
);
274 case GG_EVENT_PUBDIR50_READ
:
277 case GG_EVENT_PUBDIR50_WRITE
:
280 case GG_EVENT_USERLIST
:
281 if(e
->event
.userlist
.type
== GG_USERLIST_GET_REPLY
) {
282 char *p
= e
->event
.userlist
.reply
;
291 face
.log(_("+ [gg] connection lost"));
296 void gaduhook::getsockets(fd_set
&rfds
, fd_set
&wfds
, fd_set
&efds
, int &hsocket
) const {
297 if(sess
&& sess
->fd
!= -1) {
298 if((sess
->check
& GG_CHECK_READ
))
299 FD_SET(sess
->fd
, &rfds
);
301 if((sess
->check
& GG_CHECK_WRITE
))
302 FD_SET(sess
->fd
, &wfds
);
304 hsocket
= max(sess
->fd
, hsocket
);
308 bool gaduhook::isoursocket(fd_set
&rfds
, fd_set
&wfds
, fd_set
&efds
) const {
309 if(sess
&& sess
->fd
!= -1) {
310 return FD_ISSET(sess
->fd
, &rfds
) || FD_ISSET(sess
->fd
, &wfds
);
316 bool gaduhook::online() const {
320 bool gaduhook::logged() const {
321 return sess
&& flogged
;
324 bool gaduhook::isconnecting() const {
325 return sess
&& !flogged
;
328 bool gaduhook::enabled() const {
332 bool gaduhook::send(const imevent
&ev
) {
333 icqcontact
*c
= clist
.get(ev
.getcontact());
337 if(ev
.gettype() == imevent::message
) {
338 const immessage
*m
= static_cast<const immessage
*>(&ev
);
339 if(m
) text
= rushtmlconv("kw", m
->gettext());
341 } else if(ev
.gettype() == imevent::url
) {
342 const imurl
*m
= static_cast<const imurl
*>(&ev
);
343 if(m
) text
= rushtmlconv("kw", m
->geturl()) + "\n\n" + rusconv("kw", m
->getdescription());
347 gg_send_message(sess
, GG_CLASS_MSG
, c
->getdesc().uin
, (const unsigned char *) text
.c_str());
354 void gaduhook::sendnewuser(const imcontact
&c
) {
355 gg_add_notify(sess
, c
.uin
);
359 void gaduhook::removeuser(const imcontact
&c
) {
360 gg_remove_notify(sess
, c
.uin
);
363 void gaduhook::setautostatus(imstatus st
) {
365 if(getstatus() != offline
) disconnect();
368 if(getstatus() != offline
) {
369 gg_change_status_descr(sess
, imstatus2gg(st
, conf
->getawaymsg(proto
)), conf
->getawaymsg(proto
).c_str());
370 logger
.putourstatus(proto
, getstatus(), st
);
378 imstatus
gaduhook::getstatus() const {
379 if(!sess
) return offline
;
381 if(GG_S_NA(sess
->status
)) return notavail
; else
382 if(GG_S_B(sess
->status
)) return occupied
; else
383 if(GG_S_I(sess
->status
)) return invisible
; else
387 void gaduhook::requestinfo(const imcontact
&c
) {
388 if(ireq
) gg_pubdir50_free(ireq
);
389 ireq
= gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST
);
391 gg_pubdir50_add(ireq
, GG_PUBDIR50_UIN
, i2str(c
.uin
).c_str());
392 gg_pubdir50(sess
, ireq
);
395 void gaduhook::requestawaymsg(const imcontact
&ic
) {
396 icqcontact
*c
= clist
.get(ic
);
399 string am
= awaymsgs
[ic
.uin
];
402 em
.store(imnotification(ic
, (string
) _("Away message:") + "\n\n" + am
));
404 face
.log(_("+ [gg] no away message from %s, %s"),
405 c
->getdispnick().c_str(), ic
.totext().c_str());
410 bool gaduhook::regconnect(const string
&aserv
) {
414 bool gaduhook::regattempt(unsigned int &auin
, const string
&apassword
, const string
&email
) {
416 struct gg_http
*th
, *rh
;
420 string token
, tokenid
;
423 if(!th
) return false;
425 token
= handletoken(th
);
426 tokenid
= ((struct gg_token
*) th
->data
)->tokenid
;
429 if((r
= !token
.empty())) {
430 rh
= gg_register3(email
.c_str(), apassword
.c_str(), tokenid
.c_str(), token
.c_str(), 0);
432 auin
= ((struct gg_pubdir
*) th
->data
)->uin
;
433 gg_free_register(rh
);
440 void gaduhook::lookup(const imsearchparams
¶ms
, verticalmenu
&dest
) {
442 while(!foundguys
.empty()) {
443 delete foundguys
.back();
444 foundguys
.pop_back();
447 if(lreq
) gg_pubdir50_free(lreq
);
448 lreq
= gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST
);
450 if(params
.uin
) gg_pubdir50_add(lreq
, GG_PUBDIR50_UIN
, i2str(params
.uin
).c_str());
451 if(!params
.nick
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_NICKNAME
, params
.nick
.c_str());
452 if(!params
.firstname
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_FIRSTNAME
, params
.firstname
.c_str());
453 if(!params
.lastname
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_LASTNAME
, params
.lastname
.c_str());
454 if(!params
.city
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_CITY
, params
.city
.c_str());
456 if(params
.onlineonly
) gg_pubdir50_add(lreq
, GG_PUBDIR50_ACTIVE
, GG_PUBDIR50_ACTIVE_TRUE
);
457 if(params
.gender
!= genderUnspec
) gg_pubdir50_add(lreq
, GG_PUBDIR50_GENDER
, params
.gender
== genderMale
? GG_PUBDIR50_GENDER_MALE
: GG_PUBDIR50_GENDER_FEMALE
);
459 gg_pubdir50(sess
, lreq
);
462 void gaduhook::sendupdateuserinfo(const icqcontact
&c
) {
465 if(req
= gg_pubdir50_new(GG_PUBDIR50_WRITE
)) {
466 icqcontact::basicinfo bi
= c
.getbasicinfo();
467 icqcontact::moreinfo mi
= c
.getmoreinfo();
469 gg_pubdir50_add(req
, GG_PUBDIR50_NICKNAME
, c
.getnick().c_str());
470 gg_pubdir50_add(req
, GG_PUBDIR50_FIRSTNAME
, bi
.fname
.c_str());
471 gg_pubdir50_add(req
, GG_PUBDIR50_LASTNAME
, bi
.lname
.c_str());
472 gg_pubdir50_add(req
, GG_PUBDIR50_CITY
, bi
.city
.c_str());
476 gg_pubdir50_add(req
, GG_PUBDIR50_GENDER
, GG_PUBDIR50_GENDER_SET_MALE
);
479 gg_pubdir50_add(req
, GG_PUBDIR50_GENDER
, GG_PUBDIR50_GENDER_SET_FEMALE
);
483 gg_pubdir50(sess
, req
);
484 gg_pubdir50_free(req
);
488 // ----------------------------------------------------------------------------
490 void gaduhook::searchdone(void *p
) {
491 gg_pubdir50_t sp
= (gg_pubdir50_t
) p
;
493 if(searchdest
&& lreq
) {
494 for(int i
= 0; i
< sp
->count
; i
++) {
495 icqcontact
*c
= new icqcontact(imcontact(strtoul(gg_pubdir50_get(sp
, i
, GG_PUBDIR50_UIN
), 0, 0), gadu
));
496 icqcontact::basicinfo binfo
= c
->getbasicinfo();
498 const char *p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_NICKNAME
);
501 c
->setdispnick(c
->getnick());
504 p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_FIRSTNAME
);
505 if(p
) binfo
.fname
= p
;
507 p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_LASTNAME
);
508 if(p
) binfo
.lname
= p
;
512 p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_STATUS
);
513 if(p
&& atoi(p
) != GG_STATUS_NOT_AVAIL
) line
= "o ";
516 line
+= c
->getnick();
517 if(line
.size() > 12) line
.resize(12);
518 else line
+= string(12-line
.size(), ' ');
520 line
+= " " + binfo
.fname
+ " " + binfo
.lname
;
522 c
->setbasicinfo(binfo
);
524 foundguys
.push_back(c
);
525 searchdest
->additem(conf
->getcolor(cp_clist_gadu
), c
, line
);
528 searchdest
->redraw();
530 log(logSearchFinished
, foundguys
.size());
534 icqcontact
*c
= clist
.get(imcontact(strtoul(gg_pubdir50_get(sp
, 0, GG_PUBDIR50_UIN
), 0, 0), gadu
));
538 if(!c
) c
= clist
.get(contactroot
);
540 icqcontact::basicinfo bi
= c
->getbasicinfo();
541 icqcontact::moreinfo mi
= c
->getmoreinfo();
543 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_FIRSTNAME
);
546 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_LASTNAME
);
549 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_NICKNAME
);
554 if((c
->getnick() == c
->getdispnick())
555 || (c
->getdispnick() == i2str(c
->getdesc().uin
)))
556 c
->setdispnick(nick
);
561 c
->setdispnick(bi
.fname
);
565 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_CITY
);
568 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_GENDER
);
572 if(gender
== GG_PUBDIR50_GENDER_MALE
) mi
.gender
= genderMale
; else
573 if(gender
== GG_PUBDIR50_GENDER_FEMALE
) mi
.gender
= genderFemale
; else
574 mi
.gender
= genderUnspec
;
582 void gaduhook::userstatuschange(unsigned int uin
, int status
, const char *desc
) {
583 icqcontact
*c
= clist
.get(imcontact(uin
, gadu
));
585 imstatus ust
= gg2imstatus(status
);
586 logger
.putonline(c
, c
->getstatus(), ust
);
590 awaymsgs
[uin
] = rusconv("wk", desc
);
596 void gaduhook::userlistsend() {
601 for(i
= 0; i
< clist
.count
; i
++) {
602 c
= (icqcontact
*) clist
.at(i
);
604 if(c
->getdesc().pname
== proto
&& c
->getdesc().uin
)
605 uins
.push_back(c
->getdesc().uin
);
608 auto_ptr
<uin_t
> cuins(new uin_t
[uins
.size()]);
609 /* TODO: allocated with new[], but will be freed by delete */
610 auto_ptr
<char> ctypes(new char[uins
.size()]);
612 for(vector
<uin_t
>::const_iterator iu
= uins
.begin(); iu
!= uins
.end(); ++iu
) {
613 cuins
.get()[iu
-uins
.begin()] = *iu
;
614 ctypes
.get()[iu
-uins
.begin()] = GG_USER_NORMAL
;
617 gg_notify_ex(sess
, cuins
.get(), ctypes
.get(), uins
.size());
620 void gaduhook::usernotify(unsigned int uin
, int status
, const char *desc
,
621 unsigned int ip
, int port
, int version
) {
622 imcontact
ic(uin
, gadu
);
623 icqcontact
*c
= clist
.get(ic
);
627 addr
.s_addr
= ntohl(ip
);
628 char *p
= inet_ntoa(addr
);
629 if(p
) c
->setlastip(p
);
631 imstatus ust
= gg2imstatus(status
);
632 logger
.putonline(c
, c
->getstatus(), ust
);
636 awaymsgs
[ic
.uin
] = rusconv("wk", desc
);
638 awaymsgs
[ic
.uin
] = "";
643 // ----------------------------------------------------------------------------
645 const int token_char_height
= 12;
646 const char token_id_char
[] = {"0123456789abcdef"};
647 const char token_id
[][15] = {
856 static int token_check(int nr
, int x
, int y
, const char *ocr
, int maxx
, int maxy
) {
859 for(i
= nr
*token_char_height
; i
< (nr
+1)*token_char_height
; i
++, y
++) {
862 for(j
= 0; token_id
[i
][j
] && j
+ xx
< maxx
; j
++, xx
++) {
863 if(token_id
[i
][j
] != ocr
[y
* (maxx
+ 1) + xx
])
871 static char *token_ocr(const char *ocr
, int width
, int height
, int length
) {
875 token
= (char *) malloc(length
+ 1);
876 memset(token
, 0, length
+ 1);
878 for(x
= 0; x
< width
; x
++) {
879 for(y
= 0; y
< height
- token_char_height
; y
++) {
880 int result
= 0, token_part
= 0;
883 result
= token_check(token_part
++, x
, y
, ocr
, width
, height
);
884 } while(!result
&& token_part
< 16);
886 if(result
&& count
< length
)
887 token
[count
++] = token_id_char
[token_part
- 1];
899 string
gaduhook::handletoken(struct gg_http
*h
) {
902 char *tmpfilep
= NULL
;
907 if(gg_token_watch_fd(h
) || h
->state
== GG_STATE_ERROR
)
910 if(h
->state
!= GG_STATE_DONE
)
913 if(!(t
= (struct gg_token
*) h
->data
) || !h
->body
)
917 while (tmpfilep
== NULL
) {
918 char tmpnam
[PATH_MAX
];
920 char * tmpdir
= getenv("TMPDIR");
925 strncat(tmpnam
, "/gg.token.XXXXXX", sizeof(tmpnam
)-strlen(tmpnam
)-1);
927 if ((tmpfiledes
= mkstemp (tmpnam
)) == -1) {
936 } while(!access(fname
.c_str(), F_OK
));
938 ofstream
bf(fname
.c_str());
941 bf
.write(h
->body
, h
->body_size
);
949 struct jpeg_decompress_struct j
;
950 struct jpeg_error_mgr e
;
957 if(!(f
= fopen(fname
.c_str(), "rb")))
960 j
.err
= jpeg_std_error(&e
);
961 jpeg_create_decompress(&j
);
962 jpeg_stdio_src(&j
, f
);
963 jpeg_read_header(&j
, TRUE
);
964 jpeg_start_decompress(&j
);
966 size
= j
.output_width
* j
.output_components
;
967 buf
[0] = (JSAMPLE
*) malloc(size
);
969 token
= (char *) malloc((j
.output_width
+ 1) * j
.output_height
);
971 while(j
.output_scanline
< j
.output_height
) {
972 jpeg_read_scanlines(&j
, buf
, 1);
974 for(i
= 0; i
< j
.output_width
; i
++, ih
++)
975 token
[ih
] = (buf
[0][i
*3] + buf
[0][i
*3+1] + buf
[0][i
*3+2] < 384) ? '#' : '.';
980 if((tmp
= token_ocr(token
, j
.output_width
, j
.output_height
, t
->length
))) {
987 jpeg_finish_decompress(&j
);
988 jpeg_destroy_decompress(&j
);
993 unlink(fname
.c_str());