261 lines
7.4 KiB
Plaintext
261 lines
7.4 KiB
Plaintext
/*
|
|
# Tiger-bot
|
|
|
|
Attempt at writing an IRC bot in Tiger. Actually mostly worked,
|
|
by wrapping the call in a specifically designed script to handle SSL
|
|
and the connection. The Tiger implementation only needed to respond to
|
|
messages on input, by writing to output.
|
|
|
|
Design started on 6. Marts 2019.
|
|
|
|
## Usage
|
|
|
|
Run using `run.sh`.
|
|
|
|
Requires `openssl` utility.
|
|
*/
|
|
|
|
let /* Parsing util */
|
|
|
|
var seen_char := 0
|
|
var next_char := "IS NOT A CHAR; SHOULD NOT EVER BE VISIBLE!"
|
|
|
|
function peak_char (): string =
|
|
( if not(seen_char)
|
|
then ( next_char := getchar()
|
|
; seen_char := 1 )
|
|
; next_char )
|
|
|
|
function pop_char (): string =
|
|
let var char := peak_char()
|
|
in seen_char := 0
|
|
; char
|
|
end
|
|
|
|
/* String and number util */
|
|
|
|
function max (a:int, b:int): int =
|
|
if a > b
|
|
then a
|
|
else b
|
|
|
|
function min (a:int, b:int): int =
|
|
if a < b
|
|
then a
|
|
else b
|
|
|
|
function safe_substring (str: string, i_start: int, i_end: int): string =
|
|
( i_start := max(0, i_start)
|
|
; i_end := min(size(str) - 1, i_end)
|
|
; if i_start > i_end
|
|
then ""
|
|
else substring(str, i_start, i_end - i_start + 1) )
|
|
|
|
/* Messages */
|
|
|
|
type str_arr = array of string
|
|
type message = { /* TODO: @tags, :source, */
|
|
command: string
|
|
, parameters: str_arr
|
|
, num_parameters: int }
|
|
|
|
function parse_message (plain_msg: string): message =
|
|
let var msg := message
|
|
{ command = "NO CMD"
|
|
, parameters = str_arr[16] of "NO PARAM" /* TODO: Support any number of paramters */
|
|
, num_parameters = -1 }
|
|
|
|
var word_start_i := 0
|
|
|
|
function skip_ws () =
|
|
while substring(plain_msg, word_start_i, 1) = " "
|
|
do word_start_i := word_start_i + 1
|
|
|
|
function skip_to_ws () =
|
|
while substring(plain_msg, word_start_i, 1) <> " "
|
|
do word_start_i := word_start_i + 1
|
|
|
|
in skip_ws()
|
|
|
|
/* Skip @tags */
|
|
; if substring(plain_msg, word_start_i, 1) = "@"
|
|
then skip_to_ws()
|
|
|
|
; skip_ws()
|
|
|
|
/* Skip :source */
|
|
; if substring(plain_msg, word_start_i, 1) = ":"
|
|
then skip_to_ws()
|
|
|
|
; skip_ws()
|
|
|
|
; for i := word_start_i to size(plain_msg)
|
|
do let var char := if i < size(plain_msg)
|
|
then ord(substring(plain_msg, i, 1))
|
|
else ord(" ") /* Ensures that msg parsing
|
|
always ends on a space character */
|
|
|
|
in if char = ord(" ")
|
|
then (if word_start_i < i
|
|
then ( if msg.num_parameters < 0
|
|
then msg.command := safe_substring(plain_msg, word_start_i, i-1)
|
|
else msg.parameters[msg.num_parameters] := safe_substring(plain_msg, word_start_i, i-1)
|
|
; msg.num_parameters := msg.num_parameters + 1)
|
|
; word_start_i := i + 1)
|
|
|
|
else if char = ord(":")
|
|
then ( msg.parameters[msg.num_parameters] := safe_substring(plain_msg, i+1, size(plain_msg))
|
|
; msg.num_parameters := msg.num_parameters + 1
|
|
; break )
|
|
|
|
end
|
|
|
|
/* Rather return nil than an incorrect message */
|
|
; if msg.command <> "NO CMD" & msg.num_parameters >= 0
|
|
then msg
|
|
else nil
|
|
end
|
|
|
|
function format_message (msg: message): string =
|
|
let var str := msg.command
|
|
in for i := 0 to msg.num_parameters-2
|
|
do str := concat(concat(str, " "), msg.parameters[i])
|
|
|
|
/* Last parameter */
|
|
; if msg.num_parameters
|
|
then str := concat(concat(str, " :"), msg.parameters[msg.num_parameters-1])
|
|
|
|
; str
|
|
end
|
|
|
|
/* Main loop */
|
|
|
|
var have_registered := 0
|
|
|
|
function read_next_message(): message =
|
|
let var msg := ""
|
|
function is_eoc(c: string): int =
|
|
c = "\n" | c = "\013"
|
|
|
|
/* Concat until newline */
|
|
in while peak_char() <> "\013"
|
|
do ( msg := concat(msg, pop_char())
|
|
; /*log_info(msg)*/ () )
|
|
|
|
/* Ignore newline */
|
|
; pop_char(); pop_char()
|
|
|
|
/* Parse message */
|
|
; parse_message(msg)
|
|
end
|
|
|
|
function send_msg (msg: message) =
|
|
( print(format_message(msg))
|
|
; print("\n")
|
|
; flush() )
|
|
|
|
function cmd0 (cmd: string): message =
|
|
message { command = cmd
|
|
, parameters = str_arr[0] of ""
|
|
, num_parameters = 0 }
|
|
function cmd1 (cmd: string, param1: string): message =
|
|
message { command = cmd
|
|
, parameters = str_arr[1] of param1
|
|
, num_parameters = 1 }
|
|
function cmd2 (cmd: string, param1: string, param2:string): message =
|
|
let var params := str_arr[2] of param1
|
|
in params[1] := param2
|
|
; message { command = cmd
|
|
, parameters = params
|
|
, num_parameters = 2 }
|
|
end
|
|
|
|
function wait_for_msg (cmd: string): message =
|
|
let var msg := read_next_message()
|
|
in if msg.command <> cmd
|
|
then wait_for_msg(cmd)
|
|
else msg
|
|
end
|
|
|
|
function ping_respond () =
|
|
let var msg := wait_for_msg("PING")
|
|
in send_msg(cmd1("PONG", msg.parameters[0]))
|
|
end
|
|
|
|
function quit(quit_msg: string) =
|
|
( log_info(concat("Quitting: ", quit_msg))
|
|
; send_msg(cmd1("QUIT", quit_msg))
|
|
; exit(0) )
|
|
|
|
function log_info(info: string) =
|
|
if have_registered
|
|
then send_msg(cmd2("PRIVMSG", "Jmaa", info))
|
|
|
|
function is_char_digit(c: int): int =
|
|
ord("0") <= c & c <= ord("9")
|
|
|
|
function is_number_string(c: string): int =
|
|
let var derp := 1
|
|
in for i := 0 to size(c) - 1
|
|
do if not(is_char_digit(ord(substring(c, i, 1))))
|
|
then ( derp := 0
|
|
; break )
|
|
; derp
|
|
end
|
|
|
|
function main_loop () =
|
|
let var msg := read_next_message()
|
|
|
|
in if msg = nil
|
|
then log_info("Could not parse message!")
|
|
/*; quit("Could not parse message") )*/
|
|
|
|
/* Handle number messages */
|
|
else if is_number_string(msg.command)
|
|
then () /* Ignore */
|
|
|
|
/* Handle pings */
|
|
else if msg.command = "PING"
|
|
then send_msg(cmd1("PONG", msg.parameters[0]))
|
|
|
|
/* Handle yo */
|
|
else if msg.command = "PRIVMSG" & msg.parameters[1] = "yo"
|
|
then send_msg(cmd2("PRIVMSG", msg.parameters[0], "Yo yourself"))
|
|
|
|
/* Handle ERROR messages */
|
|
else if msg.command = "ERROR"
|
|
then quit("Got ERROR message")
|
|
|
|
/* Do nothing */
|
|
else () /*log_info(concat("Got message, but did nothing: ", format_message(msg)))*/
|
|
|
|
/* THE RIDE NEVER ENDS */
|
|
; main_loop()
|
|
end
|
|
|
|
in ()
|
|
/* Establish connection */
|
|
/* At this point, we can register */
|
|
; print("USER tiger-bot * * : yo\n")
|
|
; print("NICK TigerBot\n")
|
|
; flush()
|
|
|
|
/* Wait for first PING, and respond to it */
|
|
; ping_respond()
|
|
|
|
/* Wait for 001; so we know that we have been connected and
|
|
* registered */
|
|
; have_registered := 1
|
|
; wait_for_msg("001")
|
|
|
|
/* Connect to channel and greet */
|
|
; send_msg(cmd1("JOIN", "#bot-test"))
|
|
; send_msg(cmd2("PRIVMSG", "#bot-test", "Hello, how's it going?"))
|
|
|
|
/* Start normal connection */
|
|
; main_loop()
|
|
|
|
/* Exit! */
|
|
; quit("Nothing more to say")
|
|
end
|