irc-functions.rkt 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #lang typed/racket
  2. ;; A bunch of code in this file was copied from the irc package
  3. ;; (c) Jonathan Schuster
  4. (provide (all-defined-out))
  5. (require "ws-typed.rkt")
  6. ;; TODO: just use a hash table
  7. (define-type Channel-List (Listof (Pairof String WS)))
  8. ;; :prefix command params crlf
  9. (struct irc-message ([prefix : (U False String)]
  10. ;; XXX: just Positive-Integer?
  11. [command : (U Positive-Integer String)]
  12. [params : (Listof String)])
  13. #:transparent)
  14. (struct irc-connection ([in : Input-Port]
  15. [out : Output-Port]
  16. [nick : String]
  17. [user : String]
  18. [custodian : Custodian]
  19. [channels : Channel-List])
  20. #:mutable)
  21. (: send-to-client (-> irc-connection irc-message Void))
  22. (define (send-to-client conn message)
  23. ;; TODO: flush?
  24. (match message
  25. [(irc-message prefix command params)
  26. (if prefix
  27. (fprintf (irc-connection-out conn)
  28. ":~a ~a ~a\r\n"
  29. prefix
  30. command
  31. (string-join params))
  32. (fprintf (irc-connection-out conn)
  33. "~a ~a\r\n"
  34. command
  35. (string-join params)))]))
  36. ;; blocking read & parse
  37. ;; returns eof if there is no more data on the irc socket,
  38. ;; returns #f if the message cannot be parsed
  39. (: read-from-input-port (-> Input-Port (U irc-message False EOF)))
  40. (define (read-from-input-port in)
  41. (if (port-closed? in)
  42. eof
  43. ;; 'any ==> break line on etieher return, linefeed, or return-linefeed combo
  44. (let ([line (read-line in 'any)])
  45. ;; (log-info (format "Raw line: ~s" line))
  46. (if (string? line)
  47. (parse-message line)
  48. eof))))
  49. (: read-from-client (->* (irc-connection)
  50. (#:timeout (U False Nonnegative-Real))
  51. (U irc-message False EOF)))
  52. (define (read-from-client conn #:timeout [timeout #f])
  53. (define in-port (irc-connection-in conn))
  54. (: rl-evt (Evtof (U EOF String)))
  55. (define rl-evt (read-line-evt in-port 'any))
  56. (: line (U False EOF String))
  57. (define line (if timeout
  58. (sync/timeout timeout rl-evt)
  59. (sync rl-evt)))
  60. (if (string? line)
  61. (parse-message line)
  62. ;; if the timeout was triggered
  63. eof ;; return eof so that the caller closes teh connection
  64. ))
  65. ;; Given the string of an IRC message, returns an irc-message that has been parsed as far as possible,
  66. ;; or #f if the input was unparsable
  67. (: parse-message (-> String (U irc-message False)))
  68. (define (parse-message message)
  69. (define parts (string-split (string-trim message) " " #:trim? #f))
  70. (define prefix (if (and (pair? parts)
  71. (string-starts-with? (list-ref parts 0) ":"))
  72. (substring (list-ref parts 0) 1)
  73. #f))
  74. (cond [(> (length parts) (if prefix 1 0))
  75. (define command (list-ref parts (if prefix 1 0)))
  76. (define param-parts (list-tail parts (if prefix 2 1)))
  77. (irc-message prefix (string-upcase command) (parse-params param-parts))]
  78. [(empty? parts) #f ;; the message is entirely empty
  79. ;; don't log this as a warning
  80. ;; this happens on erc because it ends messages
  81. ;; with a linefeed-return combination instead
  82. ;; of the usual return-linefeed
  83. ]
  84. [else (begin (log-warning (format "Couldn't parse ~a" message))
  85. #f)]))
  86. ;; Given the list of param parts, return the list of params
  87. (: parse-params (-> (Listof String) (Listof String)))
  88. (define (parse-params parts)
  89. (define first-tail-part (find-first-tail-part parts))
  90. (cond [first-tail-part
  91. (define tail-with-colon (string-join (list-tail parts first-tail-part)))
  92. (define tail-param (if (string-starts-with? tail-with-colon ":")
  93. (substring tail-with-colon 1)
  94. tail-with-colon))
  95. (append (take parts first-tail-part)
  96. (list tail-param))]
  97. [else parts]))
  98. ;; Return the index of the first part that starts the tail parameters; of #f if no tail exists
  99. (: find-first-tail-part (-> (Listof String) (U Integer False)))
  100. (define (find-first-tail-part param-parts)
  101. (define first-colon-index (memf/index (lambda ([v : String]) (string-starts-with? v ":"))
  102. param-parts))
  103. (cond [(or first-colon-index (> (length param-parts) 14))
  104. (min 14 (if first-colon-index first-colon-index 14))]
  105. [else #f]))
  106. ;; Like memf, but returns the index of the first item to satisfy proc instead of
  107. ;; the list starting at that item.
  108. (: memf/index (All (a) (-> (-> a Boolean) (Listof a) (U Integer False))))
  109. (define (memf/index proc lst)
  110. (define memf-result (memf proc lst))
  111. (cond [memf-result (- (length lst) (length memf-result))]
  112. [else #f]))
  113. (: string-starts-with? (-> String String Boolean))
  114. (define (string-starts-with? s1 s2)
  115. (define s1-prefix (if (= 0 (string-length s1)) "" (substring s1 0 (string-length s2))))
  116. (equal? s1-prefix s2))
  117. (define RPL_TOPIC 332)
  118. (define RPL_NAMEREPLY 353)
  119. (define RPL_ENDOFNAMES 366)
  120. (define RPL_CHANNELMODEIS 324)
  121. (define RPL_WHOREPLY 352)
  122. (define RPL_ENDOFWHO 315)
  123. (define RPL_WHOISUSER 311)
  124. ;; "<nick> <user> <host> * :<real name>"
  125. (define RPL_WHOISSERVER 312)
  126. ;; "<nick> <server> :<server info>"
  127. (define RPL_ENDOFWHOIS 318)
  128. ;; "<nick> :End of /WHOIS list"
  129. (define ERR_NOSUCHNICK 401)
  130. ;; "<nickname> :No such nick/channel"
  131. (define RPL_MOTDSTART 375)
  132. (define RPL_MOTD 372)
  133. (define RPL_ENDOFMOTD 376)
  134. ;; channel-list related functions
  135. (: lookup-ws-conn (-> Channel-List String (U WS False)))
  136. (define (lookup-ws-conn ls x)
  137. (define v (assoc x ls))
  138. (and v (cdr v)))
  139. (: channel-joined? (-> String Channel-List Boolean))
  140. (define (channel-joined? x channels)
  141. (if (assoc x channels) #t #f))
  142. (: remove-channel (-> String Channel-List Channel-List))
  143. (define (remove-channel chan channels)
  144. (remove* (filter
  145. (lambda ([p : (Pairof String WS)]) (equal? (car p) chan))
  146. channels)
  147. channels))
  148. (: add-channel (-> String WS Channel-List Channel-List))
  149. (define (add-channel chan ws channels)
  150. (cons (cons chan ws) channels))