(local privmsg (require :privmsg))
(local handlers (require :handlers))

;; get and set require io.open, not the nodemcu API

(fn ok-term? [term] (and (: term :match "^[-%w]+$") term))
(fn term-name [term] (.. "terms/" term))

(fn handlers.set [_ _ args]
  (let [(term data) (: args :match "^(%S+) (.*)")
        f (file.open (term-name term) :w)]
    (if f
        (do (doto f
              (: :write data)
              (: :close))
            (.. "OK, " term " is now " data))
        "Could not write term for some reason.")))

(fn handlers.get [_ _ term]
  (.. term " is " (if (file.exists (term-name term))
                      (let [file (file.open (term-name term))
                            value (file.read)]
                        (file.close)
                        value)
                      "not something I know about.")))

(fn send [socket command use-separator? args]
  (var str command)
  (when (> (# args) 0)
    (let [sep (if use-separator?
                  " :"
                  (> (# args) 1)
                  " " "")]
      (set str (.. str " " (table.concat args " " 1 (- (# args) 1))
                   sep (. args (# args))))))
  (when _G.debug? (print ">" str))
  (: socket :send (.. str "\r\n")))

(fn push [conn command use-separator? args]
  (tset conn.queue conn.bottom [command use-separator? args])
  (set conn.bottom (+ conn.bottom 1)))

(fn flush [conn]
  (when (not (= conn.top conn.bottom))
    (let [[command separator? args] (. conn.queue conn.top)]
      (tset conn.queue conn.top nil)
      (set conn.top (+ conn.top 1))
      (when (= conn.top conn.bottom)
        (set conn.top 1)
        (set conn.bottom 1))
      (send conn.socket command separator? args))))

(fn tokenize [line]
  (let [(_ e1 prefix)  (: line :find "^:(%S+)")
        (_ e2 command) (: line :find "(%S+)" (if e1 (+ e1 1) 1))
        (_ _ rest)     (: line :find "%s+(.*)" (if e2 (+ e2 1) 1))]
    (values prefix command rest)))

(var commands nil)

(fn receive [conn socket data]
  (flush conn)
  (each [line (: data :gmatch "[^\r\n]+")]
    (when line
      (when _G.debug? (print "<" line))
      (let [(prefix command rest) (tokenize line)
            command (and command (: command :lower))
            handler (. commands command)]
        (when handler
          (handler prefix rest))))))

(fn connect [irc host port nick channels]
  (let [conn {:queue [] :top 1 :bottom 1
              :socket (net.createConnection) :nick nick}
        t (tmr.create)]
    (: conn.socket :connect (or port 6667) host)
    (: conn.socket :on :receive (partial receive conn))
    (: conn.socket :on :connection (fn []
                                     (irc.nick conn nick)
                                     (irc.user conn "pengbot" "0" "*"
                                               "A bot written in fennel.")
                                     (print "Connected!")
                                     (irc.start irc conn channels)))
    (: conn.socket :on :disconnection (fn [x err]
                                        (print "oh no" x err)
                                        (: t :stop)))
    (: t :alarm 200 tmr.ALARM_AUTO (partial flush conn))
    conn))

(let [irc {:connect connect
           :start (fn [irc conn channels]
                    (global connection conn)
                    (set commands {:ping (fn [prefix rest] (irc.pong conn rest))
                                   :privmsg (partial irc.privmsg* conn)
                                   :376 (fn [prefix rest]
                                          (each [_ channel (ipairs channels)]
                                            (let [channel (if (: channel :find "^#")
                                                              channel (.. "#" channel))]
                                              (irc.join conn channel))))}))
           :flush flush}]
  ;; define functions for IRC operations
  (each [name separator? (pairs {:nick false :user true :join false :kick true
                                 :privmsg true :ping false :pong false :part true})]
    (tset irc name
          (fn [conn ...]
            (push conn (: name :upper) separator? [...]))))
  (fn irc.privmsg* [conn prefix rest]
    (let [chan (: rest :match "(%S+)")
          msg (: rest :match ":(.*)")
          their-nick (: prefix :match "(%S+)!")
          ;; can use comma prefix char or nick mention
          (cmd args) (if (string.find msg "^,")
                         (: msg :match "^,(%S+) ?(.*)")
                         (: msg :match (string.format "^%s: (%%S+) ?(.*)"
                                                      conn.nick)))
          chan (if (: chan :find "^#") chan their-nick)
          handler (. handlers cmd)]
      (when handler
        (let [(ok response) (pcall handler conn chan args)]
          (if (and ok (= (type response) "string"))
              (irc.privmsg conn chan response)
              (not ok)
              (print "Error in handler" response))))))
  irc)

Generated by Phil Hagelberg using scpaste at Sun Feb 3 16:12:10 2019. PST. (original)