retroforth/example/irc-bot.retro
crc 36c8929528 new example - irc bot
FossilOrigin-Name: acf3c516f8be44c3ed67e7bb6bb260272d1a4adea9509e1e946a4f1964d9668b
2019-10-14 19:30:08 +00:00

207 lines
4.4 KiB
Text

This is a little IRC bot written in RETRO. Currently it's being
used for testing the socket: words. In the near future, it'll
also be used for logging and interactive tests. Longer term,
a future version will be used as part of my Para order and bid
management system, for interactive lookups and updates.
Consider this to be public domain, or use under the ISC license
if your country does not recognize the public domain.
Todo:
- support direct messages
- support retro-describe: queries
In Process:
- logging of channel
- append to log working
- need to add timestamps
- need to only log things we care about:
- join part quit privmsg
- tools to generate more readable output from the
raw logs
Done:
- add a configuration section
- connect to IRC server
- join a channel
- handle PING
- process messages
- run retro code in a separate vm/image
- image persists across runs
- stacks reset each run
- support multiple channels
- wrappers over socket: words
Possible future additions:
- per-user images for the retro-via-pipe
----
Configuration
I'm connecting to one of the plaintext ports on Freenode. I have
the server IP hard coded here, but the socket words do work with
domain names.
~~~
'/home/crc/retro-irc.image 'IMAGE s:const
'retro-sandboxed 'VM s:const
'193.10.255.100:6665 'SERVER s:const
'retroforth-bot 'NICK s:const
{ '#retro '#forth '#retrotesting } 'CHANNELS const
~~~
These words allow me to capture output into a buffer, which
I can send out via the socket words. It's best if you use
these in a `buffer:preserve` block.
~~~
'Output d:create
#32768 allot
:capture{ &Output buffer:set &buffer:add &c:put set-hook ;
:}output &c:put unhook ;
~~~
This is a higher level set of words for interacting with a
single socket.
~~~
'Sock var
:connect (s-)
socket:create !Sock
$: s:split swap n:inc socket:configure
@Sock socket:connect drop ;
:s:transmit @Sock socket:send drop-pair ;
{{
:eol?
[ ASCII:CR eq? ] [ ASCII:LF eq? ] bi or ;
:terminate
buffer:get drop #0 buffer:add ;
---reveal---
:s:receive
[ here buffer:set
s:empty dup #256 @Sock socket:recv drop-pair
[ dup buffer:add eol? [ terminate ] if ] s:for-each ] buffer:preserve ;
}}
:disconnect
@Sock socket:close ;
~~~
Create a socket and connect to the IRC network. I'm using
Freenode on a plain text port.
~~~
SERVER connect
~~~
Next, some helpers. One to send a line of text to the server and
one to read a line from the server.
~~~
:irc:send
'%s\r\n s:format s:transmit ;
~~~
Log into the desired channel. Adjust your user name, nick, and
the channel(s) here.
~~~
:login 'USER_retrobot_crc@forth.works_forth.works_charles irc:send
NICK 'NICK_%s s:format irc:send
CHANNELS [ 'JOIN_%s s:format irc:send ] a:for-each ;
login
~~~
The server will send a PING periodically to see if we are still
active. Send the correct PONG message back in reply.
~~~
:ping?
here 'PING s:begins-with? ;
:pong
'PONG here #4 + s:append irc:send ;
~~~
Messages in the channel contain PRIVMSG. Here is where I
process them. If the actual message starts with `retro:`,
I pass it to `s:evaluate`, capturing and relaying the output.
~~~
'Channel d:create
#128 allot
:message?
here 'PRIVMSG s:contains-string? ;
:channel
here 'PRIVMSG s:split-on-string drop #8 + $: s:split nip s:chop &Channel s:copy ;
:transmit
'PRIVMSG_ s:transmit
&Channel s:transmit
'_: s:transmit
&Output s:transmit
'\r\n s:format s:transmit ;
:export (ss-
[ '~~~\n%s\n;\n~~~ s:format ] dip file:spew ;
:run (s-s)
[ dup IMAGE VM '%s_-u_%s_-f_%s_>%s.out s:format unix:system ] sip ;
:send-results
[ '.out s:append [ &Output s:copy transmit ] file:for-each-line ] sip ;
:cleanup
dup '.out s:append [ file:delete ] bi@ ;
:sandbox
#7 + n:random '/tmp/retro-%n s:format
[ export ] sip run send-results cleanup ;
:process (s-
channel sandbox ;
:command
dup 'retro:_ s:begins-with? [ process ] [ drop ] choose ;
:process
here 'PRIVMSG s:split-on-string drop $: s:split drop n:inc dup s:put nl
command ;
~~~
~~~
:log
here s:put nl
'irc.log file:A file:open here
[ over file:write ] s:for-each ASCII:LF over file:write file:close ;
~~~
Run the main loop to read and send messages.
~~~
[ repeat
s:receive log
message? [ process ] if
ping? [ pong ] if
again ] call
~~~
Clean up before exiting. Normally we won't reach this point as the
above loop is endless.
~~~
disconnect
~~~