3 * centericq 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 <konst@konst.org.ua>
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"
34 #include "libgadu-config.h"
38 #include <arpa/inet.h>
39 #include <netinet/in.h>
47 #define PERIOD_PING 50
49 static imstatus
gg2imstatus(int st
) {
53 case GG_STATUS_INVISIBLE
:
54 case GG_STATUS_INVISIBLE_DESCR
:
59 case GG_STATUS_BUSY_DESCR
:
63 case GG_STATUS_NOT_AVAIL
:
64 case GG_STATUS_NOT_AVAIL_DESCR
:
76 static int imstatus2gg(imstatus st
, const string
&desc
= "") {
82 GG_STATUS_INVISIBLE
: GG_STATUS_INVISIBLE_DESCR
;
90 GG_STATUS_BUSY
: GG_STATUS_BUSY_DESCR
;
97 GG_STATUS_AVAIL
: GG_STATUS_AVAIL_DESCR
;
104 // ----------------------------------------------------------------------------
108 gg_pubdir50_t lreq
= 0, ireq
= 0;
110 gaduhook::gaduhook(): abstracthook(gadu
), flogged(false), sess(0) {
111 fcapabs
.insert(hookcapab::changepassword
);
112 fcapabs
.insert(hookcapab::changenick
);
113 fcapabs
.insert(hookcapab::changedetails
);
114 fcapabs
.insert(hookcapab::setaway
);
115 fcapabs
.insert(hookcapab::fetchaway
);
116 fcapabs
.insert(hookcapab::ssl
);
119 gaduhook::~gaduhook() {
122 void gaduhook::init() {
123 manualstatus
= conf
.getstatus(proto
);
126 void gaduhook::connect() {
127 icqconf::imaccount acc
= conf
.getourid(proto
);
128 static struct gg_login_params lp
;
129 /* TODO investigate this, auto_ptr will free with delete, but allocated by malloc */
130 static auto_ptr
<char> pass(strdup(acc
.password
.c_str()));
132 memset(&lp
, 0, sizeof(lp
));
134 struct hostent
*he
= gethostbyname(acc
.server
.c_str());
138 memcpy((char *) &addr
, he
->h_addr
, sizeof(addr
));
139 lp
.server_addr
= addr
.s_addr
;
140 lp
.server_port
= acc
.port
;
143 lp
.password
= pass
.get();
146 /* TODO investigate this, auto_ptr will free with delete, but allocated by malloc */
147 static auto_ptr
<char> descr(strdup(rusconv("kw", conf
.getawaymsg(proto
)).c_str()));
149 lp
.status_descr
= descr
.get();
150 lp
.status
= imstatus2gg(manualstatus
, descr
.get());
152 lp
.tls
= acc
.additional
["ssl"] == "1" ? 1 : 0;
155 sess
= gg_login(&lp
);
158 face
.log(_("+ [gg] connection failed"));
161 face
.log(_("+ [gg] cannot resolve %s"), acc
.server
.c_str());
166 void gaduhook::cutoff() {
168 gg_change_status(sess
, GG_STATUS_NOT_AVAIL
);
170 gg_free_session(sess
);
174 clist
.setoffline(proto
);
177 void gaduhook::disconnect() {
179 log(logDisconnected
);
180 logger
.putourstatus(proto
, getstatus(), offline
);
183 void gaduhook::exectimers() {
185 if(timer_current
-timer_ping
> PERIOD_PING
) {
187 timer_ping
= timer_current
;
192 void gaduhook::main() {
198 struct gg_notify_reply
*nr
;
201 e
= gg_watch_fd(sess
);
205 case GG_EVENT_CONN_SUCCESS
:
213 case GG_EVENT_CONN_FAILED
:
215 face
.log(_("+ [gg] connection to the server failed"));
218 case GG_EVENT_DISCONNECT
:
223 if(e
->event
.msg
.sender
&& e
->event
.msg
.message
) {
224 text
= rusconv("wk", (const char *) e
->event
.msg
.message
);
225 em
.store(immessage(imcontact(e
->event
.msg
.sender
, gadu
),
226 imevent::incoming
, text
, e
->event
.msg
.time
));
230 case GG_EVENT_NOTIFY
:
231 case GG_EVENT_NOTIFY_DESCR
:
232 nr
= (e
->type
== GG_EVENT_NOTIFY
) ? e
->event
.notify
: e
->event
.notify_descr
.notify
;
234 for(; nr
->uin
; nr
++) {
235 char *desc
= (e
->type
== GG_EVENT_NOTIFY_DESCR
) ? e
->event
.notify_descr
.descr
: 0;
237 usernotify(nr
->uin
, nr
->status
, desc
,
238 nr
->remote_ip
, nr
->remote_port
,
243 case GG_EVENT_NOTIFY60
:
244 for(i
= 0; e
->event
.notify60
[i
].uin
; i
++)
245 usernotify(e
->event
.notify60
[i
].uin
,
246 e
->event
.notify60
[i
].status
,
247 e
->event
.notify60
[i
].descr
,
248 e
->event
.notify60
[i
].remote_ip
,
249 e
->event
.notify60
[i
].remote_port
,
250 e
->event
.notify60
[i
].version
);
253 case GG_EVENT_STATUS
:
254 userstatuschange(e
->event
.status
.uin
, e
->event
.status
.status
, e
->event
.status
.descr
);
257 case GG_EVENT_STATUS60
:
258 userstatuschange(e
->event
.status60
.uin
, e
->event
.status60
.status
, e
->event
.status60
.descr
);
267 case GG_EVENT_PUBDIR50_SEARCH_REPLY
:
268 searchdone(e
->event
.pubdir50
);
271 case GG_EVENT_PUBDIR50_READ
:
274 case GG_EVENT_PUBDIR50_WRITE
:
277 case GG_EVENT_USERLIST
:
278 if(e
->event
.userlist
.type
== GG_USERLIST_GET_REPLY
) {
279 char *p
= e
->event
.userlist
.reply
;
288 face
.log(_("+ [gg] connection lost"));
293 void gaduhook::getsockets(fd_set
&rfds
, fd_set
&wfds
, fd_set
&efds
, int &hsocket
) const {
294 if(sess
&& sess
->fd
!= -1) {
295 if((sess
->check
& GG_CHECK_READ
))
296 FD_SET(sess
->fd
, &rfds
);
298 if((sess
->check
& GG_CHECK_WRITE
))
299 FD_SET(sess
->fd
, &wfds
);
301 hsocket
= max(sess
->fd
, hsocket
);
305 bool gaduhook::isoursocket(fd_set
&rfds
, fd_set
&wfds
, fd_set
&efds
) const {
306 if(sess
&& sess
->fd
!= -1) {
307 return FD_ISSET(sess
->fd
, &rfds
) || FD_ISSET(sess
->fd
, &wfds
);
313 bool gaduhook::online() const {
317 bool gaduhook::logged() const {
318 return sess
&& flogged
;
321 bool gaduhook::isconnecting() const {
322 return sess
&& !flogged
;
325 bool gaduhook::enabled() const {
329 bool gaduhook::send(const imevent
&ev
) {
330 icqcontact
*c
= clist
.get(ev
.getcontact());
334 if(ev
.gettype() == imevent::message
) {
335 const immessage
*m
= static_cast<const immessage
*>(&ev
);
336 if(m
) text
= rushtmlconv("kw", m
->gettext());
338 } else if(ev
.gettype() == imevent::url
) {
339 const imurl
*m
= static_cast<const imurl
*>(&ev
);
340 if(m
) text
= rushtmlconv("kw", m
->geturl()) + "\n\n" + rusconv("kw", m
->getdescription());
344 gg_send_message(sess
, GG_CLASS_MSG
, c
->getdesc().uin
, (const unsigned char *) text
.c_str());
351 void gaduhook::sendnewuser(const imcontact
&c
) {
352 gg_add_notify(sess
, c
.uin
);
356 void gaduhook::removeuser(const imcontact
&c
) {
357 gg_remove_notify(sess
, c
.uin
);
360 void gaduhook::setautostatus(imstatus st
) {
362 if(getstatus() != offline
) disconnect();
365 if(getstatus() != offline
) {
366 gg_change_status_descr(sess
, imstatus2gg(st
, conf
.getawaymsg(proto
)), conf
.getawaymsg(proto
).c_str());
367 logger
.putourstatus(proto
, getstatus(), st
);
375 imstatus
gaduhook::getstatus() const {
376 if(!sess
) return offline
;
378 if(GG_S_NA(sess
->status
)) return notavail
; else
379 if(GG_S_B(sess
->status
)) return occupied
; else
380 if(GG_S_I(sess
->status
)) return invisible
; else
384 void gaduhook::requestinfo(const imcontact
&c
) {
385 if(ireq
) gg_pubdir50_free(ireq
);
386 ireq
= gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST
);
388 gg_pubdir50_add(ireq
, GG_PUBDIR50_UIN
, i2str(c
.uin
).c_str());
389 gg_pubdir50(sess
, ireq
);
392 void gaduhook::requestawaymsg(const imcontact
&ic
) {
393 icqcontact
*c
= clist
.get(ic
);
396 string am
= awaymsgs
[ic
.uin
];
399 em
.store(imnotification(ic
, (string
) _("Away message:") + "\n\n" + am
));
401 face
.log(_("+ [gg] no away message from %s, %s"),
402 c
->getdispnick().c_str(), ic
.totext().c_str());
407 bool gaduhook::regconnect(const string
&aserv
) {
411 bool gaduhook::regattempt(unsigned int &auin
, const string
&apassword
, const string
&email
) {
413 struct gg_http
*th
, *rh
;
417 string token
, tokenid
;
420 if(!th
) return false;
422 token
= handletoken(th
);
423 tokenid
= ((struct gg_token
*) th
->data
)->tokenid
;
426 if((r
= !token
.empty())) {
427 rh
= gg_register3(email
.c_str(), apassword
.c_str(), tokenid
.c_str(), token
.c_str(), 0);
429 auin
= ((struct gg_pubdir
*) th
->data
)->uin
;
430 gg_free_register(rh
);
437 void gaduhook::lookup(const imsearchparams
¶ms
, verticalmenu
&dest
) {
439 while(!foundguys
.empty()) {
440 delete foundguys
.back();
441 foundguys
.pop_back();
444 if(lreq
) gg_pubdir50_free(lreq
);
445 lreq
= gg_pubdir50_new(GG_PUBDIR50_SEARCH_REQUEST
);
447 if(params
.uin
) gg_pubdir50_add(lreq
, GG_PUBDIR50_UIN
, i2str(params
.uin
).c_str());
448 if(!params
.nick
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_NICKNAME
, params
.nick
.c_str());
449 if(!params
.firstname
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_FIRSTNAME
, params
.firstname
.c_str());
450 if(!params
.lastname
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_LASTNAME
, params
.lastname
.c_str());
451 if(!params
.city
.empty()) gg_pubdir50_add(lreq
, GG_PUBDIR50_CITY
, params
.city
.c_str());
453 if(params
.onlineonly
) gg_pubdir50_add(lreq
, GG_PUBDIR50_ACTIVE
, GG_PUBDIR50_ACTIVE_TRUE
);
454 if(params
.gender
!= genderUnspec
) gg_pubdir50_add(lreq
, GG_PUBDIR50_GENDER
, params
.gender
== genderMale
? GG_PUBDIR50_GENDER_MALE
: GG_PUBDIR50_GENDER_FEMALE
);
456 gg_pubdir50(sess
, lreq
);
459 void gaduhook::sendupdateuserinfo(const icqcontact
&c
) {
462 if(req
= gg_pubdir50_new(GG_PUBDIR50_WRITE
)) {
463 icqcontact::basicinfo bi
= c
.getbasicinfo();
464 icqcontact::moreinfo mi
= c
.getmoreinfo();
466 gg_pubdir50_add(req
, GG_PUBDIR50_NICKNAME
, c
.getnick().c_str());
467 gg_pubdir50_add(req
, GG_PUBDIR50_FIRSTNAME
, bi
.fname
.c_str());
468 gg_pubdir50_add(req
, GG_PUBDIR50_LASTNAME
, bi
.lname
.c_str());
469 gg_pubdir50_add(req
, GG_PUBDIR50_CITY
, bi
.city
.c_str());
473 gg_pubdir50_add(req
, GG_PUBDIR50_GENDER
, GG_PUBDIR50_GENDER_SET_MALE
);
476 gg_pubdir50_add(req
, GG_PUBDIR50_GENDER
, GG_PUBDIR50_GENDER_SET_FEMALE
);
480 gg_pubdir50(sess
, req
);
481 gg_pubdir50_free(req
);
485 // ----------------------------------------------------------------------------
487 void gaduhook::searchdone(void *p
) {
488 gg_pubdir50_t sp
= (gg_pubdir50_t
) p
;
490 if(searchdest
&& lreq
) {
491 for(int i
= 0; i
< sp
->count
; i
++) {
492 icqcontact
*c
= new icqcontact(imcontact(strtoul(gg_pubdir50_get(sp
, i
, GG_PUBDIR50_UIN
), 0, 0), gadu
));
493 icqcontact::basicinfo binfo
= c
->getbasicinfo();
495 const char *p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_NICKNAME
);
498 c
->setdispnick(c
->getnick());
501 p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_FIRSTNAME
);
502 if(p
) binfo
.fname
= p
;
504 p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_LASTNAME
);
505 if(p
) binfo
.lname
= p
;
509 p
= gg_pubdir50_get(sp
, i
, GG_PUBDIR50_STATUS
);
510 if(p
&& atoi(p
) != GG_STATUS_NOT_AVAIL
) line
= "o ";
513 line
+= c
->getnick();
514 if(line
.size() > 12) line
.resize(12);
515 else line
+= string(12-line
.size(), ' ');
517 line
+= " " + binfo
.fname
+ " " + binfo
.lname
;
519 c
->setbasicinfo(binfo
);
521 foundguys
.push_back(c
);
522 searchdest
->additem(conf
.getcolor(cp_clist_gadu
), c
, line
);
525 searchdest
->redraw();
527 log(logSearchFinished
, foundguys
.size());
531 icqcontact
*c
= clist
.get(imcontact(strtoul(gg_pubdir50_get(sp
, 0, GG_PUBDIR50_UIN
), 0, 0), gadu
));
535 if(!c
) c
= clist
.get(contactroot
);
537 icqcontact::basicinfo bi
= c
->getbasicinfo();
538 icqcontact::moreinfo mi
= c
->getmoreinfo();
540 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_FIRSTNAME
);
543 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_LASTNAME
);
546 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_NICKNAME
);
551 if((c
->getnick() == c
->getdispnick())
552 || (c
->getdispnick() == i2str(c
->getdesc().uin
)))
553 c
->setdispnick(nick
);
558 c
->setdispnick(bi
.fname
);
562 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_CITY
);
565 p
= gg_pubdir50_get(sp
, 0, GG_PUBDIR50_GENDER
);
569 if(gender
== GG_PUBDIR50_GENDER_MALE
) mi
.gender
= genderMale
; else
570 if(gender
== GG_PUBDIR50_GENDER_FEMALE
) mi
.gender
= genderFemale
; else
571 mi
.gender
= genderUnspec
;
579 void gaduhook::userstatuschange(unsigned int uin
, int status
, const char *desc
) {
580 icqcontact
*c
= clist
.get(imcontact(uin
, gadu
));
582 imstatus ust
= gg2imstatus(status
);
583 logger
.putonline(c
, c
->getstatus(), ust
);
587 awaymsgs
[uin
] = rusconv("wk", desc
);
593 void gaduhook::userlistsend() {
598 for(i
= 0; i
< clist
.count
; i
++) {
599 c
= (icqcontact
*) clist
.at(i
);
601 if(c
->getdesc().pname
== proto
&& c
->getdesc().uin
)
602 uins
.push_back(c
->getdesc().uin
);
605 auto_ptr
<uin_t
> cuins(new uin_t
[uins
.size()]);
606 /* TODO: allocated with new[], but will be freed by delete */
607 auto_ptr
<char> ctypes(new char[uins
.size()]);
609 for(vector
<uin_t
>::const_iterator iu
= uins
.begin(); iu
!= uins
.end(); ++iu
) {
610 cuins
.get()[iu
-uins
.begin()] = *iu
;
611 ctypes
.get()[iu
-uins
.begin()] = GG_USER_NORMAL
;
614 gg_notify_ex(sess
, cuins
.get(), ctypes
.get(), uins
.size());
617 void gaduhook::usernotify(unsigned int uin
, int status
, const char *desc
,
618 unsigned int ip
, int port
, int version
) {
619 imcontact
ic(uin
, gadu
);
620 icqcontact
*c
= clist
.get(ic
);
624 addr
.s_addr
= ntohl(ip
);
625 char *p
= inet_ntoa(addr
);
626 if(p
) c
->setlastip(p
);
628 imstatus ust
= gg2imstatus(status
);
629 logger
.putonline(c
, c
->getstatus(), ust
);
633 awaymsgs
[ic
.uin
] = rusconv("wk", desc
);
635 awaymsgs
[ic
.uin
] = "";
640 // ----------------------------------------------------------------------------
642 const int token_char_height
= 12;
643 const char token_id_char
[] = {"0123456789abcdef"};
644 const char token_id
[][15] = {
853 static int token_check(int nr
, int x
, int y
, const char *ocr
, int maxx
, int maxy
) {
856 for(i
= nr
*token_char_height
; i
< (nr
+1)*token_char_height
; i
++, y
++) {
859 for(j
= 0; token_id
[i
][j
] && j
+ xx
< maxx
; j
++, xx
++) {
860 if(token_id
[i
][j
] != ocr
[y
* (maxx
+ 1) + xx
])
868 static char *token_ocr(const char *ocr
, int width
, int height
, int length
) {
872 token
= (char *) malloc(length
+ 1);
873 memset(token
, 0, length
+ 1);
875 for(x
= 0; x
< width
; x
++) {
876 for(y
= 0; y
< height
- token_char_height
; y
++) {
877 int result
= 0, token_part
= 0;
880 result
= token_check(token_part
++, x
, y
, ocr
, width
, height
);
881 } while(!result
&& token_part
< 16);
883 if(result
&& count
< length
)
884 token
[count
++] = token_id_char
[token_part
- 1];
896 string
gaduhook::handletoken(struct gg_http
*h
) {
903 if(gg_token_watch_fd(h
) || h
->state
== GG_STATE_ERROR
)
906 if(h
->state
!= GG_STATE_DONE
)
909 if(!(t
= (struct gg_token
*) h
->data
) || !h
->body
)
913 fname
= (getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp");
914 fname
+= "/gg.token." + i2str(getpid()) + i2str(time(0));
915 } while(!access(fname
.c_str(), F_OK
));
917 ofstream
bf(fname
.c_str());
920 bf
.write(h
->body
, h
->body_size
);
928 struct jpeg_decompress_struct j
;
929 struct jpeg_error_mgr e
;
936 if(!(f
= fopen(fname
.c_str(), "rb")))
939 j
.err
= jpeg_std_error(&e
);
940 jpeg_create_decompress(&j
);
941 jpeg_stdio_src(&j
, f
);
942 jpeg_read_header(&j
, TRUE
);
943 jpeg_start_decompress(&j
);
945 size
= j
.output_width
* j
.output_components
;
946 buf
[0] = (JSAMPLE
*) malloc(size
);
948 token
= (char *) malloc((j
.output_width
+ 1) * j
.output_height
);
950 while(j
.output_scanline
< j
.output_height
) {
951 jpeg_read_scanlines(&j
, buf
, 1);
953 for(i
= 0; i
< j
.output_width
; i
++, ih
++)
954 token
[ih
] = (buf
[0][i
*3] + buf
[0][i
*3+1] + buf
[0][i
*3+2] < 384) ? '#' : '.';
959 if((tmp
= token_ocr(token
, j
.output_width
, j
.output_height
, t
->length
))) {
966 jpeg_finish_decompress(&j
);
967 jpeg_destroy_decompress(&j
);
972 unlink(fname
.c_str());