irc-translator.lisp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. (defpackage :irc-translator
  2. (:use :cl :hurd-common :mach
  3. :hurd :hurd-translator
  4. :hurd-tree-translator
  5. :flexi-streams))
  6. (in-package :irc-translator)
  7. (defvar *nickname* (first ext:*args*))
  8. (defvar *server* (second ext:*args*))
  9. (defconstant +max-file-size+ 5000)
  10. (defun create-adjustable-array (&key (size 0) (contents nil))
  11. (make-array size
  12. :adjustable t
  13. :fill-pointer t
  14. :element-type '(unsigned-byte 8)
  15. :initial-contents contents))
  16. (defun has-data-p (connection)
  17. (let ((result (socket:socket-status (irc:network-stream connection))))
  18. (or (eq :input result)
  19. (eq :io result))))
  20. (defclass irc-translator (tree-translator)
  21. ((file-stat :initarg :file-stat
  22. :initform nil
  23. :accessor file-stat
  24. :documentation "Stat information for regular files.")
  25. (dir-stat :initarg :dir-stat
  26. :initform nil
  27. :accessor dir-stat
  28. :documentation "Stat information for directories.")
  29. (connection :initarg :connection
  30. :initform nil
  31. :accessor connection
  32. :documentation "Irc connection object.")
  33. (notice-node :initform nil
  34. :accessor notice-node
  35. :documentation "Node with notices from the server.")))
  36. (defclass data-entry ()
  37. ((contents :initform nil
  38. :initarg :data
  39. :accessor data)))
  40. (defclass channel-obj-entry ()
  41. ((channel :initarg :channel
  42. :accessor channel)))
  43. (defclass channel-entry (dir-entry channel-obj-entry) ())
  44. (defclass topic-entry (data-entry entry channel-obj-entry) ())
  45. (defclass users-entry (entry channel-obj-entry) ())
  46. (defclass log-entry (entry channel-obj-entry data-entry) ())
  47. (defclass notice-entry (log-entry) ())
  48. (defclass kick-entry (channel-obj-entry entry) ())
  49. (defclass pvt-entry (log-entry)
  50. ((user :initarg :user
  51. :accessor user)))
  52. (defun update-topic-data (node)
  53. (setf (data node)
  54. (string-to-octets (concatenate-string
  55. (irc:topic (channel node))
  56. (list #\Newline)))))
  57. (defun read-from-data-entry (node start amount stream)
  58. (let* ((size (stat-get (stat node) 'st-size))
  59. (size-res (- size start)))
  60. (unless (plusp size-res)
  61. (return-from read-from-data-entry t))
  62. (let* ((total (min size-res amount))
  63. (end (+ start total)))
  64. (write-sequence (subseq (data node) start end)
  65. stream)
  66. t)))
  67. (define-callback read-file irc-translator
  68. ((node topic-entry) user start amount stream)
  69. (when (has-access-p node user :read)
  70. (when (null (data node))
  71. (update-topic-data node))
  72. (read-from-data-entry node start amount stream)))
  73. (define-callback read-file irc-translator
  74. ((node kick-entry) user start amount stream)
  75. (declare (ignore start amount stream))
  76. (has-access-p node user :read)) ;; Nothing to read at all.
  77. (defun get-key-list (hashtable)
  78. (sort (loop for key being the hash-keys of hashtable
  79. collect key)
  80. #'string<))
  81. (define-callback read-file irc-translator
  82. ((node users-entry) user start amount stream)
  83. (when (has-access-p node user :read)
  84. (let* ((users (irc:users (channel node)))
  85. (size (calculate-users-size users))
  86. (size-res (- size start)))
  87. (unless (plusp size-res)
  88. (return-from read-file t))
  89. (let* ((total (min size-res amount))
  90. (end (+ start total))
  91. (pos 0))
  92. (loop for nick in (get-key-list users)
  93. do (cond
  94. ((>= pos end)
  95. (return-from read-file t))
  96. (t
  97. (loop for a across (concatenate-string nick
  98. (list #\Newline))
  99. do (progn
  100. (cond
  101. ((>= pos end)
  102. (return-from read-file t))
  103. ((>= pos start)
  104. (write-byte (char-code a) stream)))
  105. (incf pos))))))
  106. t))))
  107. (define-callback read-file irc-translator
  108. ((node log-entry) user start amount stream)
  109. (when (has-access-p node user :read)
  110. (read-from-data-entry node start amount stream)))
  111. (define-callback write-file irc-translator
  112. ((node notice-entry) user offset stream amount)
  113. (declare (ignore user offset stream amount))
  114. nil)
  115. (defun get-message-stream (stream amount)
  116. (let ((array (make-array amount :element-type '(unsigned-byte 8))))
  117. (read-sequence array stream)
  118. (string-trim (list #\Newline)
  119. (octets-to-string array))))
  120. (define-callback write-file irc-translator
  121. ((node pvt-entry) user offset stream amount)
  122. (declare (ignore offset))
  123. (when (has-access-p node user :write)
  124. (let ((msg (get-message-stream stream amount)))
  125. (irc:privmsg (connection translator)
  126. (user node)
  127. msg)
  128. (add-new-info node
  129. (make-privmsg-string *nickname* msg))
  130. t)))
  131. (define-callback write-file irc-translator
  132. ((node log-entry) user offset stream amount)
  133. (declare (ignore offset))
  134. (when (has-access-p node user :write)
  135. (let ((msg (get-message-stream stream amount)))
  136. (irc:privmsg (connection *translator*)
  137. (channel node)
  138. msg)
  139. (add-new-info node
  140. (make-privmsg-string *nickname* msg))
  141. t)))
  142. (define-callback write-file irc-translator
  143. ((node kick-entry) user offset stream amount)
  144. (declare (ignore offset))
  145. (when (has-access-p node user :write)
  146. (let* ((kick-str (string-trim " " (get-message-stream stream amount)))
  147. (space-pos (position #\Space kick-str))
  148. (reason-p (not (null space-pos))))
  149. (let ((nick (if reason-p (subseq kick-str 0 space-pos) kick-str))
  150. (reason (if reason-p (string-trim " " (subseq kick-str (1+ space-pos)))
  151. "no reason")))
  152. (irc:kick (connection *translator*)
  153. (channel node)
  154. (irc:find-user (connection *translator*)
  155. nick)
  156. reason)
  157. t))))
  158. (define-callback write-file irc-translator
  159. ((node topic-entry) user offset stream amount)
  160. (declare (ignore offset))
  161. (when (has-access-p node user :write)
  162. (let ((new-topic (get-message-stream stream amount)))
  163. (irc:topic- (connection *translator*)
  164. (channel node)
  165. new-topic)
  166. t)))
  167. (define-callback report-no-users irc-translator
  168. ((node topic-entry))
  169. (setf (data node) nil))
  170. (defun calculate-users-size (table)
  171. (loop for key being the hash-keys of table
  172. sum (1+ (length key))))
  173. (define-callback refresh-node irc-translator
  174. ((node topic-entry) user)
  175. (declare (ignore user))
  176. (setf (stat-get (stat node) 'st-size)
  177. (1+ (length (irc:topic (channel node))))))
  178. (define-callback refresh-node irc-translator
  179. ((node users-entry) user)
  180. (declare (ignore user))
  181. (let* ((users (irc:users (channel node)))
  182. (size (calculate-users-size users)))
  183. (setf (stat-get (stat node) 'st-size) size)))
  184. (defmethod do-remove-directory-entry ((found node) node name)
  185. (remove-dir-entry node name))
  186. (defmethod do-remove-directory-entry ((found channel-entry) node name)
  187. (declare (ignore node))
  188. (irc:part (connection *translator*)
  189. (irc:normalized-name (channel found))
  190. (format nil "rm ~a" name))
  191. t)
  192. (define-callback remove-directory-entry irc-translator
  193. (node user name)
  194. (let ((found (get-entry node name)))
  195. (when (and found
  196. (is-owner-p found user))
  197. (do-remove-directory-entry found node name))))
  198. (define-callback create-directory irc-translator
  199. (node user name mode)
  200. (declare (ignore mode))
  201. (unless (eq node (root *translator*))
  202. (return-from create-directory nil))
  203. (unless (is-owner-p node user)
  204. (return-from create-directory nil))
  205. (let ((old (get-entry node name)))
  206. (cond
  207. (old nil)
  208. (t
  209. (irc:join (connection *translator*)
  210. (concatenate-string "#"
  211. name))
  212. t))))
  213. (define-callback shutdown irc-translator
  214. ()
  215. (irc:quit (connection *translator*) "settrans -g")
  216. (sleep 0.5))
  217. (defun make-pvt-file (root user)
  218. (let ((new-entry (make-instance 'pvt-entry
  219. :parent root
  220. :stat (make-stat (file-stat *translator*))
  221. :data (create-adjustable-array)
  222. :user user)))
  223. (add-entry root new-entry user)))
  224. (define-callback create-file irc-translator
  225. (node user filename mode)
  226. (declare (ignore user mode))
  227. (when (eq node (root translator))
  228. (make-pvt-file (root translator)
  229. filename)))
  230. (define-callback fill-root-node irc-translator
  231. ((node dir-entry))
  232. (setf (file-stat translator)
  233. (make-stat (stat node)
  234. :mode (make-mode :perms '((:owner :read :write)
  235. (:group :read)))
  236. :type :reg
  237. :size 0))
  238. (setf (dir-stat translator)
  239. (make-stat (stat node)
  240. :nlink 0
  241. :mode (make-mode :perms '((:owner :read :exec)
  242. (:group :read :exec)))
  243. :type :dir))
  244. (setf (connection translator)
  245. (irc:connect :nickname *nickname* :server *server*))
  246. (let ((notice-entry (make-instance 'notice-entry
  247. :parent node
  248. :stat (make-stat (file-stat translator))
  249. :data (create-adjustable-array))))
  250. (setf (notice-node translator) notice-entry)
  251. (add-entry node notice-entry "notice")))
  252. (defmethod add-new-info ((node log-entry) str)
  253. (let* ((current-size (stat-get (stat node) 'st-size))
  254. (final-str (concatenate-string str (list #\Newline)))
  255. (this-size (length final-str))
  256. (new-size (+ current-size this-size)))
  257. (adjust-array (data node)
  258. new-size
  259. :fill-pointer t)
  260. (replace (data node) (string-to-octets final-str)
  261. :start1 current-size)
  262. (when (> new-size +max-file-size+)
  263. (let ((extra (- new-size +max-file-size+)))
  264. (setf (data node)
  265. (create-adjustable-array :size +max-file-size+
  266. :contents (subseq (data node) extra)))
  267. (decf new-size extra)))
  268. (setf (stat-get (stat node) 'st-size) new-size)))
  269. (defmethod add-new-info ((channel-name string) str)
  270. (let ((found (get-entry (root *translator*) channel-name)))
  271. (when (and found
  272. (typep found 'channel-entry))
  273. (let ((found2 (get-entry found "conversation")))
  274. (when (and found2
  275. (typep found2 'log-entry))
  276. (add-new-info found2 str))))))
  277. (defun create-new-channel (orig-channel channel)
  278. (let* ((channel-obj (irc:find-channel (connection *translator*)
  279. orig-channel))
  280. (channel-dir (make-instance 'channel-entry
  281. :parent (root *translator*)
  282. :stat (make-stat (dir-stat *translator*))
  283. :channel channel-obj)))
  284. (assert (not (null channel-obj)))
  285. (add-entry (root *translator*) channel-dir channel)
  286. (let ((topic-entry (make-instance 'topic-entry
  287. :parent channel-dir
  288. :stat (make-stat (file-stat *translator*))
  289. :channel channel-obj))
  290. (users-entry (make-instance 'users-entry
  291. :parent channel-dir
  292. :stat (make-stat (file-stat *translator*))
  293. :channel channel-obj))
  294. (conversation-entry (make-instance 'log-entry
  295. :parent channel-dir
  296. :stat (make-stat (file-stat *translator*))
  297. :data (create-adjustable-array)
  298. :channel channel-obj))
  299. (kick-entry (make-instance 'kick-entry
  300. :parent channel-dir
  301. :stat (make-stat (file-stat *translator*))
  302. :channel channel-obj)))
  303. (add-entry channel-dir kick-entry "kick")
  304. (add-entry channel-dir conversation-entry "conversation")
  305. (add-entry channel-dir users-entry "users")
  306. (add-entry channel-dir topic-entry "topic"))))
  307. (defun get-channel-name (str)
  308. (string-left-trim "#"
  309. (string-downcase str)))
  310. (defun handle-join (msg)
  311. (let* ((orig-channel (first (irc:arguments msg)))
  312. (channel (get-channel-name orig-channel))
  313. (who (irc:source msg)))
  314. (when (string= who *nickname*)
  315. (create-new-channel orig-channel channel))
  316. (add-new-info channel
  317. (format nil "~s enters the room" who))))
  318. (defun remove-channel (name)
  319. (remove-dir-entry (root *translator*) name))
  320. (defun handle-part (msg)
  321. (let* ((args (irc:arguments msg))
  322. (orig-channel (first (irc:arguments msg)))
  323. (channel (get-channel-name orig-channel))
  324. (who (irc:source msg)))
  325. (when (string= who *nickname*)
  326. (remove-channel channel))
  327. (add-new-info channel
  328. (format nil "~s exits the room (~s)" who
  329. (if (null (rest args))
  330. "no reason"
  331. (second args))))))
  332. (defun make-privmsg-string (who msg)
  333. (format nil "~s: ~a" who msg))
  334. (defun handle-privmsg-pvt (source msg)
  335. (add-new-info (if (has-entry-p (root *translator*) source)
  336. (get-entry (root *translator*) source)
  337. (make-pvt-file (root *translator*) source))
  338. (make-privmsg-string source msg)))
  339. (defun handle-privmsg (msg)
  340. (let ((dest (first (irc:arguments msg)))
  341. (src (irc:source msg))
  342. (str (second (irc:arguments msg))))
  343. (cond
  344. ((string= dest *nickname*)
  345. (handle-privmsg-pvt src str))
  346. (t
  347. (add-new-info (get-channel-name dest)
  348. (make-privmsg-string src str))))))
  349. (defun join-string-list (string-list)
  350. "Concatenates a list of strings and puts spaces between the elements."
  351. (format nil "~{~A~^ ~}" string-list))
  352. (defun handle-notice (msg)
  353. (add-new-info (notice-node *translator*)
  354. (join-string-list (irc:arguments msg))))
  355. (defun handle-quit (msg)
  356. (add-new-info (notice-node *translator*)
  357. (format nil "QUIT: ~a ~a"
  358. (irc:source msg)
  359. (join-string-list (irc:arguments msg)))))
  360. (defun handle-kick (msg)
  361. (let* ((args (irc:arguments msg))
  362. (orig-channel (first (irc:arguments msg)))
  363. (channel (get-channel-name orig-channel))
  364. (kicker (irc:source msg))
  365. (kicked (second args))
  366. (reason (if (= (length args) 3) (third args) "no reason")))
  367. (add-new-info channel
  368. (format nil "~s kicks ~s from the room (~s)"
  369. kicker
  370. kicked
  371. reason))
  372. (when (string= kicked *nickname*)
  373. (remove-channel channel)
  374. (add-new-info (notice-node *translator*)
  375. (format nil "KICK: You got kicked from ~s by ~s (~s)"
  376. orig-channel
  377. kicker
  378. reason)))))
  379. (defun handle-irc-message (msg)
  380. (let ((cmd (irc:command msg)))
  381. (cond
  382. ((string= "JOIN" cmd)
  383. (handle-join msg))
  384. ((string= "PART" cmd)
  385. (handle-part msg))
  386. ((string= "PRIVMSG" cmd)
  387. (handle-privmsg msg))
  388. ((or (string= "NOTICE" cmd)
  389. (and (>= (length cmd) 3)
  390. (or (string= "ERR" (subseq cmd 0 3))
  391. (string= "RPL" (subseq cmd 0 3)))))
  392. (handle-notice msg))
  393. ((string= "QUIT" cmd)
  394. (handle-quit msg))
  395. ((string= "KICK" cmd)
  396. (handle-kick msg))
  397. ((or (string= "PING" cmd)
  398. (string= "UNKNOWN-REPLY" cmd))))))
  399. (defun main ()
  400. (let ((translator
  401. (make-instance 'irc-translator
  402. :name "irc-translator")))
  403. (setup-translator translator)
  404. (let ((*translator* translator))
  405. (loop do (progn
  406. (wait :miliseconds 100)
  407. (loop while (has-data-p (connection *translator*))
  408. do (handler-bind
  409. ((irc:no-such-reply
  410. #'(lambda (c)
  411. (declare (ignore c))
  412. (continue))))
  413. (handle-irc-message (irc:read-message
  414. (connection *translator*))))))))))
  415. (main)