|
@@ -0,0 +1,373 @@
|
|
|
|
+#!/usr/bin/env ruby
|
|
|
|
+
|
|
|
|
+require 'eventmachine'
|
|
|
|
+require 'digest/md5'
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# .. allowed commands .. ROLES = CAPABILITIES
|
|
|
|
+# normal users have ROLE broadcast. Roles are defined on a per-user basis .. in a config.
|
|
|
|
+
|
|
|
|
+$version = "0.2EventMachineSocketServer"
|
|
|
|
+$debug = 0
|
|
|
|
+
|
|
|
|
+$role_commands = Hash[
|
|
|
|
+ "everyone" => ["PING", "WHO", "C", "PART"],
|
|
|
|
+
|
|
|
|
+ "broadcast_admin" => ["BC_ID", "BC", "BC_ENDCOUNT"],
|
|
|
|
+ "broadcast" => ["REQ_BC", "BC_RE"],
|
|
|
|
+
|
|
|
|
+ "specbot_admin" => ["REQ_ASSIGN", "REQ_UNASSIGN", "REQ_PING", "REQ_ASSIGNMENTS"],
|
|
|
|
+ "specbot" => ["ASSIGN_RE", "UNASSIGN_RE", "PING_RE", "ASSIGNMENTS_RE"],
|
|
|
|
+ ]
|
|
|
|
+
|
|
|
|
+$default_role = "everyone"
|
|
|
|
+
|
|
|
|
+# which role is talking to which role?
|
|
|
|
+# effectively it says: this (local) command is sent to that (remote) topic .. that certain topic is read by that user with that role.
|
|
|
|
+$role_dialogs = Hash[
|
|
|
|
+ "everyone" => ["everyone"],
|
|
|
|
+
|
|
|
|
+ "broadcast_admin" => ["broadcast"],
|
|
|
|
+ "broadcast" => ["broadcast_admin"],
|
|
|
|
+
|
|
|
|
+ "specbot_admin" => ["specbot"],
|
|
|
|
+ "specbot" => ["specbot_admin"],
|
|
|
|
+ ]
|
|
|
|
+
|
|
|
|
+$user_roles = Hash[
|
|
|
|
+ "paul_dev_eggdrop" => ["everyone", "broadcast"],
|
|
|
|
+ "paul_eggdrop" => ["everyone", "broadcast"],
|
|
|
|
+
|
|
|
|
+ "paul_dev_specbot" => ["everyone", "broadcast", "specbot"],
|
|
|
|
+ "paul_specbot" => ["everyone", "broadcast", "specbot"],
|
|
|
|
+
|
|
|
|
+ "qw.nu" => ["everyone", "broadcast"],
|
|
|
|
+ "qw.nu_poster" => ["everyone", "broadcast"],
|
|
|
|
+
|
|
|
|
+ "mihawk_dev_specbot" => ["everyone", "broadcast", "specbot"],
|
|
|
|
+ "mihawk_specbot" => ["everyone", "broadcast", "specbot"],
|
|
|
|
+
|
|
|
|
+ "central_brain" => ["everyone", "broadcast_admin", "specbot_admin"],
|
|
|
|
+ ]
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+module InterconnectionPointProtocolHandler
|
|
|
|
+
|
|
|
|
+ @@connected_clients = Array.new
|
|
|
|
+ @@broadcasts = Hash.new
|
|
|
|
+
|
|
|
|
+ # default method that is being run on connection!
|
|
|
|
+ def post_init # connection of someone starts here...
|
|
|
|
+ @username = nil
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ ### getters
|
|
|
|
+ def entered_username?
|
|
|
|
+ !@username.nil? && !@username.empty? # then it's true
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ def username
|
|
|
|
+ @username
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ def my_roles
|
|
|
|
+ return $user_roles[@username]
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ def my_cmds
|
|
|
|
+ return my_roles.collect {|v| $role_commands[v]}.flatten
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ # returns online users by default by searching through saved connections
|
|
|
|
+ def ousers
|
|
|
|
+ def online(user)
|
|
|
|
+ if ousers.include? user
|
|
|
|
+ return true
|
|
|
|
+ else
|
|
|
|
+ return false
|
|
|
|
+ end
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ users = Array.new
|
|
|
|
+ @@connected_clients.each {|c| users.push(c.username)}
|
|
|
|
+ return users
|
|
|
|
+ end # of ousers
|
|
|
|
+
|
|
|
|
+ ### checkers getters
|
|
|
|
+ def allowed_cmd(inputmessage)
|
|
|
|
+ my_cmds.each{|c|
|
|
|
|
+ #print "A #{c} B #{inputmessage}\n"
|
|
|
|
+ if inputmessage =~ /^#{c}/
|
|
|
|
+ return true
|
|
|
|
+ end
|
|
|
|
+ }
|
|
|
|
+ return false
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ ### actions
|
|
|
|
+ def put_log(msg)
|
|
|
|
+ puts "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ # searches through our hash of saved connections and writes them a messages.
|
|
|
|
+ def write_user(input, username = nil)
|
|
|
|
+ if not username
|
|
|
|
+ username = @username
|
|
|
|
+ end
|
|
|
|
+ if connection = @@connected_clients.find { |c| c.username == username }
|
|
|
|
+ sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}"
|
|
|
|
+ line = "#{sometime}: #{input}\n"
|
|
|
|
+ connection.send_data line
|
|
|
|
+ end
|
|
|
|
+ end # of write_user
|
|
|
|
+
|
|
|
|
+ # searches through roles and writes those users on their connections
|
|
|
|
+ def write_role(role, input, *exempts)
|
|
|
|
+ #find users with role
|
|
|
|
+ users = Array.new
|
|
|
|
+ $user_roles.each {|k, v|
|
|
|
|
+ if v.include? role
|
|
|
|
+ users.push(k)
|
|
|
|
+ end
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ # find users that are online and inside Array "users"
|
|
|
|
+ lala = Array.new
|
|
|
|
+ users.each {|v|
|
|
|
|
+ if ousers.include? v
|
|
|
|
+ lala.push(v)
|
|
|
|
+ end
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ # now write to them
|
|
|
|
+ lala.each {|user|
|
|
|
|
+ if not exempts.include? user
|
|
|
|
+ write_user(input, user)
|
|
|
|
+ end
|
|
|
|
+ }
|
|
|
|
+ end # of write_role()
|
|
|
|
+
|
|
|
|
+ # what happens with what input?!
|
|
|
|
+ def inputting(input)
|
|
|
|
+
|
|
|
|
+ write_user("SYS You typed: #{input}")
|
|
|
|
+
|
|
|
|
+ if input =~ /^([A-Z_]+)/
|
|
|
|
+ cmd = $1
|
|
|
|
+ end
|
|
|
|
+ # now we have the cmd .. or not ;)
|
|
|
|
+
|
|
|
|
+ if input =~ /^[A-Z_]+ (.+)/
|
|
|
|
+ payload = $1
|
|
|
|
+ end
|
|
|
|
+ # now we can use payload
|
|
|
|
+
|
|
|
|
+ if allowed_cmd(cmd)
|
|
|
|
+
|
|
|
|
+ ### typical system commands follow
|
|
|
|
+ if cmd == "PING"
|
|
|
|
+ write_user("PONG")
|
|
|
|
+
|
|
|
|
+ elsif cmd == "C"
|
|
|
|
+
|
|
|
|
+ if payload =~ /^(.+)$/
|
|
|
|
+ write_role($default_role, "C #{@username}: #{$1}")
|
|
|
|
+
|
|
|
|
+ else
|
|
|
|
+ write_user("SYS Format is C <chattext>")
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ elsif cmd == "WHO"
|
|
|
|
+ ousers.each {|ouser| write_user("WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}")}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ elsif cmd == "PART"
|
|
|
|
+ write_user("SYS Goodbye '#{@username}'.")
|
|
|
|
+ write_role($default_role, "PARTED User '#{@username}' just left the party.", @username)
|
|
|
|
+ return "bye"
|
|
|
|
+
|
|
|
|
+ ###now for the good stuff, broadcasting
|
|
|
|
+ elsif cmd == "REQ_BC"
|
|
|
|
+
|
|
|
|
+ # help with format .. but send the raw payload
|
|
|
|
+ if payload =~ /^(.+),(.+),(.+),'(.+)','(.+)'$/
|
|
|
|
+ # n f s ni txt
|
|
|
|
+ error = 0
|
|
|
|
+ # $&
|
|
|
|
+ # 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
|
|
|
|
+ network = $1
|
|
|
|
+ freqname = $2
|
|
|
|
+ source = $3
|
|
|
|
+ nickname = $4
|
|
|
|
+ text = $5
|
|
|
|
+
|
|
|
|
+ if not network =~ /^(QWalt)|(QDEV)/
|
|
|
|
+ write_user("SYS Network name #{network} is unknown: QWalt or QDEV allowed.")
|
|
|
|
+ error = 1
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ if not freqname =~ /^(-qw-)|(-spam-)|(-dev-)/
|
|
|
|
+ write_user("SYS Frequency name is unknown #{freqname}")
|
|
|
|
+ error = 1
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ if not source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/
|
|
|
|
+ write_user("SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}")
|
|
|
|
+ error = 1
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ if not nickname =~ /^.+/
|
|
|
|
+ write_user("SYS Nickname string is not in the form ^.+ #{nickname}")
|
|
|
|
+ error = 1
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ if not text =~ /^.+/
|
|
|
|
+ write_user("SYS Message string is not in the form ^.+ #{text}")
|
|
|
|
+ error = 1
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ else # of check syntax
|
|
|
|
+ write_user("SYS Command format is REQ_BC <network>,<frequency>,<source/channel>,'<nickname>','<message>'")
|
|
|
|
+ error = 1
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ if error == 0
|
|
|
|
+
|
|
|
|
+ # send REQ_BC to the corresponding role.
|
|
|
|
+ bcid = Digest::MD5.hexdigest("%d %s %s %s %s %s" % [Time.now.utc.to_i, network, freqname, source, nickname, text])
|
|
|
|
+
|
|
|
|
+ # so it only reaches the issuer of REQ_BC
|
|
|
|
+ write_user("BC_ID #{bcid} for: #{network},#{freqname},#{source}")
|
|
|
|
+ @@broadcasts[bcid] = @username
|
|
|
|
+
|
|
|
|
+ finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text]
|
|
|
|
+ write_role("broadcast", finalmessage, @username) # write to the role with the exempt of myself.
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ end
|
|
|
|
+ #end of REQ_BC here..
|
|
|
|
+
|
|
|
|
+ elsif cmd == "BC_RE"
|
|
|
|
+
|
|
|
|
+ if payload =~ /^(.+) (.+)=(\d+),(.+)=(\d+)$/
|
|
|
|
+
|
|
|
|
+ bcid = $1
|
|
|
|
+ user_string = $2
|
|
|
|
+ user_count = $3
|
|
|
|
+ item_string = $4
|
|
|
|
+ item_count = $5
|
|
|
|
+
|
|
|
|
+ payload = "%s %s=%d,%s=%d" % [bcid, user_string, user_count, item_string, item_count]
|
|
|
|
+ # according bcid it is possible to get the originating user.. so send only to his topic!
|
|
|
|
+
|
|
|
|
+ if @@broadcasts[bcid]
|
|
|
|
+ to_user = @@broadcasts[bcid]
|
|
|
|
+
|
|
|
|
+ write_user("SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.")
|
|
|
|
+ write_user("#{cmd} #{payload}", to_user)
|
|
|
|
+ else
|
|
|
|
+ write_user("SYS Broadcast reply bcid: #{bcid} underway.")
|
|
|
|
+ write_role("broadcast", "#{cmd} #{payload}", @username)
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ else # of bc_re format check
|
|
|
|
+ put_log "SYS Format is BC_RE <bcid> <userstring>=<usercount>,<itemstring>=<itemcount>"
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ else # of if allowed command
|
|
|
|
+ write_user("SYS Command #{input} not allowed, your commands are: #{my_cmds.join(", ")}.")
|
|
|
|
+ end # of if allowed command
|
|
|
|
+
|
|
|
|
+ end # of inputting!
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ # default method that is being run on receiving data!
|
|
|
|
+ def receive_data(data) # connection receives a line on the server end
|
|
|
|
+
|
|
|
|
+ if not entered_username? # oh, it seems, it's a new user
|
|
|
|
+ # this is the user name coming in!
|
|
|
|
+ username = data.chomp
|
|
|
|
+ if not $user_roles.any? {|k, v| k.include? username}
|
|
|
|
+ put_log "SYS User '#{username}' unknown to me. Saying bye."
|
|
|
|
+ send_data "SYS User '#{username}' unknown to me. Bye."
|
|
|
|
+
|
|
|
|
+ # disconnect him
|
|
|
|
+ close_connection
|
|
|
|
+ else
|
|
|
|
+ @username = username
|
|
|
|
+ @@connected_clients.push(self)
|
|
|
|
+
|
|
|
|
+ put_log("'#{@username}' connected. Online now: #{ousers.join(", ")}")
|
|
|
|
+
|
|
|
|
+ write_user("HELLO Hi user '#{@username}'! How are you? I'm '#{$version}'")
|
|
|
|
+ write_user("ROLES #{my_roles.join(", ")}")
|
|
|
|
+ write_user("COMMANDS #{my_cmds.join(", ")}")
|
|
|
|
+ write_role($default_role, "JOINED User '#{@username}' just joined the party.", @username)
|
|
|
|
+
|
|
|
|
+ end
|
|
|
|
+ else # of username not known (first connect)
|
|
|
|
+ # it's alive!
|
|
|
|
+ inputter = inputting(data.chomp)
|
|
|
|
+
|
|
|
|
+ if inputter == "bye"
|
|
|
|
+ close_connection
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ end # of username not known
|
|
|
|
+
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ # default method that is being run on disconnection!
|
|
|
|
+ def unbind # a connection goes bye bye
|
|
|
|
+ if @username
|
|
|
|
+ @@connected_clients.delete(self)
|
|
|
|
+ put_log "'#{@username}' disconnected. Online now: #{ousers.join(", ")}"
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ end
|
|
|
|
+end
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def main
|
|
|
|
+
|
|
|
|
+ # #run: Note that this will block current thread.
|
|
|
|
+ EventMachine.run {
|
|
|
|
+ EventMachine.start_server "0.0.0.0", 7338, InterconnectionPointProtocolHandler
|
|
|
|
+ }
|
|
|
|
+end
|
|
|
|
+
|
|
|
|
+main
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|