Allow disabling auto-away/auto-na timeouts.
[emchat] / emchat.el
1 ;;; emchat.el --- IM client for (S)XEmacs
2
3 ;; Copyright (C) 2000 - 2011 Steve Youngs
4
5 ;; Maintainer:     Steve Youngs <steve@emchat.org>
6 ;; Created:        Aug 08, 1998
7 ;; Homepage:       http://www.emchat.org/
8 ;; Keywords:       comm ICQ
9
10 ;; This file is part of EMchat.
11
12 ;; Redistribution and use in source and binary forms, with or without
13 ;; modification, are permitted provided that the following conditions
14 ;; are met:
15 ;;
16 ;; 1. Redistributions of source code must retain the above copyright
17 ;;    notice, this list of conditions and the following disclaimer.
18 ;;
19 ;; 2. Redistributions in binary form must reproduce the above copyright
20 ;;    notice, this list of conditions and the following disclaimer in the
21 ;;    documentation and/or other materials provided with the distribution.
22 ;;
23 ;; 3. Neither the name of the author nor the names of any contributors
24 ;;    may be used to endorse or promote products derived from this
25 ;;    software without specific prior written permission.
26 ;;
27 ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
28 ;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29 ;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30 ;; DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
34 ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
35 ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
36 ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
37 ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39 ;;; Commentary:
40 ;;
41 ;; Clone of Mirabilis ICQ communication client.
42 ;;
43 ;; Entry points:
44 ;;   emchat-login
45 ;;   emchat-show-window
46 ;;   emchat-customize
47 ;;
48 ;; See README & INSTALL which come with this package
49 ;;
50 ;; This project is done without the consent of Mirabilis.
51 ;;
52
53 ;;; Code:
54
55 (eval-and-compile
56   (require 'emchat-utils)
57   (require 'timezone)
58   (require 'outline)
59   (require 'emchat-doctor))
60
61 (eval-when-compile
62   (defvar emchat-add-user-success)
63   (defvar emchat-user-status)
64   (defvar emchat-user-initial-status)
65   (defvar emchat-buddy-buffer)
66   (defvar emchat-buddy-window-width)
67   (defvar emchat-status-buffer)
68   (defvar emchat-status-use-gutter)
69   (defvar emchat-status-window-height)
70   (defvar emchat-wharf-frame)
71   (defvar seq-num-bin)
72   (defvar seq-num)
73   (defvar user-bin)
74   (defvar local-year)
75   (defvar emchat-fix-nick)
76   (defvar emchat-wharf-frame-use-p)
77   (require 'ehelp)
78   (require 'cus-edit)
79   (require 'browse-url)
80   (require 'passwd)
81   (require 'regexp-opt)
82   (require 'toolbar-utils)
83   (autoload 'emchat-wharf-dec-messages "emchat-wharf"))
84
85 (autoload 'emchat-status-auto-reply "emchat-status")
86 (autoload 'emchat-status-idle-reply "emchat-status")
87 (autoload 'emchat-status-name "emchat-status")
88 (autoload 'emchat-change-status "emchat-status" nil t)
89 (autoload 'emchat-status-show-buffer "emchat-status" nil t)
90 (autoload 'emchat-update-tab-in-gutter "emchat-status")
91 (autoload 'emchat-status-v8 "emchat-status")
92 (autoload 'emchat-buddy-update-status "emchat-status")
93 (autoload 'emchat-buddy-selected-in-view "emchat-buddy")
94 (autoload 'emchat-buddy-show-buffer "emchat-buddy" nil t)
95 (autoload 'emchat-buddy-select-all-in-view "emchat-buddy")
96
97 ;; Customize Groups.
98
99 (defgroup emchat nil
100   "Mirabilis ICQ communication client."
101   :group 'comm)
102
103 (defgroup emchat-info nil
104   "Essential account info."
105   :group 'emchat)
106
107 (defgroup emchat-option nil
108   "System settings and general preferences."
109   :group 'emchat)
110
111 (defgroup emchat-sound nil
112   "Sound preferences."
113   :group 'emchat)
114
115 (defgroup emchat-interface nil
116   "Change the look and \"feel\"."
117   :group 'emchat)
118
119 ;; Customize.
120 ;;;###autoload
121 (defcustom emchat-directory (file-name-as-directory
122                            (expand-file-name ".emchat" (user-home-directory)))
123   "*All EMchat support files and directories hang off this."
124   :type 'directory
125   :group 'emchat)
126
127 ;; Because of the incredibly complex and hairy twisted maze of
128 ;; inter-connections between the different EMchat libs, these
129 ;; emchat-history defcustoms are here instead of in
130 ;; emchat-history.el. --SY.
131 (defgroup emchat-history nil
132   "History preferences."
133   :prefix "emchat-history-"
134   :group 'emchat)
135
136 (defcustom emchat-history-enabled-flag nil
137   "*Non-nil means keep \"per-user\" histories."
138   :group 'emchat-history
139   :type 'boolean)
140
141 (defcustom emchat-history-directory
142   (file-name-as-directory (expand-file-name "history" emchat-directory))
143   "*Directory path for storing \"per-user\" history files."
144   :type 'directory
145   :group 'emchat-history)
146
147 (defcustom emchat-history-mode-hook nil
148   "*Hooks run in `emchat-history-mode'."
149   :type 'hook
150   :group 'emchat-history)
151
152 ;; This is here and not at the top because some of these libs use
153 ;; emchat-directory
154 (eval-and-compile
155   (require 'emchat-log)
156   (require 'emchat-meta)
157   (require 'emchat-world)
158   (require 'emchat-v8)
159   (require 'emchat-version))
160
161 (defcustom emchat-server "login.icq.com"
162   "*Server host to connect to."
163   :type 'string
164   :group 'emchat)
165
166 (defcustom emchat-port 5401
167   "*Port to connect to."
168   :type 'number
169   :group 'emchat)
170
171 ;;;###autoload(autoload 'emchat-prefix "emchat-menu" nil nil 'keymap)
172 (defun emchat-install-bindings (&optional sym value)
173   (when (eq (key-binding (symbol-value sym)) emchat-prefix)
174     (global-set-key (symbol-value sym) nil)) ; unbind old
175   (if (key-binding value)
176       (progn
177         (lwarn 'binding 'warning
178           "%S already bound, reseting `emchat-prefix-key'" value)
179         (set sym nil))
180     (global-set-key value emchat-prefix)
181     (set sym value)))
182
183 (defcustom emchat-prefix-key [(meta ?`)]
184   "*Default global prefix key for EMchat.
185
186 If you change this outside of the customize buffer you _MUST_ use
187 `customize-set-variable', not `setq'."
188   :type 'sexp
189   :set 'emchat-install-bindings
190   :initialize 'custom-initialize-default
191   :group 'emchat)
192
193 (defcustom emchat-use-sound-flag nil
194   "*Whether to use sound or not."
195   :group 'emchat-sound
196   :type 'boolean
197   :tag "Use Sound")
198
199 (defcustom emchat-sound-directory
200   (file-name-as-directory (expand-file-name "sounds" emchat-directory))
201   "*Directory where sound files are kept."
202   :group 'emchat-sound
203   :type 'directory
204   :tag "emchat-sound-directory")
205
206 (defcustom emchat-sound-alist
207   '((message-sound . nil)
208     (chat-sound . nil)
209     (url-sound . nil)
210     (buddy-sound . nil)
211     (auth-sound . nil)
212     (emailx-sound . nil)
213     (pager-sound . nil)
214     (system-sound . nil))
215   "*Sound event to sound file alist.
216 The possible sound events are:
217       \"message-sound\" - Incoming message sound.
218       \"chat-sound\"    - Incoming chat request sound.
219       \"url-sound\"     - Incoming url sound.
220       \"buddy-sound\"   - Online notify sound.
221       \"auth-sound\"    - Authorise sound.
222       \"emailx-sound\"  - Email express sound.
223       \"pager-sound\"   - Pager sound.
224       \"system-sound\"  - System message sound."
225   :group 'emchat-sound
226   :type '(repeat
227           (cons (sexp :tag "Sound Event")
228                 (sexp :tag "Sound File")))
229   :tag "Sounds")
230
231 (defcustom emchat-coding-system
232   (when (featurep '(or mule file-coding))
233     (if (eq default-buffer-file-coding-system 'cyrillic)
234         (find-coding-system 'windows-1251)
235       default-buffer-file-coding-system))
236   "*Coding for incoming and outgoing messages.
237 This feature is supported only in Emacs with MULE
238 Nil means not to use any codings.
239 See `list-coding-systems'."
240   :group 'emchat-option
241   :type (append '(choice (item nil))
242                 (when (fboundp 'coding-system-list)
243                   (mapcar
244                    #'(lambda (x)
245                        (list 'item x))
246                    (coding-system-list)))))
247
248 (defcustom emchat-auto-response-messages-p t
249   "Set this to non-NIL to send automatic messages.
250 The automatic messages are those that are sent when somebody
251 sends you a message while you are 'away', 'na', 'dnd', or 'occ'."
252   :tag "Send auto-response messages."
253   :type 'boolean
254   :group 'emchat-option)
255
256 (defcustom emchat-auto-reply-away
257   "I am currently away from the computer.
258
259 If you would like to be notified when I am back online
260 send me a message with \",,notify-me\" in it.
261
262 This message has been automatically sent to you
263 by the (S)XEmacs IM client \"EMchat\".
264 <http://www.emchat.org/>"
265   "Auto reply with this when you are away."
266   :group 'emchat-option)
267
268 (defcustom emchat-auto-reply-occ
269   "I am currently occupied.
270
271 If you would like to be notified when I am back online
272 send me a message with \",,notify-me\" in it.
273
274 This message has been automatically sent to you
275 by the (S)XEmacs IM client \"EMchat\".
276 <http://www.emchat.org/>"
277   "Auto reply with this when you are occupied."
278   :group 'emchat-option)
279
280 (defcustom emchat-auto-reply-dnd
281   "Hey, the sign on the door says \"Do Not Disturb\"!
282
283 Leave me a message, if you feel you must.
284 I might get back to you.
285
286 If you would like to be notified when I am back online
287 send me a message with \",,notify-me\" in it.
288
289 This message has been automatically sent to you
290 by the (S)XEmacs IM client \"EMchat\".
291 <http://www.emchat.org/>"
292   "Auto reply with this when you want to leave alone."
293   :group 'emchat-option)
294
295 (defcustom emchat-auto-reply-na
296   "I am currently not available.
297
298 If you would like to be notified when I am back online
299 send me a message with \",,notify-me\" in it.
300
301 This message has been automatically sent to you
302 by the (S)XEmacs IM client \"EMchat\".
303 <http://www.emchat.org/>"
304   "Auto reply with this when you are not available."
305   :group 'emchat-option)
306
307 ;; FIXME: How can I make this display how long we've been away
308 (defcustom emchat-idle-reply-away
309    "I must be too busy to talk because I have
310 been idle now for at least...seconds
311
312 If you would like to be notified when I am back online
313 send me a message with \",,notify-me\" in it.
314
315 This message has been automatically sent to you
316 by the (S)XEmacs IM client \"EMchat\".
317 <http://www.emchat.org/>"
318   "Auto reply with this when you have idled away."
319   :group 'emchat-option)
320
321 ;; FIXME: How can I make this display how long we've been away
322 (defcustom emchat-idle-reply-na
323    "I must be too busy to talk because I have
324 been idle now for at least...seconds
325
326 If you would like to be notified when I am back online
327 send me a message with \",,notify-me\" in it.
328
329 This message has been automatically sent to you
330 by the (S)XEmacs IM client \"EMchat\".
331 <http://www.emchat.org/>"
332   "Auto reply with this when you have idled to na."
333   :group 'emchat-option)
334
335 (defcustom emchat-auto-response-never-send-to nil
336   "*This is a list of people that shouldn't get auto-responses.
337
338 When you add someone's alias here and they send you a message while
339 your status would cause an automatic response to be sent, they won't
340 be sent one."
341   :type '(repeat (string :tag "Alias"))
342   :group 'emchat-option)
343
344 (defcustom emchat-oops-msg-wrong-recipient
345   "That last message was meant for somebody else.
346 Sorry about that. :-)"
347   "*The \"apology\" sent when you send to the wrong person."
348   :type 'string
349   :group 'emchat-option)
350
351 (defcustom emchat-start-in-new-frame nil
352   "*If non-NIL, EMchat will start in its own frame."
353   :group 'emchat-interface
354   :type 'boolean)
355
356 (defcustom emchat-new-message-hook nil
357   "*Hooks to run when there is an incoming message.
358 Dynamically ALIAS and MESSAGE are binded to be used in hooks."
359   :group 'emchat-option
360   :type 'hook)
361
362 (defcustom emchat-read-message-hook nil
363   "*Hooks run when a message is marked as \"read\"."
364   :group 'emchat-option
365   :type 'hook)
366
367 (defcustom emchat-system-message-hook nil
368   "*Hooks run when a \"system\" message is received."
369   :group 'emchat-option
370   :type 'hook)
371
372 (defcustom emchat-load-hook nil
373   "*Hooks run after EMchat has loaded everything up."
374   :type 'hook
375   :group 'emchat-option)
376
377 (defcustom emchat-missed-message-hook nil
378   "*Hooks run when SRV_MISSED_ICBM packet comes in.
379
380 This is usually when you are getting too many incoming messages at
381 once.  You can use this hook, for example to send back a \"please
382 resend\" message to the original sender.
383
384 It is called with 3 arguments:
385
386   ALIAS -- The alias/UIN of the person who sent the message that
387            caused the SRV_MISSED_ICBM packet to be sent. \(string\)
388     NUM -- The number of missed messages. \(integer\)
389  REASON -- The reason that the messages were dropped. \(string\)"
390   :type 'hook
391   :group 'emchat-option)
392
393 ;; Some debugging counters.  Do NOT set any of these.
394 (defvar emchat-dropped-packet-counter 0
395   "For debug purpose only.")
396
397 (defvar emchat-resend-packet-counter 0
398   "For debug purpose only.")
399
400 (defvar emchat-recent-packet nil
401   "The most recent incoming packet.
402 For debug only.")
403
404 (defvar emchat-trimmed-packet-counter 0
405   "For debug purpose only.")
406
407 (defvar emchat-error-packets nil
408   "A list of error incoming packets.
409 For debug only.")
410
411 (defcustom emchat-about-fields
412   '((:nick . "Nick Name")
413     (:first-name . "First Name")
414     (:second-name . "Surname")
415     (:email . "Email")
416     (:country . "Country")
417     (:city . "City")
418     (:state . "State")
419     (:zip . "Postal Code")
420     (:phone . "Phone")
421     (:fax . "Fax")
422     (:cellular . "Cellular")
423     (:flags . "Flags")
424     (:web-indicator . "Web Indicator"))
425   "*Alist of field . field-name for basic info queries."
426   :type '(repeat (cons :tag "Field"
427                        (choice :tag "Field Keyword"
428                                (const :tag "Nick Name" :value :nick)
429                                (const :tag "First Name" :value :first-name)
430                                (const :tag "Second Name" :value :second-name)
431                                (const :tag "Email" :value :email)
432                                (const :tag "Country" :value :country)
433                                (const :tag "City" :value :city)
434                                (const :tag "State" :value :state)
435                                (const :tag "Phone" :value :phone)
436                                (const :tag "Fax" :value :fax)
437                                (const :tag "Street" :value :street)
438                                (const :tag "Cellular" :value :cellular)
439                                (const :tag "ZIP Code" :value :zip)
440                                (const :tag "Flags" :value :flags)
441                                (const :tag "Web Indicator" :value :web-indicator))
442                        (string :tag "Field Name")))
443   :group 'emchat)
444
445 (defcustom emchat-about-more-fields
446   '((:age . "Age")
447     (:gender . "Gender")
448     (:homepage . "Homepage")
449     (:birth-year . "Birth Year")
450     (:birth-month . "Birth Month")
451     (:birth-day . "Birth Day")
452     (:lang1 . "Language")
453     (:lang2 . "Second Language")
454     (:lang3 . "Third Language")
455     (:ocity . "Old City")
456     (:ostate . "Old State")
457     (:ocountry . "Old Country")
458     (:marital . "Marital Status"))
459   "*Alist of field . fieldname for extended info queries."
460   :type '(repeat (cons :tag "Field"
461                        (choice :tag "Field Keyword"
462                                (const :tag "Age" :value :age)
463                                (const :tag "Gender" :value :gender)
464                                (const :tag "Homepage" :value :homepage)
465                                (const :tag "Birth Year" :value :birth-year)
466                                (const :tag "Birth Month" :value :birth-month)
467                                (const :tag "Birth Day" :value :birth-day)
468                                (const :tag "Language" :value :lang1)
469                                (const :tag "Second Language" :value :lang2)
470                                (const :tag "Third Language" :value :lang3)
471                                (const :tag "Originate City" :value :ocity)
472                                (const :tag "Originate State" :value :ostate)
473                                (const :tag "Originate Country" :value :ocountry)
474                                (const :tag "Marital" :value :marital))
475                        (string :tag "Documentation")))
476   :group 'emchat)
477
478 (defcustom emchat-auth-accept-reason "You are AUTHORISED!"
479   "*Default reason for rejecting incoming auth requests."
480   :type 'string
481   :group 'emchat)
482
483 (defcustom emchat-auth-reject-reason "Authorisation Rejected!"
484   "*Default reason for rejecting incoming auth requests."
485   :type 'string
486   :group 'emchat)
487
488 (defcustom emchat-auth-request-reason "Please add me to your contact list"
489   "*Message to send with outgoing auth requests."
490   :type 'string
491   :group 'emchat)
492
493 (defun emchat-init-visible-list (&rest args)
494   "Initialises the default value for `emchat-visible-contacts'."
495   (when (file-readable-p emchat-world-rc-filename)
496     (emchat-world-update)
497     (mapcar
498      #'(lambda (e)
499          (car e))
500      emchat-world)))
501
502 (defcustom emchat-visible-contacts (emchat-init-visible-list)
503   "*List of contacts on your \"visible\" list."
504   :type '(repeat (string :tag "Contact Alias Name"))
505   :initialize #'custom-initialize-reset
506   :get #'emchat-init-visible-list
507   :set #'custom-set-default
508   :group 'emchat)
509
510 (defcustom emchat-invisible-contacts nil
511   "*List of contacts on your \"invisible\" list."
512   :type '(repeat (string :tag "Contact Alias Name"))
513   :group 'emchat)
514
515 ;;; Internal variables
516 (defcustom emchat-user-password nil
517   "*Password for your ICQ account.
518 Nil means prompt for entering password every time you login."
519  :group 'emchat-info)
520
521 (defvar emchat-ctx nil
522   "Current emchat context in emchat-v8 protocol.
523 Internal variable, do not modify.")
524
525 ;;;###autoload
526 (defun emchat-version (&optional arg)
527   "Return the version of emchat you are currently using.
528 If ARG, insert version string at point."
529   (interactive "P")
530   (if arg
531       (insert (message "EMchat: %s" emchat-version))
532     (message "EMchat: %s" emchat-version)))
533
534 ;;;###autoload
535 (defun emchat-copyright ()
536   "*Display the copyright notice for EMchat."
537   (interactive)
538   (with-electric-help
539    '(lambda ()
540       (insert
541        (with-temp-buffer
542          (erase-buffer)
543          (insert-file-contents (locate-library "emchat.el"))
544          (goto-char (point-min))
545          (re-search-forward ";;; Commentary" nil t)
546          (beginning-of-line)
547          (narrow-to-region (point-min) (point))
548          (while (re-search-backward "^;+ ?" nil t)
549            (replace-match "" nil nil))
550          (buffer-string (current-buffer)))))
551    "*EMchat Copyright Notice*"))
552
553 (defconst emchat-donation-notice
554   "EMchat is an Open Source project and we have had a lot of fun in
555 getting it into your hands.  But this project is NOT a \"for profit\"
556 organisation.  We do not receive any funding, Government grants, or
557 subsidies of any kind.  None of us who are involved with the project
558 are remunerated in any fashion for what we do with EMchat.  We are all
559 just volunteers, coding in our spare time.
560
561 Often the end user doesn't realise that their \"free\" software has
562 come at some considerable cost.  Costs and expenses like...
563
564     Bandwidth and ISP expenses
565     Hardware updates and maintenance expenses
566     Hosting expenses
567     Domain name registrations
568     Electricity and other utility expenses
569     Outrageous amounts of coffee for all-night coding sessions
570
571 If you have found this software useful/cool/entertaining please
572 consider dipping into your hard earned and making a donation.  Doing
573 so will give you the eternal gratitude and thanks from the EMchat
574 team, and think of the warm fuzzies you'll get.
575
576 Seriously, even if you decide against making a donation at this time,
577 I would like to sincerely thank you for at least taking the time to
578 consider it.  I hope you enjoy EMchat as much as we have enjoyed writing
579 it for you.
580
581 Steve Youngs
582 EMchat Project Lead.
583
584 \t\t [Donate]\t\t\t [Cancel]
585
586 \t\t"
587   "Contents of donation buffer.")
588
589 (defconst emchat-paypal-glyph
590   "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
634 YII="
635   "A base64 encoded paypal donate button.")
636
637 (defconst emchat-maybe-later-glyph
638   "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"
681   "A base64 encoded png \"Maybe Later\" button.")
682
683 (defun emchat-make-donation ()
684   "Proceed with making a donation to the EMchat project."
685   (interactive)
686   (browse-url "http://tinyurl.com/2uzel4")
687   (kill-buffer "*emchat-donate*"))
688
689 (defun emchat-no-donation ()
690   "Don't make a donation to the EMchat project."
691   (interactive)
692   (kill-buffer "*emchat-donate*"))
693
694 (defconst emchat-donation-map
695   (let* ((map (make-sparse-keymap 'emchat-donation-map)))
696     (define-key map [button1] 'emchat-make-donation)
697     (define-key map [button2] 'emchat-make-donation)
698     (define-key map [button3] 'emchat-make-donation)
699     (define-key map [return] 'emchat-make-donation)
700     map)
701   "A keymap for the extents in the EMchat donation buffer.")
702
703 (defconst emchat-nodonation-map
704   (let* ((map (make-sparse-keymap 'emchat-nodonation-map)))
705     (define-key map [button1] 'emchat-no-donation)
706     (define-key map [button2] 'emchat-no-donation)
707     (define-key map [button3] 'emchat-no-donation)
708     (define-key map [return] 'emchat-no-donation)
709     map)
710   "A keymap for the extents in the EMchat donation buffer.")
711
712 (defun emchat-donation ()
713   "Make a donation to the EMchat project via PayPal."
714   (interactive)
715   (let ((buf (get-buffer-create "*emchat-donate*"))
716         (donate-help "Make a donation to the EMchat team.")