summaryrefslogtreecommitdiff
path: root/elisp/erbot/erbot.el
blob: 9769dbf8d730fb2880c51f8699d1bf4fe4bae91e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
;;; erbot.el --- Another robot for ERC.
;; Time-stamp: <2009-09-26 22:28:50 fledermaus>
;; Emacs Lisp Archive entry
;; Filename: erbot.el
;; Package: erbot
;; Authors:  David Edmunston (dme@dme.org)
;; Modified by: D. Goel <deego@gnufans.org>, V Dasmohapatra <vivek@etla.org>
;; Version: 0.0
;; URL:  http://www.emacswiki.org/cgi-bin/wiki.pl?ErBot
;; Maintainer: Deepak Goel <deego@gnufans.org>


(defvar erbot-home-page
  "http://www.emacswiki.org/cgi-bin/wiki.pl?ErBot/")

;; Version:
;; Keywords: ERC, IRC, chat, robot, bot

;; Copyright (C) 2002 Deepak Goel, FSF

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.




;; See also:
;; erc-robot.el from which this was derived...




;; See http://www.emacswiki.org/cgi-bin/wiki/ErBot

;; OLD DOCS:
;; Thanks for erbot's/erbot's behavior and their data go to a lot
;; of people on #emacs, like:
;; kensanata (Alex Schroeder)
;; resolve (Damien Elmes)
;; bpt  (Brian P. Templeton)
;; forcer (Jorgen "forcer" Schaefer)
;; and many others

;; and also to bot(s):
;; apt on debian, for english syntax examples.

;; Thanks for code go to:
;; David Edmonsdon (who wrote erc-robot.el which is what this started
;; out from).
;; Nick Hober (who wrote google.el)






;;; David E's Commentary:

;; erbot is a derivative of David's erc-robot.el --- that code was
;; copied over on 2002-09-02 into erbot.el.  Erbot seeks to make the
;; bot similar to apt on #debian.. viz: English style.. yet allowing
;; access to commands via the "cmd" command.  Erbot shall seek to
;; save all its information periodically, and publicly...


;; Erc-robot implements a simple robot for ERC.

;; Installation:

;; The robot uses hooks to gain access to ERC.  The following need to
;; be executed after ERC has loaded:

;;     (load-library "erbot")


;; It is particularly important that the remote robot function is added
;; to the tail of the PRIVMSG hook.

;; Robot commands are declared using the list "erbot-commands".
;; XXX better description of the functions.
;; An example might be:

;; (setq erbot-commands
;;       '(
;; 	("cmds" t (lambda (args)
;; 		  (concat "commands available: "
;; 			  (mapconcat
;; 			   (lambda (e)
;; 			     (car e))
;; 			   erbot-commands " "))))
;; 	("hello" t (lambda (args) "hello to you too !"))
;; 	("zippy" t (lambda (args) (erc-replace-regexp-in-string "\n" " " (yow))))
;; 	("music" t (lambda (args) (concat "now playing: "
;; 					(let ((track (dme:now-playing)))
;; 					  (if track
;; 					      track
;; 					    "nothing.")))))
;; 	("echo" t (lambda (args) args))
;;	; only i'm allowed to talk to my doctor !
;; 	("doctor" nil erc-doctor)
;; 	("version" t (lambda (args) (erc-version)))
;;       ))


