3 * centerim single IM contact class
4 * $Id: icqcontact.cc,v 1.105 2005/02/13 12:10:55 iulica Exp $
6 * Copyright (C) 2001-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 "icqcontacts.h"
26 #include "icqgroups.h"
29 #include "abstracthook.h"
30 #include "imexternal.h"
31 #include "eventmanager.h"
40 icqcontact::icqcontact(const imcontact adesc
) {
42 imevent::imeventtype ie
;
46 lastread
= fhistoffset
= 0;
49 congratulated
= false;
50 openedforchat
= false;
53 for(ie
= imevent::message
; ie
!= imevent::imeventtype_size
; ie
++)
62 fname
= conf
->getdirname() + conf
->getprotocolprefix(cdesc
.pname
);
65 tname
= fname
+ i2str(i
);
66 if(access(tname
.c_str(), F_OK
)) break;
79 icqcontact::~icqcontact() {
82 string
icqcontact::tosane(const string
&p
) const {
86 for(buf
= p
, i
= buf
.begin(); i
!= buf
.end(); ++i
) {
87 if(strchr("\n\r", *i
)) *i
= ' ';
93 string
icqcontact::getdirname() const {
96 ret
= conf
->getdirname();
103 ret
+= conf
->getprotocolprefix(cdesc
.pname
) + i2str(cdesc
.uin
);
106 ret
+= conf
->getprotocolprefix(cdesc
.pname
) + cdesc
.nickname
;
114 void icqcontact::clear() {
115 fupdated
= groupid
= fhistoffset
= lasttyping
= 0;
117 modified
= usepgpkey
= false;
128 nick
= about
= dispnick
= postponed
= lastip
= "";
129 lastseen
= onlinesince
= 0;
132 void icqcontact::save() {
135 if(cdesc
== contactroot
)
138 string lrname
= getdirname() + "lastread";
139 string infoname
= getdirname() + "info";
140 string aboutname
= getdirname() + "about";
141 string postponedname
= getdirname() + "postponed";
143 string dname
= getdirname();
144 dname
.erase(dname
.size()-1);
147 || access(lrname
.c_str(), F_OK
)
148 || access(infoname
.c_str(), F_OK
)
149 || access(aboutname
.c_str(), F_OK
);
151 if(modified
&& conf
->enoughdiskspace()) {
152 mkdir(dname
.c_str(), S_IRUSR
| S_IWUSR
| S_IXUSR
);
154 if(!access(getdirname().c_str(), W_OK
)) {
155 f
.open(lrname
.c_str());
157 f
<< lastread
<< endl
;
162 f
.open(infoname
.c_str());
164 string
options(""); /* set initial value */
165 if(binfo
.requiresauth
) options
+= "a";
166 if(binfo
.authawait
) options
+= "w";
167 if(usepgpkey
) options
+= "p";
170 tosane(binfo
.fname
) << endl
<<
171 tosane(binfo
.lname
) << endl
<<
172 tosane(binfo
.email
) << endl
<<
175 tosane(binfo
.city
) << endl
<<
176 tosane(binfo
.state
) << endl
<<
177 tosane(binfo
.phone
) << endl
<<
178 tosane(binfo
.fax
) << endl
<<
179 tosane(binfo
.street
) << endl
<<
180 tosane(binfo
.cellular
) << endl
<<
181 tosane(binfo
.zip
) << endl
<<
182 binfo
.country
<< endl
<<
183 tosane(winfo
.city
) << endl
<<
184 tosane(winfo
.state
) << endl
<<
185 tosane(winfo
.phone
) << endl
<<
186 tosane(winfo
.fax
) << endl
<<
187 tosane(winfo
.street
) << endl
<<
188 tosane(winfo
.zip
) << endl
<<
189 winfo
.country
<< endl
<<
190 tosane(winfo
.company
) << endl
<<
191 tosane(winfo
.dept
) << endl
<<
192 tosane(winfo
.position
) << endl
<<
193 (int) minfo
.timezone
<< endl
<<
194 tosane(winfo
.homepage
) << endl
<<
195 (int) minfo
.age
<< endl
<<
196 (int) minfo
.gender
<< endl
<<
197 tosane(minfo
.homepage
) << endl
<<
198 minfo
.lang1
<< endl
<<
199 minfo
.lang2
<< endl
<<
200 minfo
.lang3
<< endl
<<
201 minfo
.birth_day
<< endl
<<
202 minfo
.birth_month
<< endl
<<
203 minfo
.birth_year
<< endl
<<
204 (interests
.size() > 0 ? *(interests
.begin()+0) : "") << endl
<<
205 (interests
.size() > 1 ? *(interests
.begin()+1) : "") << endl
<<
206 (interests
.size() > 2 ? *(interests
.begin()+2) : "") << endl
<<
207 (interests
.size() > 3 ? *(interests
.begin()+3) : "") << endl
<<
208 (background
.size() > 0 ? *(background
.begin()+0) : "") << endl
<<
209 (background
.size() > 1 ? *(background
.begin()+1) : "") << endl
<<
210 (background
.size() > 2 ? *(background
.begin()+2) : "") << endl
<<
211 minfo
.checkfreq
<< endl
<<
212 minfo
.checklast
<< endl
<<
214 tosane(dispnick
) << endl
<<
221 tosane(binfo
.avatar
) << endl
;
227 f
.open(aboutname
.c_str());
234 if(!postponed
.empty()) {
235 f
.open(postponedname
.c_str());
242 unlink(postponedname
.c_str());
246 f
.open((getdirname() + "excluded").c_str());
247 if(f
.is_open()) f
.close();
255 void icqcontact::load() {
259 string tname
= getdirname(), fn
, buf
;
261 imcontact savedesc
= cdesc
;
266 f
.open((fn
= tname
+ "/info").c_str());
269 for(i
= 0; getstring(f
, buf
); i
++) {
271 case 0: nick
= buf
; break;
272 case 1: binfo
.fname
= buf
; break;
273 case 2: binfo
.lname
= buf
; break;
274 case 3: binfo
.email
= buf
; break;
276 binfo
.requiresauth
= (buf
.find('a') != -1);
277 binfo
.authawait
= (buf
.find('w') != -1);
278 usepgpkey
= (buf
.find('p') != -1);
280 case 5: pgpkey
= buf
; break;
281 case 6: binfo
.city
= buf
; break;
282 case 7: binfo
.state
= buf
; break;
283 case 8: binfo
.phone
= buf
; break;
284 case 9: binfo
.fax
= buf
; break;
285 case 10: binfo
.street
= buf
; break;
286 case 11: binfo
.cellular
= buf
; break;
287 case 12: binfo
.zip
= buf
; break;
288 case 13: binfo
.country
= strtoul(buf
.c_str(), 0, 0); break;
289 case 14: winfo
.city
= buf
; break;
290 case 15: winfo
.state
= buf
; break;
291 case 16: winfo
.phone
= buf
; break;
292 case 17: winfo
.fax
= buf
; break;
293 case 18: winfo
.street
= buf
; break;
294 case 19: winfo
.zip
= buf
; break;
295 case 20: winfo
.country
= strtoul(buf
.c_str(), 0, 0); break;
296 case 21: winfo
.company
= buf
; break;
297 case 22: winfo
.dept
= buf
; break;
298 case 23: winfo
.position
= buf
; break;
299 case 24: minfo
.timezone
= atoi(buf
.c_str()); break;
300 case 25: winfo
.homepage
= buf
; break;
301 case 26: minfo
.age
= atoi(buf
.c_str()); break;
302 case 27: minfo
.gender
= (imgender
) atoi(buf
.c_str()); break;
303 case 28: minfo
.homepage
= buf
; break;
304 case 29: minfo
.lang1
= strtoul(buf
.c_str(), 0, 0); break;
305 case 30: minfo
.lang2
= strtoul(buf
.c_str(), 0, 0); break;
306 case 31: minfo
.lang3
= strtoul(buf
.c_str(), 0, 0); break;
307 case 32: minfo
.birth_day
= atoi(buf
.c_str()); break;
308 case 33: minfo
.birth_month
= atoi(buf
.c_str()); break;
309 case 34: minfo
.birth_year
= atoi(buf
.c_str()); break;
310 case 35: if(!buf
.empty()) interests
.push_back(buf
); break;
311 case 36: if(!buf
.empty()) interests
.push_back(buf
); break;
312 case 37: if(!buf
.empty()) interests
.push_back(buf
); break;
313 case 38: if(!buf
.empty()) interests
.push_back(buf
); break;
314 case 39: if(!buf
.empty()) background
.push_back(buf
); break;
315 case 40: if(!buf
.empty()) background
.push_back(buf
); break;
316 case 41: if(!buf
.empty()) background
.push_back(buf
); break;
317 case 42: minfo
.checkfreq
= atoi(buf
.c_str()); break;
318 case 43: minfo
.checklast
= atoi(buf
.c_str()); break;
319 case 44: lastip
= buf
; break;
320 case 45: dispnick
= buf
; break;
321 case 46: lastseen
= strtoul(buf
.c_str(), 0, 0); break;
326 case 51: groupid
= atoi(buf
.c_str()); break;
327 case 52: binfo
.avatar
= buf
; break;
334 nick
= i2str(cdesc
.uin
);
339 f
.open((fn
= tname
+ "/about").c_str());
342 while(getstring(f
, buf
)) {
343 if(about
.size()) about
+= '\n';
350 f
.open((fn
= tname
+ "/postponed").c_str());
353 while(getstring(f
, buf
)) {
354 if(postponed
.size()) postponed
+= '\n';
361 f
.open((fn
= tname
+ "/lastread").c_str());
365 lastread
= strtoul(buf
.c_str(), 0, 0);
369 finlist
= stat((fn
= tname
+ "/excluded").c_str(), &st
);
372 unlink((tname
+ "/congratulated").c_str());
375 nick
= cdesc
.nickname
;
380 if(conf
->getgroupmode() != icqconf::nogroups
)
381 if(find(groups
.begin(), groups
.end(), groupid
) == groups
.end()) {
386 bool icqcontact::isbirthday() const {
388 time_t curtime
= time(0);
389 struct tm tbd
, *tcur
= localtime(&curtime
);
391 memset(&tbd
, 0, sizeof(tbd
));
393 tbd
.tm_year
= tcur
->tm_year
;
394 tbd
.tm_mday
= minfo
.birth_day
;
395 tbd
.tm_mon
= minfo
.birth_month
-1;
397 if(tbd
.tm_mday
== tcur
->tm_mday
)
398 if(tbd
.tm_mon
== tcur
->tm_mon
) {
405 void icqcontact::remove() {
406 string dname
= getdirname(), fname
;
411 gethook(cdesc
.pname
).removeuser(cdesc
);
413 if(d
= opendir(dname
.c_str())) {
414 while(e
= readdir(d
)) {
415 fname
= dname
+ "/" + e
->d_name
;
416 if(!stat(fname
.c_str(), &st
) && !S_ISDIR(st
.st_mode
))
417 unlink(fname
.c_str());
420 rmdir(dname
.c_str());
424 void icqcontact::excludefromlist() {
426 string fname
= getdirname() + "excluded";
427 if(f
= fopen(fname
.c_str(), "w")) fclose(f
);
431 void icqcontact::includeintolist(int agroupid
, bool reqauth
) {
432 binfo
.requiresauth
= binfo
.authawait
= reqauth
;
433 if(groupid
) groupid
= agroupid
;
438 void icqcontact::includeintolist() {
439 if(cdesc
.pname
!= icq
)
442 if(!ischannel(cdesc
))
443 gethook(cdesc
.pname
).sendnewuser(cdesc
);
445 unlink((getdirname() + "excluded").c_str());
449 bool icqcontact::inlist() const {
453 void icqcontact::scanhistory() {
454 string fn
= getdirname() + "history", block
;
456 int pos
, backstep
, r
;
457 FILE *f
= fopen(fn
.c_str(), "r");
461 stat(fn
.c_str(), &st
);
464 fseek(f
, 0, SEEK_END
);
470 if(ftell(f
) < backstep
) backstep
= ftell(f
);
472 if(fseek(f
, -backstep
, SEEK_CUR
)) break;
473 if((r
= fread(buf
, 1, backstep
, f
)) <= 0) break;
474 if(fseek(f
, -backstep
, SEEK_CUR
)) break;
477 block
.insert(0, buf
);
479 if((r
= block
.find("\f\nIN\n")) != -1) {
480 pos
= st
.st_size
-block
.size()+r
;
485 ostringstream oevdata
;
487 istringstream
evdata(oevdata
.str());
493 for(r
= 0; (r
< 3) && getline(evdata
, block
); r
++);
494 if(r
== 3) sethasevents(strtoul(block
.c_str(), 0, 0) > lastread
);
500 if(!gethistoffset()) sethistoffset(pos
);
504 void icqcontact::setstatus(imstatus fstatus
, bool reflect
) {
505 if(status
!= fstatus
) {
506 if(!ischannel(cdesc
)) {
507 if(fstatus
!= offline
&& status
== offline
508 || fstatus
== offline
&& status
!= offline
) {
509 imevent::imeventtype et
=
510 status
== offline
? imevent::online
: imevent::offline
;
512 external
.exec(imrawevent(et
, cdesc
, imevent::incoming
));
517 abstracthook
&h
= gethook(cdesc
.pname
);
518 if(fstatus
== offline
) h
.removeuser(cdesc
);
519 else h
.sendnewuser(cdesc
);
523 if (fstatus
== offline
&& status
!= offline
) {
526 else if (fstatus
!= offline
&& status
== offline
) {
527 setonlinesince(time(NULL
));
532 face
.relaxedupdate();
536 void icqcontact::setdesc(const imcontact
&ic
) {
537 string dir
= getdirname();
539 rename(dir
.c_str(), getdirname().c_str());
542 void icqcontact::setnick(const string
&fnick
) {
548 void icqcontact::setdispnick(const string
&fnick
) {
553 void icqcontact::setlastread(time_t flastread
) {
554 lastread
= flastread
;
559 void icqcontact::unsetupdated() {
563 void icqcontact::setbasicinfo(const basicinfo
&ainfo
) {
569 void icqcontact::setmoreinfo(const moreinfo
&ainfo
) {
575 void icqcontact::setworkinfo(const workinfo
&ainfo
) {
581 void icqcontact::setreginfo(const reginfo
&arinfo
) {
585 void icqcontact::setinterests(const vector
<string
> &ainterests
) {
586 interests
= ainterests
;
591 void icqcontact::setbackground(const vector
<string
> &abackground
) {
592 background
= abackground
;
597 void icqcontact::setabout(const string
&data
) {
603 void icqcontact::setsound(imevent::imeventtype event
, const string
&sf
) {
607 void icqcontact::playsound(imevent::imeventtype event
) const {
608 string sf
= sound
[event
], cline
;
613 static time_t lastmelody
= 0;
615 if(time(0)-lastmelody
< 5) return;
618 if(sf
.substr(1) == "spk1") {
619 for(i
= 0; i
< 3; i
++) {
621 setbeep((i
+1)*100, 60);
625 } else if(sf
.substr(1) == "spk2") {
626 for(i
= 0; i
< 2; i
++) {
628 setbeep((i
+1)*300, 60);
632 } else if(sf
.substr(1) == "spk3") {
633 for(i
= 3; i
> 0; i
--) {
634 setbeep((i
+1)*200, 60-i
*10);
637 usleep(90000-i
*10000);
639 } else if(sf
.substr(1) == "spk4") {
640 for(i
= 0; i
< 4; i
++) {
641 setbeep((i
+1)*400, 60);
646 } else if(sf
.substr(1) == "spk5") {
647 for(i
= 0; i
< 4; i
++) {
648 setbeep((i
+1)*250, 60+i
);
651 usleep(90000-i
*5000);
653 } else if(sf
.substr(1) == "spk6") {
659 if(pid
) kill(pid
, SIGKILL
);
662 string cline
= sf
+ " >/dev/null 2>&1";
663 execlp("/bin/sh", "/bin/sh", "-c", cline
.c_str(), (char *)NULL
);
667 } else if(cdesc
!= contactroot
) {
668 icqcontact
*c
= clist
.get(contactroot
);
673 void icqcontact::setlastip(const string
&flastip
) {
679 string
icqcontact::getpgpkey() const {
683 void icqcontact::setpgpkey(const string
&key
) {
685 usepgpkey
= modified
= true;
689 bool icqcontact::getusepgpkey() const {
693 void icqcontact::setusepgpkey(bool usekey
) {
698 string
icqcontact::getabout() const {
702 string
icqcontact::getlastip() const {
706 time_t icqcontact::getlastread() const {
710 imstatus
icqcontact::getstatus() const {
711 if(conf
->getnonimonline(cdesc
.pname
)) return available
;
715 string
icqcontact::getnick() const {
719 string
icqcontact::getdispnick() const {
723 int icqcontact::updated() const {
727 void icqcontact::setlastseen() {
733 time_t icqcontact::getlastseen() const {
737 void icqcontact::setidlefor(unsigned int idle
) {
741 idlefor
-= (idle
* 60);
748 time_t icqcontact::getidlefor() const {
752 char icqcontact::getshortstatus() const {
753 if(status
>= offline
&& status
< imstatus_size
) {
754 return imstatus2char
[getstatus()];
756 return imstatus2char
[offline
];
760 bool icqcontact::operator > (const icqcontact
&acontact
) const {
761 if(lastread
!= acontact
.lastread
) {
762 return lastread
< acontact
.lastread
;
763 } else if(cdesc
.uin
!= acontact
.cdesc
.uin
) {
764 return cdesc
.uin
> acontact
.cdesc
.uin
;
766 return cdesc
.nickname
.compare(acontact
.cdesc
.nickname
);
770 // this is the bizaare compare by last read, then uin, then nickname
771 int icqcontact::compare1(const icqcontact
& a
, const icqcontact
& b
){
772 return a
> b
? 1 : -1;
775 // this is the compare by nickname
776 int icqcontact::compare2(const icqcontact
& a
, const icqcontact
& b
){
777 return a
.getdispnick().compare(b
.getdispnick());
780 void icqcontact::setpostponed(const string
&apostponed
) {
781 if(apostponed
.find_first_not_of(" \r\n") != -1) postponed
= apostponed
;
787 string
icqcontact::getpostponed() const {
791 void icqcontact::setgroupid(int agroupid
, bool reflect
) {
796 gethook(cdesc
.pname
).updatecontact(this);
799 int icqcontact::getgroupid() const {
803 const imcontact
icqcontact::getdesc() const {
807 int icqcontact::gethistoffset() const {
811 void icqcontact::sethistoffset(int aoffset
) {
812 fhistoffset
= aoffset
;
815 void icqcontact::remindbirthday(bool r
) {
816 string tname
= getdirname() + "congratulated";
818 if(!congratulated
&& r
) {
819 congratulated
= !access(tname
.c_str(), F_OK
);
822 ofstream
f(tname
.c_str());
823 if(f
.is_open()) f
.close(), f
.clear();
825 em
.store(imnotification(getdesc(), _("The user has a birthday today")));
826 congratulated
= true;
829 } else if(congratulated
&& !r
) {
830 congratulated
= false;
831 unlink(tname
.c_str());
836 // ----------------------------------------------------------------------------
838 string
icqcontact::moreinfo::strtimezone() const {
841 if(timezone
<= 24 && timezone
>= -24) {
842 r
= abstracthook::getTimezoneIDtoString(timezone
) + ", " +
843 abstracthook::getTimezonetoLocaltime(timezone
);
849 string
icqcontact::moreinfo::strbirthdate() const {
852 static const string smonths
[12] = {
853 _("Jan"), _("Feb"), _("Mar"), _("Apr"),
854 _("May"), _("Jun"), _("Jul"), _("Aug"),
855 _("Sep"), _("Oct"), _("Nov"), _("Dec")
858 if((birth_day
> 0) && (birth_day
<= 31))
859 if((birth_month
> 0) && (birth_month
<= 12)) {
860 r
= i2str(birth_day
) + "-" + smonths
[birth_month
-1] + "-" + i2str(birth_year
);