1
0
Disbranĉigi 0
Ĉi tiu deponejo arĥiviĝis je 2024-01-29. Vi povas vidi kaj elŝuti dosierojn, sed ne povas puŝi nek raporti problemojn nek tirpeti.
chicken-ircc/ircc.scm
Jaidyn Ann fe77077c68 Store information on joined channels and channel membership
When a user JOINs or is in RPL_NAMREPLY, it will be saved;
all rooms whch the client joins will be saved in a channel list,
as well.
2023-01-03 20:58:49 -06:00

278 lines
11 KiB
Scheme

;;
;; Copyright 2022, Jaidyn Levesque <jadedctrl@posteo.at>
;;
;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of
;; the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;
(import scheme
(chicken io) (chicken tcp)
srfi-1 srfi-69 srfi-130
openssl)
;; ——————————————————————————————————————————————————————————————————————————————
;; Main
;; ——————————————————————————————————————————————————————————————————————————————
;; Connect to the given IRC server, returning an IRC connection object.
(define (irc:connect host port username nick #!optional (password #f) (realname #f))
(let ([conn (make-hash-table)])
(define-values (out in)
(ssl-connect* hostname: host port: port))
(hash-table-set! conn 'in in)
(hash-table-set! conn 'out out)
(hash-table-set! conn 'username username)
(hash-table-set! conn 'nick nick)
(hash-table-set! conn 'realname realname)
(hash-table-set! conn 'channels (make-hash-table))
(if password
(irc:write-cmd conn "PASS" password))
(irc:write-cmd conn "USER" username "*" "0"
(if realname realname "Jane Row"))
(irc:write-cmd conn "NICK" nick)
conn))
;; Basic loop for using an IRC connection, using two hook functions:
;; (on-command connection command params sender)
;; (on-reply connection reply-code params sender)
(define (irc:loop connection on-command on-reply)
(let* ([output (irc:read-alist connection)]
[command (alist-ref 'command output)]
[reply (alist-ref 'reply output)]
[params (alist-ref 'params output)]
[sender (alist-ref 'sender output)])
(if (and on-command (car command))
(apply on-command (append (list connection) command (list params) sender)))
(if (and on-reply (car reply))
(apply on-reply (append (list connection) reply (list params) sender)))
(irc:loop connection on-command on-reply)))
;; ——————————————————————————————————————————————————————————————————————————————
;; I/O
;; ——————————————————————————————————————————————————————————————————————————————
;; Read-in the next reply or command from the server, into a parsable alist with
;; four keys:
(define (irc:read-alist conn)
(irc:process-alist-internally
conn
(irc:line->alist (irc:read-line conn))))
;; Read a single line from the IRC server
(define (irc:read-line conn)
(read-line (hash-table-ref conn 'out)))
;; Send a specific command to the server.
(define (irc:write-cmd conn command . parameters)
(irc:write-line (apply irc:cmd->string (append `(,command) parameters))
conn))
;; Write a line to the IRC server connection.
(define (irc:write-line text connection)
(write-line text (hash-table-ref connection 'in)))
;; ——————————————————————————————————————————————————————————————————————————————
;; Processing/saving metadata
;; ——————————————————————————————————————————————————————————————————————————————
;; The user should have more-or-less total control over how to respond to
;; received messages, but ircc has to sneakily process some responses itself,
;; to ensure basic functionality (i.e., pings, chanlist, userlist, etc.)
(define (irc:process-alist-internally conn alist)
(let ([command (alist-car-ref 'command alist)]
[reply (alist-car-ref 'reply alist)]
[sender (alist-car-ref 'sender alist)]
[params (alist-ref 'params alist)])
(if command
(irc:process-command-internally conn command params sender)
(irc:process-reply-internally conn reply params sender)))
alist)
;; Handle some replies necssary for basic functionality
(define (irc:process-reply-internally conn reply params #!optional sender)
(cond [(eq? reply RPL_WELCOME)
(hash-table-set! conn 'registered #t)
(hash-table-set! conn 'nick (car params))]
[(eq? reply RPL_NAMREPLY)
(let ([channel (third params)]
[chan-symbol (second params)]
[users (cdddr params)])
(irc:channel-set! conn channel 'symbol chan-symbol)
(apply
(lambda (user)
(irc:channel-user-add! conn channel user))
users))]))
;; Handle some commands necessary for basic functionality
(define (irc:process-command-internally conn command params #!optional sender)
(cond [(string=? command "PING")
(irc:write-cmd conn "PONG" (last params))]
[(string=? command "JOIN")
(let ([room-name (car params)]
[new-user (car (string-split sender "!"))])
(if (irc:user-is-self? conn new-user)
(irc:channel-add! conn room-name))
(irc:channel-user-add! conn room-name new-user))]))
;; ——————————————————————————————————————————————————————————————————————————————
;; Metadata accessors
;; ——————————————————————————————————————————————————————————————————————————————
;; Return whether or not the given string (username/nick/whatever) is equivalent
;; to current user.
(define (irc:user-is-self? conn user-string)
(or (string=? (hash-table-ref conn 'username) user-string)
(string=? (hash-table-ref conn 'nick)
(car (string-split user-string "!")))
(string=? (hash-table-ref conn 'client) user-string)))
;; Add a channel of name `chan` to the internal list of channels
(define (irc:channel-add! conn chan)
(hash-table-set! (hash-table-ref conn 'channels) chan (make-hash-table))
(hash-table-set! (irc:channel-table conn chan) 'users '()))
;; Remove a channel of name `chan` from the internal list of channels
(define (irc:channel-remove! conn chan)
(hash-table-remove! (hash-table-ref conn 'channels) chan))
;; Return a list of saved channels by name
(define (irc:channels conn)
(hash-table-keys (hash-table-ref conn 'channels)))
;; Return a saved channel's table
(define (irc:channel-table conn chan)
(hash-table-ref (hash-table-ref conn 'channels) chan))
;; Get a stored value associated with a channel, by key
(define (irc:channel-get conn chan key)
(hash-table-ref (irc:channel-table conn chan) key))
;; Associate a value with a given channel, by key
(define (irc:channel-set! conn chan key value)
(hash-table-set! (irc:channel-table conn chan)
key value))
;; Returns a list of users that are stored as members of the given channel
(define (irc:channel-users conn chan)
(irc:channel-get conn chan 'users))
;; Add a user to a channel's list of users, by nick
(define (irc:channel-user-add! conn chan nick)
(irc:channel-set!
conn chan 'users
(append (irc:channel-get conn chan 'users)
(list nick))))
;; Remove a user from a channel's list of users, by nick
(define (irc:channel-user-del! conn chan nick)
(irc:channel-set!
conn chan 'users
(filter (lambda (a-nick)
(not (string=? nick a-nick)))
(irc:channel-users conn name))))
;; —————————————————————————————————————————————————————————————————————————————
;; Parsing lines/commands
;; —————————————————————————————————————————————————————————————————————————————
;; Construct a string to write to IRC for the given command and parameters.
(define (irc:cmd->string command . parameters)
(let ([parameters
(append (reverse (cdr (reverse parameters)))
`(,(string-append ":" (last parameters))))])
(string-append
command
" "
(reduce-right
(lambda (a b)
(string-append a " " b))
#f
parameters))))
;; Convert a string to a `msg` alist, with keys 'command', 'reply', 'params',
;; and 'sender'.
(define (irc:line->alist str)
(let* ([colon-split (string-split str " :")]
[last-column (reduce string-append #f (cdr colon-split))] ;; for post-colon colons
[other-columns (string-split (car colon-split) " ")]
[sender (if (eq? #\:
(car (string->list (car other-columns))))
(string-drop (car other-columns) 1)
#f)]
[command (if sender
(cadr other-columns)
(car other-columns))]
[reply (string->number command)]
[params (append (if sender (cddr other-columns) (cdr other-columns))
(list last-column))])
`((command ,(if (not reply) command #f))
(reply ,reply)
,(append '(params) params)
(sender ,sender))))
;; —————————————————————————————————————————————————————————————————————————————
;; Misc. helpers
;; —————————————————————————————————————————————————————————————————————————————
;; Just car's the value of alist-ref (if it exists)
(define (alist-car-ref key alist)
(let ([value (alist-ref key alist)])
(if value
(car value)
#f)))
;; —————————————————————————————————————————————————————————————————————————————
;; IRC constants
;; —————————————————————————————————————————————————————————————————————————————
(define RPL_WELCOME 1)
(define RPL_WHOISUSER 311)
(define RPL_ENDOFWHO 315)
(define RPL_ENDOFWHOIS 318)
(define RPL_LIST 322)
(define RPL_LISTEND 323)
(define RPL_TOPIC 332)
(define RPL_WHOREPLY 352)
(define RPL_NAMREPLY 353)
(define RPL_MOTD 372)
(define RPL_MOTDSTART 375)
(define RPL_ENDOFMOTD 376)
(define ERR_NONICKNAMEGIVEN 431)
(define ERR_ERRONEUSNICKNAME 432)
(define ERR_NICKNAMEINUSE 433)