em_server.rb 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. #!/usr/bin/env ruby
  2. require 'eventmachine'
  3. require 'digest/md5'
  4. # .. allowed commands .. ROLES = CAPABILITIES
  5. # normal users have ROLE broadcast. Roles are defined on a per-user basis .. in a config.
  6. $version = "0.2EventMachineSocketServer"
  7. $debug = 0
  8. $role_commands = Hash[
  9. "everyone" => ["PING", "WHO", "C", "PART"],
  10. "broadcast_admin" => ["BC_ID", "BC", "BC_ENDCOUNT"],
  11. "broadcast" => ["REQ_BC", "BC_RE"],
  12. "specbot_admin" => ["REQ_ASSIGN", "REQ_UNASSIGN", "REQ_PING", "REQ_ASSIGNMENTS"],
  13. "specbot" => ["ASSIGN_RE", "UNASSIGN_RE", "PING_RE", "ASSIGNMENTS_RE"],
  14. ]
  15. $default_role = "everyone"
  16. # which role is talking to which role?
  17. # effectively it says: this (local) command is sent to that (remote) topic .. that certain topic is read by that user with that role.
  18. $role_dialogs = Hash[
  19. "everyone" => ["everyone"],
  20. "broadcast_admin" => ["broadcast"],
  21. "broadcast" => ["broadcast_admin"],
  22. "specbot_admin" => ["specbot"],
  23. "specbot" => ["specbot_admin"],
  24. ]
  25. $user_roles = Hash[
  26. "paul_dev_eggdrop" => ["everyone", "broadcast"],
  27. "paul_eggdrop" => ["everyone", "broadcast"],
  28. "paul_dev_specbot" => ["everyone", "broadcast", "specbot"],
  29. "paul_specbot" => ["everyone", "broadcast", "specbot"],
  30. "qw.nu" => ["everyone", "broadcast"],
  31. "qw.nu_poster" => ["everyone", "broadcast"],
  32. "mihawk_dev_specbot" => ["everyone", "broadcast", "specbot"],
  33. "mihawk_specbot" => ["everyone", "broadcast", "specbot"],
  34. "central_brain" => ["everyone", "broadcast_admin", "specbot_admin"],
  35. ]
  36. module InterconnectionPointProtocolHandler
  37. @@connected_clients = Array.new
  38. @@broadcasts = Hash.new
  39. # default method that is being run on connection!
  40. def post_init # connection of someone starts here...
  41. @username = nil
  42. end
  43. ### getters
  44. def entered_username?
  45. !@username.nil? && !@username.empty? # then it's true
  46. end
  47. def username
  48. @username
  49. end
  50. def my_roles
  51. return $user_roles[@username]
  52. end
  53. def my_cmds
  54. return my_roles.collect {|v| $role_commands[v]}.flatten
  55. end
  56. # returns online users by default by searching through saved connections
  57. def ousers
  58. users = Array.new
  59. @@connected_clients.each {|c| users.push(c.username)}
  60. return users
  61. end # of ousers
  62. ### checkers
  63. def allowed_cmd(inputmessage)
  64. my_cmds.each{|c|
  65. #print "A #{c} B #{inputmessage}\n"
  66. if inputmessage =~ /^#{c}/
  67. return true
  68. end
  69. }
  70. return false
  71. end
  72. def online(user)
  73. if ousers.include? user
  74. return true
  75. else
  76. return false
  77. end
  78. end
  79. ### actions
  80. def put_log(msg)
  81. puts "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
  82. end
  83. # searches through our hash of saved connections and writes them a messages.
  84. def write_user(input, username = nil)
  85. if not username
  86. username = @username
  87. end
  88. if connection = @@connected_clients.find { |c| c.username == username }
  89. sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}"
  90. line = "#{sometime}: #{input}\n"
  91. connection.send_data line
  92. end
  93. end # of write_user
  94. # searches through roles and writes those users on their connections
  95. def write_role(role, input, *exempts)
  96. #find users with role
  97. users = Array.new
  98. $user_roles.each {|k, v|
  99. if v.include? role
  100. users.push(k)
  101. end
  102. }
  103. # find users that are online and inside Array "users"
  104. lala = Array.new
  105. users.each {|v|
  106. if ousers.include? v
  107. lala.push(v)
  108. end
  109. }
  110. # now write to them
  111. lala.each {|user|
  112. if not exempts.include? user
  113. write_user(input, user)
  114. end
  115. }
  116. end # of write_role()
  117. # what happens with what input?!
  118. def inputting(input)
  119. write_user("SYS You typed: #{input}")
  120. if input =~ /^([A-Z_]+)/
  121. cmd = $1
  122. end
  123. # now we have the cmd .. or not ;)
  124. if input =~ /^[A-Z_]+ (.+)/
  125. payload = $1
  126. end
  127. # now we can use payload
  128. if allowed_cmd(cmd)
  129. ### typical system commands follow
  130. if cmd == "PING"
  131. write_user("PONG")
  132. elsif cmd == "C"
  133. if payload =~ /^(.+)$/
  134. write_role($default_role, "C #{@username}: #{$1}")
  135. else
  136. write_user("SYS Format is C <chattext>")
  137. end
  138. elsif cmd == "WHO"
  139. ousers.each {|ouser| write_user("WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}")}
  140. elsif cmd == "PART"
  141. write_user("SYS Goodbye '#{@username}'.")
  142. write_role($default_role, "PARTED User '#{@username}' just left the party.", @username)
  143. return "bye"
  144. ###now for the good stuff, broadcasting
  145. elsif cmd == "REQ_BC"
  146. # help with format .. but send the raw payload
  147. if payload =~ /^(.+),(.+),(.+),'(.+)','(.+)'$/
  148. # n f s ni txt
  149. error = 0
  150. # $&
  151. # The string matched by the last successful pattern match in this scope, or nil if the last pattern match failed. (Mnemonic: like & in some editors.) This variable is r
  152. network = $1
  153. freqname = $2
  154. source = $3
  155. nickname = $4
  156. text = $5
  157. if not network =~ /^(QWalt)|(QDEV)/
  158. write_user("SYS Network name #{network} is unknown: QWalt or QDEV allowed.")
  159. error = 1
  160. end
  161. if not freqname =~ /^(-qw-)|(-spam-)|(-dev-)/
  162. write_user("SYS Frequency name is unknown #{freqname}")
  163. error = 1
  164. end
  165. if not source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/
  166. write_user("SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}")
  167. error = 1
  168. end
  169. if not nickname =~ /^.+/
  170. write_user("SYS Nickname string is not in the form ^.+ #{nickname}")
  171. error = 1
  172. end
  173. if not text =~ /^.+/
  174. write_user("SYS Message string is not in the form ^.+ #{text}")
  175. error = 1
  176. end
  177. else # of check syntax
  178. write_user("SYS Command format is REQ_BC <network>,<frequency>,<source/channel>,'<nickname>','<message>'")
  179. error = 1
  180. end
  181. if error == 0
  182. # send REQ_BC to the corresponding role.
  183. bcid = Digest::MD5.hexdigest("%d %s %s %s %s %s" % [Time.now.utc.to_i, network, freqname, source, nickname, text])
  184. # so it only reaches the issuer of REQ_BC
  185. write_user("BC_ID #{bcid} for: #{network},#{freqname},#{source}")
  186. @@broadcasts[bcid] = @username
  187. finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text]
  188. write_role("broadcast", finalmessage, @username) # write to the role with the exempt of myself.
  189. end
  190. #end of REQ_BC here..
  191. elsif cmd == "BC_RE"
  192. if payload =~ /^(.+) (.+)=(\d+),(.+)=(\d+)$/
  193. bcid = $1
  194. user_string = $2
  195. user_count = $3
  196. item_string = $4
  197. item_count = $5
  198. payload = "%s %s=%d,%s=%d" % [bcid, user_string, user_count, item_string, item_count]
  199. # according bcid it is possible to get the originating user.. so send only to his topic!
  200. if @@broadcasts[bcid]
  201. to_user = @@broadcasts[bcid]
  202. write_user("SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.")
  203. write_user("#{cmd} #{payload}", to_user)
  204. else
  205. write_user("SYS Broadcast reply bcid: #{bcid} underway.")
  206. write_role("broadcast", "#{cmd} #{payload}", @username)
  207. end
  208. else # of bc_re format check
  209. put_log "SYS Format is BC_RE <bcid> <userstring>=<usercount>,<itemstring>=<itemcount>"
  210. end
  211. end
  212. else # of if allowed command
  213. write_user("SYS Command #{input} not allowed, your commands are: #{my_cmds.join(", ")}.")
  214. end # of if allowed command
  215. end # of inputting!
  216. # default method that is being run on receiving data!
  217. def receive_data(data) # connection receives a line on the server end
  218. if not entered_username? # oh, it seems, it's a new user
  219. # this is the user name coming in!
  220. username = data.chomp
  221. if online(username)
  222. put_log "SYS User '#{username}' tried to double connect. Say bye bye."
  223. send_data "SYS Hey '#{username}', only one connection allowed. Bye.\n"
  224. EventMachine.add_timer 1, proc { close_connection }
  225. return false
  226. end
  227. if not $user_roles.any? {|k, v| k.include? username}
  228. put_log "SYS User '#{username}' unknown to me. Saying bye."
  229. send_data "SYS User '#{username}' unknown to me. Bye.\n"
  230. # disconnect him
  231. close_connection
  232. else
  233. @username = username
  234. @@connected_clients.push(self)
  235. put_log("SYS '#{@username}' connected. Online now: #{ousers.join(", ")}")
  236. write_user("HELLO Hi user '#{@username}'! How are you? I'm '#{$version}'")
  237. write_user("ROLES #{my_roles.join(", ")}")
  238. write_user("COMMANDS #{my_cmds.join(", ")}")
  239. write_role($default_role, "JOINED User '#{@username}' just joined the party.", @username)
  240. end
  241. else # of username not known (first connect)
  242. # it's alive!
  243. inputter = inputting(data.chomp)
  244. if inputter == "bye"
  245. close_connection
  246. end
  247. end # of username not known
  248. end
  249. # default method that is being run on disconnection!
  250. def unbind # a connection goes bye bye
  251. if @username
  252. @@connected_clients.delete(self)
  253. put_log "SYS '#{@username}' disconnected. Online now: #{ousers.join(", ")}"
  254. end
  255. end
  256. end
  257. def main
  258. # #run: Note that this will block current thread.
  259. EventMachine.run {
  260. EventMachine.start_server "0.0.0.0", 7337, InterconnectionPointProtocolHandler
  261. }
  262. end
  263. main