git.m455.casa

dog

clone url: git://git.m455.casa/dog


main.scm

1 (import utf8
2 openssl
3 srfi-1
4 (chicken file)
5 (chicken tcp)
6 (chicken format)
7 (chicken io)
8 (chicken string))
9
10 ;; variables ==========================
11 (define config-file "config.scm")
12
13 (define config-alist
14 (if (file-exists? config-file)
15 (with-input-from-file config-file read)
16 (print (format "woops! ~a doesn't exist." config-file))))
17
18 (define-values (from-server to-server)
19 (let ((irc-host (alist-ref 'irc-host config-alist))
20 (irc-port (alist-ref 'irc-port config-alist)))
21 (if (alist-ref 'irc-ssl config-alist)
22 (ssl-connect* #:hostname irc-host
23 #:port irc-port)
24 (tcp-connect irc-host irc-port))))
25
26 ;; utils ==========================
27 (define (keys alist)
28 (map car alist))
29
30 (define (is-key? key alist)
31 (member key (keys alist)))
32
33 (define (list-update! l k v)
34 (set-car! (list-tail l k) v)
35 l)
36
37 (define (config-get key)
38 (alist-ref key config-alist))
39
40 (define (file->lines file)
41 (with-input-from-file file read-lines))
42
43 (define (file->alist file)
44 (let ((lines (file->lines file)))
45 (map (lambda (index entry) `(,index . ,entry))
46 (iota (length lines))
47 lines)))
48
49 (define (file-write file contents)
50 (with-output-to-file
51 file
52 (lambda () (display contents))))
53
54 (define (file-append file contents)
55 (with-output-to-file
56 file
57 (lambda () (display contents))
58 #:append))
59
60 ;; commands ========================
61 (define (help)
62 (list "========"
63 "Usage:"
64 " !help - Show this help message"
65 " !ls - Show all catalogue items"
66 " !add <text> - Add text to the catalogue"
67 " !rm <number> - Remove an item from the catalogue"
68 " !update <number> <text> - Replace the contents of an item with new text"
69 "========"))
70
71 (define (ls)
72 (if (file-exists? (config-get 'list-file))
73 (let* ((entries (file->lines (config-get 'list-file)))
74 (number-of-entries (length entries)))
75 (if (= number-of-entries 0)
76 (list "no stuffs :(")
77 (let ((numbers (map number->string (iota number-of-entries))))
78 (map (lambda (x y) (string-append x ". " y))
79 numbers
80 entries))))
81 (list "no file ;(")))
82
83 (define (add str)
84 (file-append (config-get 'list-file)
85 (string-append str "\n"))
86 (format "added '~a' to the list!" str))
87
88 (define (rm message-split)
89 (let* ((index (string->number (car message-split)))
90 (list-file (config-get 'list-file))
91 (file-alist (file->alist list-file)))
92 (if (is-key? index file-alist)
93 (let* ((entries (map cdr file-alist))
94 (entry-to-remove (list-ref entries index))
95 (entries-with-entry-removed (remove (lambda (s) (equal? s entry-to-remove)) entries)))
96 (file-write list-file (string-intersperse entries-with-entry-removed "\n"))
97 (format "removed '~a' from the list!" entry-to-remove))
98 "woops, couldn't find that item.")))
99
100 (define (update message-split)
101 (let* ((index (string->number (car message-split)))
102 (list-file (config-get 'list-file))
103 (file-alist (file->alist list-file)))
104 (if (and (is-key? index file-alist)
105 (> (length message-split) 1))
106 (let* ((entries (map cdr file-alist))
107 (new-text (string-intersperse (cdr message-split))))
108 (file-write list-file
109 (string-intersperse (list-update! entries index new-text) "\n"))
110 (format "updated list item ~a to '~a'!" index new-text))
111 "woops. you gotta provide a valid number and text!")))
112
113 ;; irc util ====================================================================
114 ;; (tcp-read-timeout #f) so (read-line ...) on a TCP connection doesn't timeout after 60 seconds
115 (tcp-read-timeout #f)
116
117 (define (send str)
118 (write-line (string-append str "\r\n") to-server))
119
120 (define-syntax send-format
121 (syntax-rules ()
122 ((_ str v ...)
123 (send (format str v ...)))))
124
125 (define (send-password)
126 (send-format "PASS ~a" (config-get 'irc-password)))
127
128 (define (send-nick)
129 (send-format "NICK ~a" (config-get 'irc-nickname)))
130
131 (define (send-user)
132 (send-format "USER ~a 0.0.0.0 ~a :~a"
133 (config-get 'irc-nickname)
134 (config-get 'irc-username)
135 (config-get 'irc-realname)))
136
137 (define (send-message channel message)
138 (send-format "PRIVMSG ~a :~a" channel message))
139
140 (define (send-multiline-message channel lines)
141 (for-each (lambda (line)
142 (send-message channel line))
143 lines))
144
145 (define (check-for-ping server-data)
146 (when (equal? "PING" (substring server-data 0 4))
147 (send "PONG :lol")))
148
149 (define (disable-history)
150 (send-format "PRIVMSG nickserv :SET autoreplay-lines 0"))
151
152 (define (join-channel channel)
153 (send-format "JOIN ~a" channel))
154
155 (define (join-channels)
156 (for-each join-channel (config-get 'irc-channels)))
157
158 (define (get-sender mode split-server-data)
159 (let* ((sender-data (list-ref split-server-data 0)) ;; => ":nickname!~realname@user/realname" or ":nickname!realname@localhost"
160 (sender-data-split (string-split sender-data ":!~@"))) ;; => ("nickname" "realname" "user/realname")
161 (case mode
162 ('realname (cadr sender-data-split))
163 ('nickname (car sender-data-split))
164 (else "unknown"))))
165
166 (define (get-channel split-server-data)
167 (list-ref split-server-data 2))
168
169 ;; returns a message in the form of '("this" "is" "a" "test")
170 (define (get-message split-server-data)
171 (let ((raw-message-split (list-tail split-server-data 3))) ;; => '(":this" "is" "a" "test")
172 (cons (substring (car raw-message-split) 1) ;; get the first item, and remove the colon
173 (cdr raw-message-split))))
174
175 ;; irc loop ===================================================================
176 ;; how split-server-data looks:
177 ;; 0 1 2 3
178 ;; '(":some-nickname!some-realname@localhost" "PRIVMSG" "#tildetown" ":this" "is" "a" "test")
179 ;; or sometimes with a tilde:
180 ;; '(":some-nickname!~some-realname@user/some-realname" "PRIVMSG" "#tildetown" ":this" "is" "a" "test")
181 ;;
182 ;; how private messages look:
183 ;; :some-nickname!~some-realname@user/some-realname PRIVMSG botname :oh this is a test
184 (define (check-for-command server-data)
185 (let ((split-server-data (string-split server-data)))
186 (when (equal? (list-ref split-server-data 1) "PRIVMSG")
187 (let* ((nickname (get-sender 'nickname split-server-data))
188 (realname (get-sender 'realname split-server-data))
189 (chan (get-channel split-server-data))
190 (channel (let ((chan (get-channel split-server-data)))
191 (if (equal? chan (config-get 'irc-nickname))
192 nickname
193 chan)))
194 (message-split (get-message split-server-data)))
195 (when (not (null? message-split))
196 (case (string->symbol (car message-split))
197 ('!ls (send-multiline-message channel (ls)))
198 ('!add (send-message channel (add (string-intersperse (cdr message-split)))))
199 ('!rm (send-message channel (rm (cdr message-split))))
200 ('!update (send-message channel (update (cdr message-split))))
201 ('!dog (send-multiline-message channel (help)))
202 (else 'do-nothing)))))))
203
204 (define (start-irc-connection)
205 (when (config-get 'irc-password)
206 (send-password))
207 (send-nick)
208 (send-user)
209 (when (config-get 'irc-disable-history?) (disable-history))
210 (join-channels)
211 (sleep 1))
212
213 (define (listen-to-irc-server)
214 (let ((server-data (read-line from-server)))
215 (print server-data)
216 (check-for-ping server-data)
217 (check-for-command server-data)
218 (sleep 1))
219 (listen-to-irc-server))
220
221 (define (main)
222 (start-irc-connection)
223 (listen-to-irc-server))
224
225 (main)