Просмотр исходного кода

easy and fast first implementation of EventMachine

Paul Klumpp 12 лет назад
Родитель
Сommit
94c1f77fdc
1 измененных файлов с 373 добавлено и 0 удалено
  1. 373 0
      em_server.rb

+ 373 - 0
em_server.rb

@@ -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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+