; compatability
;(if (featurep 'xemacs)
;    (defun erc-replace-regexp-in-string
;      (regexp rep string &optional fixedcase literal subexp start)
;      (replace-in-string string regexp rep literal))

(defalias 'erc-replace-regexp-in-string 'replace-regexp-in-string)

(defvar erbot-paranoid-p t
  " Meant as a CATCHALL for security. Setting this variable to non-nil
should disable most features. When non-nil, all potentially funny
functions are disabled.  We think these functions are safe, but we
disable them in any case.  We also disable all functions that we can
that may potentially freeze the bot or severly slow it down upon
receiving weird requests.


t by default.  No enablings like erbot-setf-p, etc. will work
unless this is non-nil. If this is non-nil, erbot is paranoid, it will
not allow apply, setf, funcall, sregex, etc. even if the corresponding
variables are turned on.

NOTE: Making this variable nil and later non-nil in the middle of a
running emacs session will NOT make your bot completely paranoid.  You
need to have this function non-nil BEFORE you load erbot. See, for
example, how we define fs-kbd.
")




(defun erbot-commentary ()
  "Provides electric help regarding variable `erbot-commentary'."
  (interactive)
  (with-electric-help
   '(lambda () (insert erbot-commentary) nil) "*doc*"))

;;; History:

;;; Bugs:

;;; New features:
(defvar erbot-new-features
  "Help..."
)

(defun erbot-new-features ()
  "Provides electric help regarding variable `erbot-new-features'."
  (interactive)
  (with-electric-help
   '(lambda () (insert erbot-new-features) nil) "*doc*"))

;;; TO DO:
(defvar erbot-todo
  "Current shortcomings:"

)

(defun erbot-todo ()
  "Provides electric help regarding variable `erbot-todo'."
  (interactive)
  (with-electric-help
   '(lambda () (insert erbot-todo) nil) "*doc*"))

(defvar erbot-version "0.0")

;;==========================================
;;; Code:
(require 'cl)

(defcustom erbot-before-load-hooks nil "" :group 'erbot)
(defcustom erbot-after-load-hooks nil "" :group 'erbot)



(defcustom erbot-ignore-nicks '("^apt[0-9]?$" "bot" "google" "serv")
  "A list of REGEXPS.
Nicks matching these regexps will be ignored by the bot, viz. not
generate replies.

I would suggest including atleast bot, google and serv here to prevent
infinite chat loops with other bots.  :)
"
:type '(repeat regexp)
:group 'erbot)

(defcustom erbot-use-whitelist nil "Use a whitelist for accessing the bot.
Any request from another source will be ignored. If a source is present in whitelist
and in `erbot-ignore-nicks' it is ignored"
:type 'boolean
:group 'erbot)

(defcustom erbot-whitelist-nicks nil
"List of the entries that have access to the bot. Used only when `erbot-use-whitelist' is non-nil"
:type '(repeat regexp)
:group 'erbot)

(defcustom erbot-ignore-userinfos "" "list of regex's" :group 'erbot)
(run-hooks 'erbot-before-load-hooks)


(defgroup erbot nil
  "The group erbot"
   :group 'applications)

(defcustom erbot-nick "fsbot"
"Changing this in the middle of things
may have unspecified and unpleasant results..."
:group 'erbot)

(defvar erbot-end-user-nick "dummy-nick"
  "just a temporary variable..")

(defvar erbot-end-user-nick-latest "dummy-end-user-nick-latest"
  "just a temporary variable..")





(defcustom erbot-servers-channels
      '(("irc.openprojects.net"
         ("#testopn"
            ))
	(".gnome.org"
         ("#testgnome")
	 ;; optional but:
	 6667
	 ))
      "Servers and channels ..."
      :group 'erbot)



;  (defalias 'erc-replace-regexp-in-string 'replace-regexp-in-string))


(defface erbot-face '((t (:foreground "yellow")))
  "Face used for your robot's output."
  :group 'erc-faces)

(defcustom erbot-commands nil
  "A list of robot commands and the functions which implement them."
  :group 'erc
  :type '(repeat (list string (choice (const nil) (const t) string) function))
  )



(defcustom erbot-erbmsg-p nil
  "When true, erball.el loads the erbmsg module by default ")


(defcustom erbot-notify-p t 
  "Set it to t if you want RSS notification
for your erbot. 

Note that even if it is t, we will internally setq it to nil temporarily during
the inner workings of the bot.  ")

;; The next part suggested by forcer, See
;; http://www.kollektiv-hamburg.de/~forcer/erbot-notify.txt, which is
;; also copied here: 

;; erbot should include the following function lists, which are
;; called on these events with the specified arguments:

;;  erbot-notify-add-functions
;;    arguments: nick channel term entry-num entry

(defvar erbot-notify-add-functions nil
  "Functions  to call when an erbot add takes place.  Each of these is
called with the arguments arguments: nick channel term entry-num
entry")

;;  erbot-notify-forget-functions
;;    arguments: nick channel term entry-num entry
;;    If entry-num is 'all, entry is a list of entries


;; SPECS CHANGED!
(defvar erbot-notify-forget-functions nil
  "Functions to call when an erbot forget takes place.  Each of these
is called with the arguments arguments: nick channel term entry-num
entry remaining-entries.  If entry-num is 'all, entry is a list of
entries")

;;  erbot-notify-move-functions
;;    arguments: nick channel old-term new-term

(defvar erbot-notify-move-functions nil
  "Functions to call when an erbot move operation takes place.  Each
of these is called with the arguments arguments: nick channel old-term
new-term ")

;;  erbot-notify-rearrange-functions
;;    arguments: nick channel term from-num from-entry to-num
;;    entries

(defvar  erbot-notify-rearrange-functions nil
  "Functions to call when an erbot rearrange operation takes place.  Each
of these is called with the arguments arguments: nick channel term
from-num from-entry to-num entries.  Entries refers to the rearranged
entries. ")


;;  erbot-notify-substitute-functions
;;    arguments: nick channel term entry-num old-entry new-entry
(defvar erbot-notify-substitute-functions nil 
  "Functions to call when an erbot substitute operation takes place.
Each of these is called with the arguments arguments: nick channel
term entry-num old-entry new-entry")

;;; 2005-08-31 T10:56:27-0400 (Wednesday)    D. Goel
(defvar erbot-nickserv-p nil
  "When t, erbot will load the appropriate erc modules and will try to
auto-identify to nickserv.  
   
If using this, we recommend these settings at the *BEGINNING* of your
bot's .emacs: 

     (setq erbot-nickserv-p t)
     (setq erc-prompt-for-nickserv-password nil) 

     (setq erc-nickserv-passwords
          '((freenode     ((\"mybot\" . \"mypassword\")))))

See this page for more details: 
http://www.emacswiki.org/cgi-bin/wiki?ErcNickserv
")

(when erbot-nickserv-p
  (require 'erc-nickserv nil t) ;; old erc 
  (require 'erc-services nil t) ;; erc from emacs22
  (erc-nickserv-mode 1)
  )




;;  erbot-notify-merge-functions
;;    arguments: nick channel old-term new-term new-entries
;; NOW CHANGED SPEC!
(defvar erbot-notify-merge-functions nil
 "Functions to call when an erbot merge operation takes place.
Each of these is called with the arguments arguments: nick channel
from-term to-term from-entries to-entries final-entries")



; This function is used by the example above.
(defun erbot-doctor (args)
  "Glue the doctor into the ERC robot."
  (let* ((thisbuf (current-buffer))
         (dbuf (concat "*doctor: " (buffer-name thisbuf) "*"))
	 (docbuf (get-buffer dbuf))
	 outpoint
	 res)
    (if (not docbuf)
	(progn
          (set-buffer (get-buffer-create dbuf))
          (make-doctor-variables)
          (set-buffer thisbuf)
	  (setq docbuf (get-buffer dbuf))
	  (bury-buffer docbuf)))
    (save-excursion
      (set-buffer docbuf)
      (goto-char (point-max))
      (insert args)
      (goto-char (point-max))
      (setq outpoint (point))
      (doctor-ret-or-read 1)
      (doctor-ret-or-read 1)
      (goto-char outpoint)
      (re-search-forward "^.")
      (setq outpoint (- (point) 1))
      (re-search-forward "^$")
      (erc-replace-regexp-in-string
       "\n" " " (buffer-substring outpoint (point)))
    )))



(defun erbot-dunnet (arg)
  "Glue the dunnet into the ERC robot."
  (save-excursion
    (let ((freshp nil)
	  outpoint res ans
	  (pre "")
	  full
	  )
      (when (or (not (boundp 'dun-dead)) dun-dead
		(not (get-buffer "*dungeon*"))
		)
	(setq freshp t)
	(setq dun-dead nil))
      (when freshp (dunnet))
      (set-buffer "*dungeon*")
      (goto-char (point-max))
      (when (string-match "save" arg)
	(setq arg "save ~/pub/dunnet/dunnet.game")
	(setq pre "Will save to ~/pub/dunnet/dunnet.game"))
      (cond
       ((string-match "^.?more" arg)
	(setq ans (fsi-more)))
       (t
	(unless freshp (insert arg))
	(goto-char (point-max))
	(setq outpoint (if freshp (point-min) (point)))
	(unless freshp (dun-parse 1))
	(setq ans
	      (buffer-substring-no-properties
	       outpoint (- (point-max) 1)))
	(when (equal arg "quit")
	  (when (kill-buffer "*dungeon*")))))
      (setq full (concat pre ans))
      (when
	  (string-match
	   "I don't understand that"
	   full)
	(setq
	 full
	 (concat
	  full
	  " I am in dunnet mode.  For regular fsbot, type , (dunnet-mode)")))
      full)))

(defvar erbot-quiet-p nil
  "When non-nil, the erbot only listens, never replies")
(defun erbot-quiet ()
  (interactive)
  (setq erbot-quiet-p
	(not erbot-quiet-p))
  (message "set to %S" erbot-quiet-p))

(defvar erbot-quiet-target-p-function nil
  "A function.   The function should take up to 3 arguments, TARGET
\(channel) , nick and msg.  If it returns non-nil, then erbot will
listen and do everything but never reply back.")


(defvar erbot-on-new-erc-p nil
  "Whether we use erc >1.660 with new erc-backend.
The value should not be set but is auto-guessed within
`erbot-install'.")


;; A very very main function..
(defun erbot-remote (proc parsed)
  "Implements a simple robot for erc.  Messages to the robot are of the form:
\"nick: !command args\", where:
nick	- the nickname of the user who is the target of the command,
command	- the specific command,
args	- arguments to the command (optional).

For newer erc, see `erbot-on-new-erc-p' and read the specs of
the new erc-backend functions."
  (set-buffer (process-buffer proc))
  (let* (
	 (erbn-buffer (erc-server-buffer))
	 (sspec (cond (erbot-on-new-erc-p
		       (erc-response.sender parsed))
		      (t (aref parsed 1))))
	 (userinfo (erc-parse-user sspec))
	 (nick (erbutils-remove-text-properties-maybe (nth 0 userinfo)))
	 ;; bind fs-nick in a let.. so that changes to fs-nick are
	 ;; independent and do not affect each other.. when it is
	 ;; parsing too many messages once..
	 (fs-nick nick)
	 (erbn-nick fs-nick)
	 (cmdargs (and erbot-on-new-erc-p
		       (erc-response.command-args parsed)))
	 (tgta 
	  (erbutils-remove-text-properties-maybe 
	   (cond (cmdargs
		  (nth 0 cmdargs))
		 (t (aref parsed 2)))))
	 (tgt (if (equalp tgta (or (erc-current-nick) erbot-nick))
		  nick
		tgta))
	 (erbn-tgt tgt)
	 (fs-tgt tgt)
	 (msg 
	  (erbutils-remove-text-properties-maybe 
          (erc-response.contents parsed)))
	 (erbot-end-user-nick nick)
	 (csys     (if (fboundp 'erc-coding-system-for-target)
		       (erc-coding-system-for-target tgt)
		     'utf-8))
	 (code-in  (if (consp csys) (cdr csys)  csys))
	 (code-out (if (consp csys) (car csys)  csys))
	 )
    ;; changing the structure here..
    ;; also changing erbot-command to erbot-reply..
    ;; from now on, erend-main will take care of what to reply..
    ;; erbot-reply will simply take the reply and reply that...
    ;; should not be setq.. else other invocations may change it..
    ;;(setq erbot-end-user-nick nick)

    (setq erbot-end-user-nick-latest erbot-end-user-nick)
    ;;(setq fs-tgt tgt)
    ;;(setq erbn-tgt tgt)

    ;;(setq fs-nick nick)
    ;;(setq erbn-nick nick)

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;; make sure we decode the raw text we received...
    (unless (multibyte-string-p msg)
      (setq msg (decode-coding-string msg code-in)))
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    (let ((msgg
	   (erbeng-main msg proc nick tgt nil userinfo)))
      ;; erbot-reply needs a correct buffer...
      (set-buffer (process-buffer proc))

      (cond
       (erbot-quiet-p nil)
       ((and erbot-quiet-target-p-function
	     (funcall erbot-quiet-target-p-function tgt nick msg))
	nil)
       (t (erbot-reply
	   msgg
	   proc erbn-nick erbn-tgt msg nil
	   )))

      ))
  nil)


(defun erbot-frob-with-init-string (reply)
  (cond
   ((or (not (stringp reply)) (string= erbot-init-string "")) reply)
   (t
    (with-temp-buffer
      (insert reply)
      (goto-char (point-min))
      (while (re-search-forward "\n" nil t)
	(replace-match
	 (concat "\n" erbot-init-string) nil t))
      (concat erbot-init-string (erbutils-buffer-string))))))

(defvar erbot-init-string ""
  "The basic init string.. should be concated to ALL lines of
replies... right at last.. the values it will hold will look like /msg
foo, and will be set by fs-parse-english, when that function
determines it appropriate..
Currently: we do not use it, since we have found a better way to do
those things..

")

;; this one is probably never used any more... just to make sure,
;; introduced an error command..
;(defun erbot-local (str)
;  "Funnel text typed by the local user to the local robot.  See
;\"erbot-remote\" for details of the command format."
;  (error "foo")
;  (erbot-command erc-process (erc-current-nick) (buffer-name) str t))

(defcustom erbot-reply-p t
  "when nil, don't reply")

(defun erbot-toggle-reply ()
  (interactive)
  (setq erbot-reply-p (not erbot-reply-p))
  (message "erbot-reply-p set to %S" erbot-reply-p)
  )
(defun erbot-reply (main-reply proc from tgt msg locally-generated)
  "Robot worker.  Should do nothing when main-reply is nil or 'noreply
or \"noreply\"

"
  (unless (stringp main-reply)
    (setq main-reply (format "%S" main-reply)))
  (let (
	linen
	(me (or (erc-current-nick) erbot-nick))
	;;(if (and erbot-commands
	;;	     (string-match (concat "^" (regexp-quote me)
	;;				   ": !\\([^ ]+\\) ?\\(.*\\)") msg))
	;;					; this is a robot command to me.
	;;	(let* ((cmd (substring msg (match-beginning 1) (match-end 1)))
	;;	       (args (substring msg (match-beginning 2)))
	;;	       (l (assoc cmd erbot-commands))
	;;	       (allowed-users (nth 1 l))
	;;	       (function (nth 2 l))
	;;	       (permitted (or (eq t allowed-users)
	;;			      (and (eq nil allowed-users) locally-generated)
	;;			      (and (stringp allowed-users)
	;;				   (string-match allowed-users
	;;						 (regexp-quote from)))))



	;;(reply (concat from ": " main-reply))
	;; my frobbing of reply..
	(reply
	 (erbot-frob-with-init-string main-reply))


	(rep-buffer (erc-get-buffer tgt proc)))
    ;;(if permitted
    ;;				  (if l
    ;;				      (funcall function args)
    ;;(concat "unknown command: " cmd
    ;;					    ": try \"cmds\""))
    ;;				(concat "no access to command \"" cmd
    ;;					"\" for " from ".")))))
    (erc-log reply)


    (unless
	(or
	 (null erbot-reply-p)
	 (equal main-reply 'noreply)
	 (equal main-reply "noreply"))
      ;; now we are actually gonna reply.
      (save-excursion
	(setq reply (fs-limit-lines reply))
	(if rep-buffer (set-buffer rep-buffer)
	;;; this alternative reply somehow never gets sent out..
	  ;;(setq reply (concat "msg " from " "
	  ;;		      "No private msgs.. try #testopn"))
	  ;;(set-buffer (erc-get-buffer tgt proc))
	  (progn
	    (ding t)
	    (message "WTF? no rep-buffer? "))
	  )
	       
	(let* ((inhibit-read-only t)
	       (lines (split-string reply "[\n\r]+"))
	       (multiline-p (< 1 (length lines)))
	       p)
	  (mapc
	   (lambda (line)
	     (when (and line
			(not (erbot-safe-p line)))
	       (setq line (erbot-safe-make line)))
	     (goto-char (point-max))
	     (setq p (re-search-backward (erc-prompt)))
	     ;;(insert (erc-format-timestamp) "<" me "> ")
	     (insert ;;(erc-format-timestamp)
	      "<" me "> ")
	     (erc-put-text-property 0 (length line) 'face
				    'erbot-face line)
	     (insert line "\n")
	     (save-excursion
	       (save-match-data
		 (save-restriction
		   (narrow-to-region p (point))
		   (run-hook-with-args 'erc-send-modify-hook)
		   (run-hook-with-args 'erc-send-post-hook))))
	     (set-marker (process-mark erc-process) (point))
	     (set-marker erc-insert-marker (point))
	     (goto-char (point-max))
	     (setq linen (concat line "\n"))
	     ;; fledermaus: I used to force the encoding here, but I now 
	     ;; think that's the wrong thing to do. Hopefully if the data-path 
	     ;; through erc->fsbot->erc is clean, erc will do the right thing 
	     ;; to outbound data.
	     (erc-process-input-line linen nil multiline-p))
	   lines))))))


(defcustom erbot-setf-p nil
  "If you want your bot to allow setf, set this symbol to non-nil at
the beginning of your .emacs")


(defcustom erbot-setf-symbols
  '(caar cadr car cdar cddr cdr eighth elt 
	 first fourth
	 ninth nth
	 nthcdr
	 second
	 seventh sixth
	 subseq substring
	 tenth third)
"Safe symbols for setf...")


;;;###autoload
(defun erbot-install ()
  "Run this function AFTER loading all the files..."
  (interactive)
  (setq erbot-on-new-erc-p
	(and (boundp 'erc-server-PRIVMSG-functions)
	     (featurep 'erc-backend)))
  (cond (erbot-on-new-erc-p
	 (add-hook 'erc-server-PRIVMSG-functions 'erbot-remote t)
	 ;; Do we need this local command thing...?
	 ;;(add-hook 'erc-send-completed-hook 'erbot-local t)
	 (add-hook 'erc-server-001-functions
		   'erbot-autojoin-channels))
	(t
	 (add-hook 'erc-server-PRIVMSG-hook 'erbot-remote t)
	 ;; Do we need this local command thing...?
	 ;;(add-hook 'erc-send-completed-hook 'erbot-local t)
	 (add-hook 'erc-server-001-hook
		   'erbot-autojoin-channels))
	)
  (erbot-install-symbols)
  (when (and erbot-setf-p (not erbot-paranoid-p))
    (erbot-install-setf))
  ;; A running bot should have these nil, else userfunctions will not
  ;; function right:
  (setq eval-expression-print-length nil)
  (setq eval-expression-print-level nil)
  (setq print-length nil)
  (setq print-level nil)
  )



(defun erbot-install-setf ()
  (interactive)
  (defalias 'fs-setf 'setf)
  (require 'cl)
  (let*
      (
       ;; all possible symbols
       ;;(syms 
       ;;(apropos-internal "" (lambda (a) (get a 'setf-method))))
       (syms erbot-setf-symbols)
       (fssyms 
	(mapcar
	 (lambda (a) (intern (format "fs-%s" a)))
	 syms))
       (fsisyms 
	(mapcar
	 (lambda (a) (intern (format "fsi-%s" a)))
	 syms)))
    (mapcar*
     (lambda (a b c) 
       (let ((foo (get a 'setf-method)))
	 (when (fboundp b) (put b 'setf-method foo))
	 (when (fboundp c) (put c 'setf-method foo))))
     syms fssyms fsisyms)))




(defun erbot-install-symbols ()
  "By now, you should have loaded all pertinent erbot files... If you
add any new functions, don't forget to run (erbot-install) AFTER
that.."
  (interactive)
  (let ((ss (fsi-command-list-readonly)))
    (dolist (s ss)
      
      (if (symbolp s)
	  (let ((f-s (erbutils-concat-symbols 'fs- s))
		(fi-s (erbutils-concat-symbols 'fsi- s)))
	    
	    (defalias f-s fi-s)
	    (put f-s 'readonly t))
	(message "Ignoring fsi->fs for %s" s)))))


  

;;;###autoload
(defun erbot-autojoin-channels (server nick)
  ;;(interactive)
  (dolist (l erbot-servers-channels)
    (when (string-match (car l) (process-name server))
      (dolist (chan (cadr l))
        (erc-send-command (concat "join " chan))))))



(defun erbot-get-servers ()
  (mapcar '(lambda (arg) (list (car arg) (caddr arg)))
	  erbot-servers-channels))


;;;###autoload
(defun erbot-alive-p ()
  "Is atleast one connection still alive?"
  ;;(require 'cl-extra)
  (some
   'identity
   (mapcar
    (lambda (buf)
      (save-excursion
	(set-buffer buf)
	(erc-process-alive)))
    (erc-buffer-list))))

(defvar erbot-reconnection-attempts nil)

;;;###autoload
(defun erbot-keep-alive (&rest args)
  "Periodically check if atleast one connection is still alive.  If
not, try to reconnect. "
  (require 'idledo)
  (idledo-add-periodic-action-crude
   '(unless (erbot-alive-p)
      (add-to-list 'erbot-reconnection-attempts
		   (message "Erbot trying to reconnect at %s"
			    (format-time-string
			     "%Y%m%d-%H%M-%S")))
      (ignore-errors (apply 'erbot-join-servers args)))))

;;;###autoload
(defun erbot-join-servers (&optional server port nick
				   user-full-name
				   not-connect-arg passwd)
  "Try to never join if already joined..."
  (interactive)
  (require 'erc)
  (if (null server)
      (mapcar
       '(lambda (arg)
	  (erbot-join-servers
	   (car arg) (cadr arg) nick user-full-name not-connect-arg passwd)
	  (sit-for 1)
	  )

       ;; get the list of servers
       (erbot-get-servers)

       )
    (progn
      ;;(if (null server)
      ;;	  (setq server erc-server))
      ;; 2002-08-21 T11:22:35-0400 (Wednesday)    D. Goel
      (setq erc-current-server-my server)
      (if (null port) 
	  (setq port 
		(if (fboundp 'erc-compute-port)
		    (erc-compute-port)
		  erc-port)))
      (setq nick (or erbot-nick (erc-compute-nick nick)))
      (let* (
	     (foo 'bar)
             (version nil)
	     ;(nick
	     ; (if (erc-already-logged-in server port nick)
	     ;;	  (read-from-minibuffer
	     ;;	   (erc-format-message 'nick-in-use ?n nick)
	     ;;	   nick
	     ;;	   nil nil 'erc-nick-history-list)
	     ;;	nick)))
	     )
	(if (and passwd (string= "" passwd))
	    (setq passwd nil))
	;; 	(while (erc-already-logged-in server port nick)
	;; 	  (setq nick (read-from-minibuffer
	;; 		      (erc-format-message 'nick-in-use ?n nick)
	;; 		      nick
	;; 		      nil nil 'erc-nick-history-list)))

	(run-hook-with-args 'erc-before-connect server port nick)
	(if (string-match "\\(\\<[[:digit:]]+.[[:digit:]]+\\>\\)" 
			  erc-version-string)
	    (setq version (string-to-number 
			   (match-string 1 erc-version-string)))
	  (setq version 0))

	(unless (erc-already-logged-in server port nick)
	  (if (<= 5.0 version)
	      (erc :server    server 
		   :port      port 
		   :nick      nick 
		   :password  passwd
		   :full-name user-full-name)
	  (erc
	   server port nick user-full-name (not not-connect-arg) passwd) )) 
	))))


(defun erbot-safe-make (line)
  (let* ((ans line)
	 (rlist (string-to-list line)))
    (when (string-match "^/" line)
      (unless (string-match "^/me " line)
	(setq ans (concat " " line))))
    (when (member-if (lambda (a) 
		       (and (< a 32)
			    (not (= a 9))))
		     rlist)
      (setq ans "<control characters>"))
    (when (string-match "[\n\r]" line)
      (setq ans " <newlines> "))
    ans))



    

(defun erbot-safe-p (reply)
  "Determine whether a reply is safe.  Any newlines are simply
reported as unsafe.

If this functions deems a reply as unsafe, you should not send it to
ERC but call `erbot-safe-make' first. "
  (and
   (not (string-match "[\n\r]" reply))
    ;; err on the side of caution.  Demand that the 1st char. be VERY
   ;; safe.  
   (or
    (string-match "^[0-9a-zA-Z]" reply)
    ;;(not (string-match "^/" reply)) -- this is bad.. since, control
    ;;characters are bad... beginnning ^A for example, will send CTCP requests..
    
    ;; Allow /me commands.. but only when the rest of the text has no
    ;; control characters..
    (equal 0 (string-match "^/me " reply)))
   ;; And there be no control characters whatsoever anywhere.
   (erbot-safe-nocontrol-p reply)))

(defun erbot-safe-nocontrol-p (reply)
  (let ((rlist (string-to-list reply)))
    (not (member-if (lambda (a) (< a 32)) rlist))))






(defun erbot-dunnet-install ()
  "Defines some dunnet specific aliases. "
  (interactive)
  (require 'dunnet)
  (defalias 'dun-read-line 'fs-botread)
  ;;(defalias 'dun-mprinc
  ;;'fs-dun-mprinc))
  )


(defmacro erbot-working (&rest args)
  `(let ((erbbdb-save-p nil)
	 (erbot-notify-p nil))
     ,@args))



(provide 'erbot)
(run-hooks 'erbot-after-load-hooks)



;;; erbot.el ends here