-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathirc-echo-bot.scm
317 lines (270 loc) · 9.65 KB
/
irc-echo-bot.scm
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
;
; irc-echo-bot.scm -- Demo of simple Atomese chatbot.
;
; Demo replicates some of the earliest, simplest and most primitive IRC
; bots. What's different here is that this one is written in Atomese.
; The goal is not to create "yet another bot", but to understand how an
; actual autonomous agent might interact in a simple world.
;
; See `irc-api.scm` for a tutorial on the interfaces used here.
; See `xterm-bridge.scm` for a simple demo of hooking input to output.
;
(use-modules (ice-9 threads))
(use-modules (srfi srfi-1))
(use-modules (opencog) (opencog exec) (opencog sensory))
; Open connection to an IRC server, and attach the stream to an Atom.
; This allows it to be located, when needed. If the Atom is "well
; known", then everyone can find it.
(cog-execute!
(SetValue
(Anchor "IRC Bot") (Predicate "echo")
(Open (Type 'IRChatStream)
(SensoryNode "irc://[email protected]:6667"))))
; Build a pair of read and Write accessors for the above location.
; Using the StreamValueOf automatically dereferences the stream for us.
; Using the naked ValueOf gives direct (read and write) access.
(define bot-read (StreamValueOf (Anchor "IRC Bot") (Predicate "echo")))
(define bot-raw (ValueOf (Anchor "IRC Bot") (Predicate "echo")))
; Individual messages can be read like so:
(cog-execute! bot-read)
; An alternate design would be to finish setting everything up, before
; opening a connection. But we want to demo it's operation as we go
; along. Having a live connection is harmless.
; -------------------------------------------------------
; Set up the basic echo filter.
;
; Messages come in two types: private messages, and channel messages.
; Private messages have the form:
; (LinkValue
; (StringValue "linas")
; (StringValue "echobot")
; (StringValue "bunch o text")))
;
; Public messages have the form:
; (LinkValue
; (StringValue "linas")
; (StringValue "#opencog")
; (StringValue "bunch o text")))
;
; The difference between the two is the target, in the second location.
;
; The filter below, when executed, will pull in a single message,
; pattern match it, and write a private reply to the sender.
; More correctly, executing `make-private-reply` will get one message,
; and rewrite it; but that's it it doesn't send. The private-echo
; below will read-modify-write.
(define make-private-reply
(Filter
(Rule
(VariableList
(Variable "$from") (Variable "$to") (Variable "$msg"))
(LinkSignature (Type 'LinkValue)
(Variable "$from") (Variable "$to") (Variable "$msg"))
(LinkSignature (Type 'LinkValue)
(Item "PRIVMSG")
(Variable "$from")
(Item "you said: ")
(Variable "$msg")))
bot-raw))
(define private-echo (Write bot-raw make-private-reply))
; Try it, once
(cog-execute! private-echo)
; The above works, but is "dangerous": if the bot is placed on a public
; channel, it will echo every post on that channel, back to the user who
; sent it. This will get annoying, fast, and get the bot kicked. Some
; sort of more restrained ehavior is needed.
;
; In a pure agential design, the bot might perceive the kick, and
; perhaps might perceive a kick as painful, and use that as a
; hint to modify it's behavior. However, using evolutionary pressure,
; where users kick annoying bots, is perhaps not a very good way of
; running agents.
;
; This "agential issue" is not solvable in the present context. The rest
; of this demo continues to focus on pipeline processing.
; -------------------------------------------------------
; How about a reply on a public channel?
;
; As noted above, DO NOT join a public channel while the private-echo
; agent is running (in the inf-loop, at bottom). It's incessant replies
; will get it kicked. So we want a message processing pipeline that is
; aware of being on a public channel, and replies only when spoken to.
; To accomplish this, a sequence of increasingly complex pipelines
; are developed below.
; -----------------
; Part one: Some generic boilerplate that all pipelines will use.
; Create a message-processiong rule. Accepts any input consisting
; of a LinkValue holding an IRC message, extracts the three parts,
; and then inserts the list of actoms in CONCLUSION. This is just
; sugar to avoid lots of cut-n-paste.
(define (make-msg-rule CONCLUSION)
(Rule
(VariableList
(Variable "$from") (Variable "$to") (Variable "$msg"))
(LinkSignature (Type 'LinkValue)
(Variable "$from") (Variable "$to") (Variable "$msg"))
(LinkSignature (Type 'LinkValue)
CONCLUSION)))
; Convenience wrapper
(define (make-applier CONCLUSION)
(Filter
(make-msg-rule CONCLUSION)
bot-raw))
; Convenience wrapper. Reads from IRC, extracts message, rewrites
; it into CONCLUSION, writes out to IRC.
(define (make-echoer CONCLUSION)
(WriteLink bot-raw (make-applier CONCLUSION)))
; Most of the demos below need the bot to know it's own name.
(cog-set-value! (Anchor "IRC Bot")
(Predicate "bot-name") (StringValue "echobot"))
; --------
; Below are a collection of examples. Some of these need the bot
; to sit on some public channel, in order to work.
; Join a channel.
(cog-execute! (Write bot-raw (List (Concept "JOIN #opencog"))))
; Leave a channel.
(cog-execute! (Write bot-raw (List (Concept "PART #opencog"))))
; --------
; Example: Show message
(define show (list (Variable "$from") (Variable "$to") (Variable "$msg")))
(cog-execute! (make-applier show))
; --------
; Example: No-op. Do nothing.
(define null-reply (list))
(cog-execute! (make-applier null-reply))
; --------
; Example: Break down message into parts.
; Is it a public or private message?
; It is private if (Variable "$to") is the name of the bot.
(define is-pub?
(Cond
(Equal (Variable "$to")
(ValueOf (Anchor "IRC Bot") (Predicate "bot-name")))
(Item "private message")
(Item "public message")))
(cog-execute! (make-applier is-pub?))
; Create a private reply to the sender, printing mssage diagnostics.
(define id-reply
(list (Item "PRIVMSG") (Variable "$from")
(Item "Message to ")
(Variable "$to")
(Item " is a ")
is-pub?
(Item " from ")
(Variable "$from")
(Item ": ")
(Variable "$msg")))
(cog-execute! (make-echoer id-reply))
; --------
; Example: Is the bot being called out?
; A "callout" is a string that starts with the botname, followed
; by a colon. This is an IRC convention, and nothing more.
(define is-callout?
(Cond
(Equal
(ElementOf (Number 0) (Variable "$msg"))
(ValueOf (Anchor "IRC Bot") (Predicate "bot-name")))
(Item "calls out the bot")
(Item "is just a message")))
(define callout-reply
(list (Item "PRIVMSG") (Variable "$from")
(Item "Message to ")
(Variable "$to")
(Item " is a ")
is-pub?
(Item " from ")
(Variable "$from")
(Item "that ")
is-callout?
(Item ": ")
(Variable "$msg")))
(cog-execute! (make-echoer callout-reply))
; --------
; Example: Reply privately to all messages on a private channel,
; and only those messages on a public channel that call out the bot.
(define private-reply-to-callout
(Cond
(Or
; Is this a private message?
(Equal
(Variable "$to")
(ValueOf (Anchor "IRC Bot") (Predicate "bot-name")))
; Is this a public callout?
(Equal
(ElementOf (Number 0) (Variable "$msg"))
(ValueOf (Anchor "IRC Bot") (Predicate "bot-name"))))
; Always reply privately.
(LinkSignature
(Type 'LinkValue)
(Item "PRIVMSG") (Variable "$from")
(Item "It seems that you said: ")
(Variable "$msg"))
(LinkSignature (Type 'LinkValue))))
(cog-execute! (make-echoer private-reply-to-callout))
; --------
; Example: Reply privately to all messages on a private channel,
; and publicly to callout messages on a public channel.
(define reply-to-callout
(Cond
; Is this a private message?
(Equal
(Variable "$to")
(ValueOf (Anchor "IRC Bot") (Predicate "bot-name")))
; Always reply privately.
(LinkSignature
(Type 'LinkValue)
(Item "PRIVMSG") (Variable "$from")
(Item "It seems that you said: ")
(Variable "$msg"))
; Reply only if called out.
(Equal
(ElementOf (Number 0) (Variable "$msg"))
(ValueOf (Anchor "IRC Bot") (Predicate "bot-name")))
; Reply politely. Note target is $to, not $from
(LinkSignature
(Type 'LinkValue)
(Item "PRIVMSG") (Variable "$to")
(Item "Did ")
(Variable "$from")
(Item " say \"")
(Variable "$msg")
(Item "\"?"))
; Ignore
(VoidValue)))
(cog-execute! (make-echoer reply-to-callout))
; --------
; Example: listen to everything, and write it to a file. This requires
; opening a log-file.
; XXX unfinished...
; -------------------------------------------------------
; Hack for long-running streams. This part of the system is not yet
; fully designed. Details will change.
;
; The `OutputStream::do_write_out()` method does enter an inf loop,
; copying source to sink for as long as the source stays open. For
; whatever reason, that is not happening in this IRC demo, probably
; because all the filters obscure that there is a stream at the far
; end. So this needs work.
;
; So, for now, hack around this. Create an inf loop here, and run it
; in it's own thread. FYI, Tail-recursive loops can also be done in
; Atomese; the below is done in pure scheme.
(define do-exit-loop #f)
(define (inf-loop AGENT)
(cog-execute! AGENT)
(if (not do-exit-loop) (inf-loop AGENT)))
(define thread-id #f)
(define (exit-loop)
(set! do-exit-loop #t)
(join-thread thread-id)
(format #t "Exited main loop\n")
(set! do-exit-loop #f))
; Start an inf loop with the private-echo handler.
;(define thread-id (call-with-new-thread
; (lambda () (inf-loop private-echo))))
; Start an inf loop with the friendly echo handler
(define thread-id (call-with-new-thread
(lambda () (inf-loop (make-echoer reply-to-callout)))))
(exit-loop)
; The End. That's all, folks!
; -------------------------------------------------------