ircd.rkt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #lang typed/racket
  2. ;; single room IRC server
  3. (require "private/irc-functions.rkt"
  4. (only-in "private/ws-typed.rkt" WS)
  5. (prefix-in ws: "api.rkt")
  6. (prefix-in movie-night: "chat.rkt"))
  7. (provide (all-defined-out))
  8. (define movie-night-ws-url (make-parameter "wss://stream.ihatebeinga.live/ws"))
  9. (: users (Listof String))
  10. (define users '())
  11. (define channel "#chats")
  12. ;; Main entry point
  13. ;; Returns the main server loop thread (for synchronizing) and
  14. ;; a function for killing the server.
  15. (: serve (->* ()
  16. (#:port Integer #:hostname (U False String))
  17. (Values Thread (-> Void))))
  18. (define (serve #:port [port-no 6667] #:hostname [host #f])
  19. (define serve-cust (make-custodian))
  20. (parameterize ([current-custodian serve-cust])
  21. (define listener (tcp-listen port-no 5 #t host))
  22. (: loop (-> Nothing))
  23. (define (loop)
  24. (accept-and-handle listener)
  25. (loop))
  26. (define t (thread loop))
  27. (values t
  28. (lambda ()
  29. (custodian-shutdown-all serve-cust)))))
  30. ;; Accepting new clients
  31. (: accept-and-handle (-> TCP-Listener Thread))
  32. (define (accept-and-handle listener)
  33. (define cust (make-custodian))
  34. (parameterize ([current-custodian cust])
  35. (define-values (in out) (tcp-accept listener))
  36. ;; once the ports are bound we spawn a new thread
  37. ;; in oreder to allow the main server loop to handler
  38. ;; other connections
  39. (thread
  40. (lambda ()
  41. (define user-conn (accept-irc-connection in out cust))
  42. (thread (lambda ()
  43. (handle-user-messages user-conn cust)))))))
  44. ;; When we accept a new IRC connection we need to do several things:
  45. ;; 1. Receive the user nick/user information
  46. ;; 2. Prepare the IO ports
  47. ;; 3. Create a new WebSocket connection through chat.rkt
  48. ;; 4. Set up a ping thread
  49. ;; 5. Send the MOTD to the user
  50. (: accept-irc-connection (-> Input-Port Output-Port Custodian irc-connection))
  51. (define (accept-irc-connection in out cust)
  52. (define nick
  53. (let #{loop : (-> String)} ()
  54. (match (read-from-input-port in)
  55. [(irc-message _ "NICK" params)
  56. (car params)]
  57. [_ (loop)])))
  58. (log-info (format "~a connected" nick))
  59. (define user
  60. (let #{loop : (-> String)} ()
  61. (match (read-from-input-port in)
  62. [(irc-message _ "USER" params)
  63. (car params)]
  64. [_ (loop)])))
  65. (file-stream-buffer-mode out 'line)
  66. ;; TODO:
  67. ;; Defining ws-c and conn simulateneously like that is asking for trouble
  68. ;; inb4 a race condition
  69. (: ws-c WS)
  70. (define ws-c
  71. (movie-night:make-connection
  72. (movie-night-ws-url)
  73. nick
  74. #:on-join (lambda ([n : String]) (on-join conn n))
  75. #:on-leave (lambda ([n : String]) (on-leave conn n))
  76. #:on-name-change (lambda ([n1 : String] [n2 : String])
  77. (on-name-change conn n1 n2))
  78. #:on-users (lambda ([l : (Listof String)])
  79. (set! users l) (notify-users conn))
  80. #:on-chat (lambda ([from : String] [msg : String])
  81. (on-chat conn from msg))
  82. #:on-response (lambda ([msg : String]) (on-response conn msg))
  83. #:on-notify (lambda ([msg : String]) (on-response conn msg))
  84. #:on-topic (lambda ([topic : String]) (on-topic conn topic))
  85. #:on-close-conn (lambda () (custodian-shutdown-all cust))))
  86. (: conn irc-connection)
  87. (define conn (irc-connection in out nick user ws-c))
  88. ;;(set-irc-connection-ws-conn! conn ws-c)
  89. (welcome-user conn)
  90. (void (thread (lambda () (ping-pong-thread conn))))
  91. conn)
  92. (: ping-pong-thread (-> irc-connection Void))
  93. (define (ping-pong-thread conn)
  94. (sleep 120)
  95. (send-to-client conn (irc-message
  96. ":lolcathost"
  97. "PING"
  98. '("fffffffffffffffffffffff")))
  99. (ping-pong-thread conn))
  100. ;; Callbacks for the MoveNight chat api
  101. (: notify-users (-> irc-connection Void))
  102. (define (notify-users conn)
  103. (send-to-client conn (irc-message
  104. ":lolcathost"
  105. RPL_NAMEREPLY
  106. (list (irc-connection-nick conn) "@" channel
  107. (format ":~a" (string-join users)))))
  108. (send-to-client conn (irc-message
  109. ":lolcathost"
  110. RPL_ENDOFNAMES
  111. (list (irc-connection-nick conn) channel ":End of /NAMES list."))))
  112. (: on-chat (-> irc-connection String String Void))
  113. (define (on-chat conn from message)
  114. (unless (equal? from (irc-connection-nick conn))
  115. (send-to-client
  116. conn
  117. (irc-message (format "~a!~a@lolcathost" from from)
  118. "PRIVMSG"
  119. (list channel (format ":~a" message))))))
  120. (: on-response (-> irc-connection String Void))
  121. (define (on-response conn message)
  122. (send-to-client
  123. conn
  124. (irc-message "OwO!SERVER@lolcathost"
  125. "NOTICE"
  126. (list channel
  127. (format ":!!! [ ~a ]" message)))))
  128. (: on-topic (-> irc-connection String Void))
  129. (define (on-topic conn topic)
  130. (send-to-client conn (irc-message
  131. ":lolcathost"
  132. RPL_TOPIC
  133. (list (irc-connection-nick conn)
  134. channel
  135. (string-append ":" topic)))))
  136. (: on-join (-> irc-connection String Void))
  137. (define (on-join conn nick)
  138. (unless (equal? nick (irc-connection-nick conn))
  139. (send-to-client
  140. conn
  141. (irc-message (format "~a!~a@lolcathost" nick nick)
  142. "JOIN"
  143. (list channel)))))
  144. (: on-leave (-> irc-connection String Void))
  145. (define (on-leave conn nick)
  146. (unless (equal? nick (irc-connection-nick conn))
  147. (send-to-client
  148. conn
  149. (irc-message (format "~a!~a@lolcathost" nick nick)
  150. "PART"
  151. (list channel)))))
  152. (: on-name-change (-> irc-connection String String Void))
  153. (define (on-name-change conn old-nick new-nick)
  154. (unless (equal? old-nick (irc-connection-nick conn))
  155. (send-to-client
  156. conn
  157. (irc-message (format "~a!~a@lolcathost" old-nick old-nick)
  158. "NICK"
  159. (list new-nick)))))
  160. ;; The loop for handling commands from the client
  161. ;; return type is Nothing ==> the function does not terminate
  162. ;; NB: we read from the client with the timeout
  163. ;; of 333 > the frequency of PINGs
  164. ;; so the client should respond to the PING within
  165. ;; some number of seconds in order to keep the connection alive
  166. (: handle-user-messages (-> irc-connection Custodian Nothing))
  167. (define (handle-user-messages conn custodian)
  168. (define nick (irc-connection-nick conn))
  169. (define msg (read-from-client conn #:timeout 333))
  170. (match msg
  171. [(irc-message _ "PING" (list ping))
  172. (send-to-client conn
  173. (irc-message "lolcathost"
  174. "PONG"
  175. (list "lolcathost"
  176. (string-append ":" ping))))]
  177. [(irc-message _ "PONG" (list pong))
  178. (void)]
  179. [(irc-message _ "NICK" params)
  180. ;; TODO: propagate this info along the WS
  181. (set-irc-connection-nick! conn (car params))]
  182. [(irc-message _ "JOIN" (list chan))
  183. #:when (equal? chan channel)
  184. (define c (irc-connection-ws-conn conn))
  185. (ws:send-join c (irc-connection-nick conn) "#00FFAA")
  186. (send-to-client conn (irc-message
  187. (format "~a!~a@lolcathost"
  188. (irc-connection-nick conn)
  189. (irc-connection-user conn))
  190. "JOIN"
  191. (list channel)))
  192. (send-to-client conn (irc-message
  193. ":lolcathost"
  194. RPL_TOPIC
  195. (list nick channel ":chatting hard")))
  196. (sleep 1.5) ;; is there a way around going to sleep? :-<
  197. (ws:send-users c)]
  198. [(irc-message _ "MODE" (cons chan _))
  199. #:when (equal? chan channel)
  200. (send-to-client conn (irc-message
  201. ":lolcathost"
  202. RPL_CHANNELMODEIS
  203. (list nick channel "+OwO")))]
  204. [(irc-message _ "LIST" _)
  205. (send-to-client conn (irc-message
  206. ":lolcathost"
  207. "002"
  208. (list nick " /list not implemented ")))
  209. ]
  210. [(irc-message _ "WHO" (list chan))
  211. #:when (equal? chan channel)
  212. (send-to-client conn (irc-message
  213. ":lolcathost"
  214. RPL_WHOREPLY
  215. (list nick channel
  216. (irc-connection-user conn)
  217. "lolcathost"
  218. "lolcathost"
  219. nick
  220. "H"
  221. ":0")))
  222. (send-to-client conn (irc-message
  223. ":lolcathost"
  224. RPL_ENDOFWHO
  225. (list nick channel ":End of /WHO list.")))]
  226. [(irc-message _ "WHOIS" (list target))
  227. (send-to-client conn (irc-message
  228. ":lolcathost"
  229. RPL_WHOISUSER
  230. (list nick target "neko" "lolcathost" "*" ":This user is a cat")))
  231. (send-to-client conn (irc-message
  232. ":lolcathost"
  233. RPL_WHOISSERVER
  234. (list nick target "lolcathost" ":🐈")))
  235. (send-to-client conn (irc-message
  236. ":lolcathost"
  237. RPL_ENDOFWHOIS
  238. (list nick target ":End of /WHOIS list")))]
  239. [(irc-message _ "PRIVMSG" (list chan msg))
  240. #:when (equal? chan channel)
  241. (send-ws-message conn msg)]
  242. [(irc-message _ "STATS" '())
  243. (send-ws-message conn "/STATS")]
  244. [(or (? eof-object?)
  245. (irc-message _ "QUIT" _))
  246. ;; somehow attach this to a custodian?
  247. (log-warning (format "ircd.rkt: Closing socket for ~a" (irc-connection-nick conn)))
  248. (custodian-shutdown-all custodian)]
  249. [#f ;;; were unable to parse the string correctly
  250. (void)]
  251. [(var msg)
  252. (log-warning (format "ircd.rkt/handle-user-message: unknown message: ~a" msg))])
  253. (handle-user-messages conn custodian))
  254. ;; Utils
  255. (: send-ws-message (-> irc-connection String Void))
  256. (define (send-ws-message conn msg)
  257. (ws:send-message (irc-connection-ws-conn conn) msg))
  258. (: welcome-user (-> irc-connection Void))
  259. (define (welcome-user conn)
  260. (define nick (irc-connection-nick conn))
  261. (: notify-nick (-> String Void))
  262. (define (notify-nick msg)
  263. (send-to-client conn
  264. (irc-message "lolcathost" "002" (list nick msg))))
  265. ;; "001" has to be a string, otherwise it's converted to 1
  266. (send-to-client conn (irc-message "lolcathost" "001" (list nick "[OwO]")))
  267. (notify-nick ":[Mar 2020] NEW! /STATS command & timeouts for broken connections")
  268. (notify-nick ":[Mar 2020] bug fixes (erc support), displaying correct topic")
  269. (notify-nick ":[Feb 2020] support for HexChat, JOIN & PARTs, html encoding of symbols")
  270. (notify-nick ":-----------------------------------------------------------------------------")
  271. (notify-nick ":If you encounter an error, try reconnecting!")
  272. (notify-nick ":This IRCd does not support many features, like user to user messages or channel lists.")
  273. (notify-nick ":A lot of things are broken, please submit an issues to ")
  274. (notify-nick ": --> <https://notabug.org/epi/movie-night-chat> <--")
  275. (notify-nick ":This pwogwam comes with ABSOWUTEWY NyO WAWWANTY!11oneone")
  276. (notify-nick ":This is fwee softwawe, and you awe wewcome to wedistwibute it")
  277. (notify-nick ":undew cewtain conditions; see the LICENSE file for details UwU :3")
  278. (notify-nick ":-----------------------------------------------------------------------------")
  279. (for ([x cofe])
  280. (notify-nick (string-append ":" x)))
  281. (notify-nick ":Welcome nyaa")
  282. (notify-nick (format ":Please join the channel ~a nyaa" channel)))
  283. (define cofe
  284. '(" ,. ,."
  285. " || ||"
  286. " ,''--''. ON THIS SERVER"
  287. " : (.)(.) : WE #cofe"
  288. " ,' `. "
  289. " : : "
  290. " : : hash tag IHBA gang"
  291. " -ctr- `._m____m_,' "))