irc-chatdir.scm 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #! /bin/sh
  2. #|
  3. exec csi -s "$0" "$@"
  4. |#
  5. ;;
  6. ;; Copyright 2023, Jaidyn Levesque <jadedctrl@posteo.at>
  7. ;;
  8. ;; This program is free software: you can redistribute it and/or
  9. ;; modify it under the terms of the GNU General Public License as
  10. ;; published by the Free Software Foundation, either version 3 of
  11. ;; the License, or (at your option) any later version.
  12. ;;
  13. ;; This program is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;;
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. ;;
  21. (import scheme
  22. (chicken file) (chicken file posix) (chicken io) (chicken pathname)
  23. (chicken process-context) (chicken process-context posix)
  24. (chicken string)
  25. srfi-1 srfi-13 srfi-18 srfi-69
  26. (prefix chatdir chatdir:) (prefix chatdir-inotify chatdir:)
  27. ircc
  28. getopt-long)
  29. ;; Join an IRC channel
  30. (define (make-join-channel-callback connection)
  31. (let ([root-dir (hash-table-ref connection 'directory)])
  32. (lambda (channel)
  33. (irc:write-cmd connection "JOIN" channel))))
  34. ;; Leave an IRC channel
  35. (define (make-leave-channel-callback connection)
  36. (let ([root-dir (hash-table-ref connection 'directory)])
  37. (lambda (channel)
  38. (irc:write-cmd connection "PART" channel))))
  39. ;; Send message to an IRC channel
  40. (define (make-send-message-callback connection)
  41. (let ([root-dir (hash-table-ref connection 'directory)])
  42. (lambda (channel message)
  43. (irc:write-cmd connection "PRIVMSG" channel message)
  44. (chatdir:channel-message-add! root-dir channel message
  45. (hash-table-ref connection 'nick)))))
  46. ;; Hook function for irc:loop; handles all IRC commands
  47. (define (make-irc-command-callback conn)
  48. (let ([root-dir (hash-table-ref conn 'directory)])
  49. (lambda (conn cmd params #!optional sender tags)
  50. (cond
  51. [(and (string=? cmd "PRIVMSG")
  52. (string? sender)
  53. (irc:channel? (car params))
  54. (irc:hostmask? sender))
  55. (let ([target (if (irc:user-is-self? conn (car params))
  56. (irc:hostmask-nick sender)
  57. (car params))])
  58. (chatdir:channel-message-add! root-dir target
  59. (last params) (irc:hostmask-nick sender)))]
  60. [(and (string=? cmd "PRIVMSG")
  61. (string? sender)
  62. (irc:hostmask? sender))
  63. (chatdir:channel-add! root-dir (irc:hostmask-nick sender))
  64. (chatdir:channel-message-add! root-dir (irc:hostmask-nick sender)
  65. (last params) (irc:hostmask-nick sender))]
  66. [(or (string=? cmd "NOTICE")
  67. (and (string=? cmd "PRIVMSG")
  68. (or (string-null? sender) (not (irc:hostmask? sender)))))
  69. (chatdir:channel-message-add! root-dir ".server" (last params))]
  70. [(and (string=? cmd "JOIN") (irc:user-is-self? conn sender))
  71. (chatdir:channel-add! root-dir (last params))]
  72. [(string=? cmd "JOIN")
  73. (let ([channel (car params)]
  74. [nick (irc:hostmask-nick sender)])
  75. (chatdir:channel-user-add! root-dir channel nick)
  76. (chatdir:channel-user-toggle-states! root-dir channel nick
  77. "online" "offline"))]
  78. [(string=? cmd "PART")
  79. (chatdir:channel-user-toggle-states!
  80. root-dir (car params) (irc:hostmask-nick sender)
  81. "offline" "online")]))))
  82. ;; [(string=? cmd "NICK")
  83. ;; (chatd-json-write conn
  84. ;; (compose-event-alist conn "user-info" #:user (last params)))])
  85. ;; Hook function for irc:loop; handles all IRC errors and replies
  86. (define (make-irc-reply-callback conn)
  87. (let ([root-dir (hash-table-ref conn 'directory)])
  88. (lambda (conn reply params #!optional sender tags)
  89. (let ([channel (second params)])
  90. (cond
  91. ;; If topic set, output to a channel's .topic file
  92. [(and (eq? reply RPL_TOPIC)
  93. (irc:channel? channel))
  94. (chatdir:channel-metadata-set! root-dir channel
  95. "topic" (last params))]
  96. [(and (eq? reply RPL_TOPICWHOTIME)
  97. (irc:channel? (second params)))
  98. (chatdir:channel-metadata-set!
  99. root-dir channel "topic" #f
  100. (if (last params)
  101. `((user.chat.sender . ,(third params))
  102. (user.chat.date . ,(last params)))
  103. `((user.chat.sender . ,(third params)))))]
  104. ;; We've got to add users, when they join the room!
  105. [(or (and (irc:capability? conn 'userhost-in-names)
  106. (eq? reply RPL_ENDOFNAMES))
  107. (eq? reply RPL_ENDOFWHO))
  108. (map (lambda (nick)
  109. (let ([hostmask (irc:user-get conn nick 'hostmask)])
  110. (chatdir:channel-user-add! root-dir channel nick)
  111. (chatdir:channel-user-toggle-states! root-dir channel nick
  112. "online" "offline")))
  113. (irc:channel-users conn (second params)))]
  114. [(string? (last params))
  115. (chatdir:channel-message-add! root-dir ".server" (last params))])))))
  116. (define *help-msg*
  117. (string-append
  118. "usage: irc-chatd [-hd] [-n nick] [-u user] [-p password] hostname\n\n"
  119. "`chatd` is a standard format for chat client-daemons; the goal being that a\n"
  120. "chat client should be able to work with any chat protocol (IRC, XMPP, etc)\n"
  121. "just by reading and writing to files served by a `chatd` daemon, without\n"
  122. "having to worry about the protocol in use.\n\n"
  123. "irc-chatd is a `chatd`-compliant IRC client-daemon, that outputs all messages\n"
  124. "from the server in parseable format to an output file, and receives input\n"
  125. "from a FIFO File.\n".))
  126. (define *opts*
  127. '((help
  128. "Print a usage message"
  129. (single-char #\h))
  130. (nickname
  131. "Your preferred nickname. Default is your system username."
  132. (single-char #\n)
  133. (value (required NICK)))
  134. (username
  135. "Username of the connection. Default is your system username."
  136. (single-char #\u)
  137. (value (required USERNAME)))
  138. (password
  139. "The password optionally used in connection."
  140. (single-char #\p)
  141. (value (required PASSWORD)))
  142. (name
  143. "Set the realname of your connection."
  144. (value (required NAME)))
  145. (directory
  146. "Root directory for channels and messages. Defaults to CWD."
  147. (single-char #\o)
  148. (value (required PATH)))
  149. (debug
  150. (single-char #\d)
  151. "Print all messages received from the IRC server.")))
  152. ;; Prints cli usage to stderr.
  153. (define (help)
  154. (write-string *help-msg* #f (open-output-file* fileno/stderr))
  155. (write-string (usage *opts*) #f (open-output-file* fileno/stderr))
  156. (exit 1))
  157. (define (wait-for-registration connection)
  158. (if (not (hash-table-exists? connection 'registered))
  159. (begin
  160. (thread-sleep! .1)
  161. (wait-for-registration connection))
  162. #t))
  163. ;; The `main` procedure that should be called to run feedsnake-unix for use as script.
  164. (define (main)
  165. (let* ([args (getopt-long (command-line-arguments) *opts*)]
  166. [free-args (alist-ref '@ args)])
  167. (if (or (null? free-args) (alist-ref 'help args))
  168. (help))
  169. (let*
  170. ([username (or (alist-ref 'username args)
  171. (current-effective-user-name))]
  172. [password (alist-ref 'password args)]
  173. [nickname (or (alist-ref 'nickname args)
  174. (current-effective-user-name))]
  175. [fullname (alist-ref 'name args)]
  176. [server (last free-args)]
  177. [hostname (first (string-split server ":"))]
  178. [port (string->number (or (last (string-split server ":"))
  179. "6697"))]
  180. [directory (normalize-pathname
  181. (string-append (or (alist-ref 'directory args) "./")
  182. "/"))]
  183. [connection (if server
  184. (irc:connect hostname port username nickname password fullname)
  185. #f)])
  186. (unless connection
  187. (help))
  188. (hash-table-set! connection 'directory
  189. (normalize-pathname (string-append directory "/")))
  190. (create-directory (string-append directory "/.server"))
  191. ;; Kick off the input loop, which monitors channels' .in/ dirs
  192. (thread-start!
  193. (make-thread
  194. (lambda ()
  195. (let ([callbacks
  196. `((join-channel . ,(make-join-channel-callback connection))
  197. (leave-channel . ,(make-leave-channel-callback connection))
  198. (send-message . ,(make-send-message-callback connection)))])
  199. (thread-sleep! 10)
  200. (chatdir:input-loop-init directory callbacks)
  201. (chatdir:input-loop directory callbacks)))
  202. "Chat input"))
  203. (print (hash-table-ref connection 'directory))
  204. ;; Kick off the main loop!
  205. (irc:loop connection
  206. (make-irc-command-callback connection)
  207. (make-irc-reply-callback connection)
  208. (alist-ref 'debug args)))))
  209. (main)