flamethrower 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +29 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +36 -0
  7. data/Rakefile +28 -0
  8. data/VERSION +1 -0
  9. data/bin/flamethrower +59 -0
  10. data/flamethrower.gemspec +106 -0
  11. data/lib/flamethrower.rb +25 -0
  12. data/lib/flamethrower/campfire/connection.rb +38 -0
  13. data/lib/flamethrower/campfire/message.rb +42 -0
  14. data/lib/flamethrower/campfire/rest_api.rb +40 -0
  15. data/lib/flamethrower/campfire/room.rb +142 -0
  16. data/lib/flamethrower/campfire/user.rb +17 -0
  17. data/lib/flamethrower/dispatcher.rb +102 -0
  18. data/lib/flamethrower/irc/channel.rb +55 -0
  19. data/lib/flamethrower/irc/codes.rb +17 -0
  20. data/lib/flamethrower/irc/commands.rb +57 -0
  21. data/lib/flamethrower/irc/message.rb +41 -0
  22. data/lib/flamethrower/irc/user.rb +33 -0
  23. data/lib/flamethrower/server.rb +43 -0
  24. data/lib/flamethrower/server/event_server.rb +28 -0
  25. data/lib/flamethrower/server/mock_server.rb +13 -0
  26. data/spec/fixtures/enter_message.json +1 -0
  27. data/spec/fixtures/kick_message.json +1 -0
  28. data/spec/fixtures/leave_message.json +1 -0
  29. data/spec/fixtures/message.json +1 -0
  30. data/spec/fixtures/paste_message.json +1 -0
  31. data/spec/fixtures/room.json +1 -0
  32. data/spec/fixtures/room_update.json +1 -0
  33. data/spec/fixtures/rooms.json +1 -0
  34. data/spec/fixtures/speak_message.json +1 -0
  35. data/spec/fixtures/streaming_message.json +1 -0
  36. data/spec/spec_helper.rb +13 -0
  37. data/spec/unit/campfire/connection_spec.rb +37 -0
  38. data/spec/unit/campfire/message_spec.rb +85 -0
  39. data/spec/unit/campfire/room_spec.rb +296 -0
  40. data/spec/unit/campfire/user_spec.rb +32 -0
  41. data/spec/unit/dispatcher_spec.rb +255 -0
  42. data/spec/unit/irc/channel_spec.rb +56 -0
  43. data/spec/unit/irc/message_spec.rb +32 -0
  44. data/spec/unit/irc/user_spec.rb +63 -0
  45. data/spec/unit/server/event_server_spec.rb +16 -0
  46. data/spec/unit/server_spec.rb +164 -0
  47. metadata +165 -0
@@ -0,0 +1,142 @@
1
+ module Flamethrower
2
+ module Campfire
3
+ class Room
4
+ include Flamethrower::Campfire::RestApi
5
+
6
+ attr_reader :stream, :token
7
+ attr_writer :topic
8
+ attr_accessor :inbound_messages, :outbound_messages, :thread_messages, :number, :name, :users, :server
9
+ attr_accessor :failed_messages
10
+
11
+ def initialize(domain, token, params = {})
12
+ @domain = domain
13
+ @token = token
14
+ @inbound_messages = Queue.new
15
+ @outbound_messages = Queue.new
16
+ @failed_messages = []
17
+ @number = params['id']
18
+ @name = params['name']
19
+ @topic = params['topic']
20
+ @users = []
21
+ @thread_running = false
22
+ end
23
+
24
+ def topic
25
+ @topic || "No topic"
26
+ end
27
+
28
+ def send_topic!(topic)
29
+ response = campfire_put("/room/#{@number}.json", {:topic => topic}.to_json)
30
+ @topic = topic if response.code == "200"
31
+ end
32
+
33
+ def fetch_room_info
34
+ response = campfire_get("/room/#{@number}.json")
35
+ json = JSON.parse(response.body)
36
+ json['room']['users'].each do |user|
37
+ @users << Flamethrower::Campfire::User.new(user)
38
+ end
39
+ end
40
+
41
+ def say(body, message_type='TextMessage')
42
+ params = {'body' => body, 'type' => message_type}
43
+ @outbound_messages << Flamethrower::Campfire::Message.new(params)
44
+ end
45
+
46
+ def start_thread
47
+ ::FLAMETHROWER_LOGGER.debug "Starting thread for room #{name}"
48
+ @thread_running = true
49
+ Thread.new do
50
+ connect
51
+ until dead?
52
+ fetch_messages
53
+ post_messages
54
+ requeue_failed_messages
55
+ messages_to_send = to_irc.retrieve_irc_messages
56
+ messages_to_send.each do |m|
57
+ ::FLAMETHROWER_LOGGER.debug "Sending irc message #{m.to_s}"
58
+ @server.send_message(m.to_s)
59
+ end
60
+ sleep 0.5
61
+ end
62
+ end
63
+ end
64
+
65
+ def alive?
66
+ @thread_running
67
+ end
68
+
69
+ def dead?
70
+ !@thread_running
71
+ end
72
+
73
+ def kill_thread!
74
+ @thread_running = false
75
+ end
76
+
77
+ def join
78
+ campfire_post("/room/#{@number}/join.json").code == "200"
79
+ end
80
+
81
+ def connect
82
+ ::FLAMETHROWER_LOGGER.debug "Connecting to #{name} stream"
83
+ @stream = Twitter::JSONStream.connect(:path => "/room/#{@number}/live.json",
84
+ :host => "streaming.campfirenow.com",
85
+ :auth => "#{@token}:x")
86
+ end
87
+
88
+ def fetch_messages
89
+ @stream.each_item do |item|
90
+ params = JSON.parse(item)
91
+ ::FLAMETHROWER_LOGGER.debug "Got json message #{params.inspect}"
92
+ params['user'] = @users.find {|u| u.number == params['user_id'] }
93
+ params['room'] = self
94
+ @inbound_messages << Flamethrower::Campfire::Message.new(params)
95
+ end
96
+ end
97
+
98
+ def post_messages
99
+ until @outbound_messages.empty?
100
+ message = @outbound_messages.pop
101
+ json = {"message" => {"body" => message.body, "type" => message.message_type}}.to_json
102
+ ::FLAMETHROWER_LOGGER.debug "Sending #{json} to campfire API"
103
+ response = campfire_post("/room/#{@number}/speak.json", json)
104
+ case response
105
+ when Net::HTTPCreated
106
+ message.mark_delivered!
107
+ else
108
+ ::FLAMETHROWER_LOGGER.debug "Failed to post to campfire API with code: #{response.inspect}"
109
+ message.mark_failed!
110
+ @failed_messages << message
111
+ end
112
+ end
113
+ end
114
+
115
+ def retrieve_messages
116
+ Array.new.tap do |new_array|
117
+ until @inbound_messages.empty?
118
+ new_array << @inbound_messages.pop
119
+ end
120
+ end
121
+ end
122
+
123
+ def requeue_failed_messages
124
+ @failed_messages.each do |m|
125
+ if m.retry_at > Time.now
126
+ @outbound_messages << m
127
+ @failed_messages.delete(m)
128
+ end
129
+ end
130
+ end
131
+
132
+ def to_irc
133
+ name = "##{@name.downcase.scan(/[A-Za-z0-9]+/).join("_")}"
134
+ @irc_channel = Flamethrower::Irc::Channel.new(name, self)
135
+ @irc_channel.tap do |channel|
136
+ channel.users = @users.map(&:to_irc)
137
+ channel.topic = topic.gsub("\n", "\s")
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,17 @@
1
+ module Flamethrower
2
+ module Campfire
3
+ class User
4
+ attr_accessor :name, :number
5
+
6
+ def initialize(params = {})
7
+ @name = params['name']
8
+ @number = params['id']
9
+ end
10
+
11
+ def to_irc
12
+ nick = @name.gsub("\s", "_")
13
+ @irc_user ||= Flamethrower::Irc::User.new(:username => nick, :nickname => nick, :hostname => 'campfirenow.com')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,102 @@
1
+ module Flamethrower
2
+ class Dispatcher
3
+ attr_reader :server
4
+
5
+ def initialize(server)
6
+ @server = server
7
+ end
8
+
9
+ def handle_message(message)
10
+ method = "handle_#{message.command.downcase}"
11
+ send(method, message) if protected_methods.include?(method)
12
+ end
13
+
14
+ private
15
+
16
+ def find_channel_or_error(name, error=Flamethrower::Irc::Codes::ERR_BADCHANNELKEY)
17
+ channel = server.irc_channels.detect {|channel| channel.name == name}
18
+ if channel && block_given?
19
+ yield(channel)
20
+ else
21
+ server.send_message(server.error(error))
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def handle_privmsg(message)
28
+ name, body = *message.parameters
29
+ find_channel_or_error(name) do |channel|
30
+ channel.to_campfire.say(body)
31
+ end
32
+ end
33
+
34
+ def handle_ping(message)
35
+ hostname = *message.parameters
36
+ server.send_pong(hostname)
37
+ end
38
+
39
+ def handle_user(message)
40
+ username, hostname, servername, realname = *message.parameters
41
+ server.current_user.username = username unless server.current_user.username
42
+ server.current_user.hostname = hostname unless server.current_user.hostname
43
+ server.current_user.servername = servername unless server.current_user.servername
44
+ server.current_user.realname = realname unless server.current_user.realname
45
+ if server.current_user.nick_set? && server.current_user.user_set?
46
+ server.after_connect
47
+ end
48
+ end
49
+
50
+ def handle_nick(message)
51
+ nickname = *message.parameters
52
+ server.current_user.nickname = nickname
53
+ if server.current_user.nick_set? && server.current_user.user_set?
54
+ server.after_connect
55
+ end
56
+ end
57
+
58
+ def handle_topic(message)
59
+ find_channel_or_error(message.parameters.first) do |channel|
60
+ channel.to_campfire.send_topic!(message.parameters.last) if message.parameters.size > 1
61
+ server.send_topic(channel)
62
+ end
63
+ end
64
+
65
+ def handle_mode(message)
66
+ first_param = message.parameters.first
67
+ error = Flamethrower::Irc::Codes::ERR_UNKNOWNCOMMAND
68
+ if first_param == server.current_user.nickname
69
+ server.send_user_mode
70
+ return
71
+ else
72
+ find_channel_or_error(first_param, error) do |channel|
73
+ server.send_channel_mode(channel)
74
+ end
75
+ end
76
+ end
77
+
78
+ def handle_join(message)
79
+ find_channel_or_error(message.parameters.first) do |channel|
80
+ room = channel.to_campfire
81
+ channel.users << server.current_user
82
+ room.join
83
+ room.fetch_room_info
84
+ room.start_thread
85
+ server.send_topic(channel)
86
+ server.send_userlist(channel)
87
+ end
88
+ end
89
+
90
+ def handle_part(message)
91
+ find_channel_or_error(message.parameters.first) do |channel|
92
+ room = channel.to_campfire
93
+ room.kill_thread!
94
+ end
95
+ end
96
+
97
+ def handle_quit(message)
98
+ server.irc_channels.each {|c| c.to_campfire.kill_thread!}
99
+ end
100
+ end
101
+ end
102
+
@@ -0,0 +1,55 @@
1
+ module Flamethrower
2
+ module Irc
3
+ class Channel
4
+
5
+ attr_accessor :name, :modes, :mode
6
+
7
+ def initialize(name, campfire_channel=nil)
8
+ @users = []
9
+ @name = name
10
+ @modes = ["t"]
11
+ @campfire_channel = campfire_channel
12
+ end
13
+
14
+ def mode
15
+ "+#{@modes.join}"
16
+ end
17
+
18
+ def topic
19
+ campfire_topic = to_campfire.topic
20
+ return "No topic" if campfire_topic && campfire_topic.empty?
21
+ campfire_topic
22
+ end
23
+
24
+ def topic=(topic)
25
+ to_campfire.topic = topic
26
+ end
27
+
28
+ def to_campfire
29
+ @campfire_channel
30
+ end
31
+
32
+ def users=(users)
33
+ @users = users
34
+ end
35
+
36
+ def users
37
+ @users.concat(@campfire_channel.users.map(&:to_irc))
38
+ end
39
+
40
+ def retrieve_irc_messages
41
+ to_campfire.retrieve_messages.inject([]) do |all_messages, message|
42
+ all_messages << message.to_irc if display_message?(message)
43
+ all_messages
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def display_message?(message)
50
+ message.message_type != "TimestampMessage"
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ module Flamethrower
2
+ module Irc
3
+ module Codes
4
+ RPL_MOTDSTART = 375
5
+ RPL_MOTD = 372
6
+ RPL_ENDOFMOTD = 376
7
+ RPL_TOPIC = 332
8
+ RPL_NAMEREPLY = 353
9
+ RPL_ENDOFNAMES = 366
10
+ RPL_CHANNELMODEIS = 324
11
+ RPL_UMODEIS = 221
12
+
13
+ ERR_UNKNOWNCOMMAND = 421
14
+ ERR_BADCHANNELKEY = 475
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ module Flamethrower
2
+ module Irc
3
+ module Commands
4
+ include Flamethrower::Irc::Codes
5
+
6
+ def send_motd
7
+ send_messages do |messages|
8
+ messages << reply(RPL_MOTDSTART, ":MOTD")
9
+ messages << reply(RPL_MOTD, ":Welcome to Flamethrower")
10
+ messages << reply(RPL_MOTD, ":Fetching channel list from campfire...")
11
+ end
12
+ end
13
+
14
+ def send_topic(channel)
15
+ send_message reply(RPL_TOPIC, "#{channel.name} :#{channel.topic}")
16
+ end
17
+
18
+ def send_channel_list
19
+ send_messages do |messages|
20
+ messages << reply(RPL_MOTD, ":Active channels:")
21
+ @irc_channels.each do |channel|
22
+ messages << reply(RPL_MOTD, ":#{channel.name} - #{channel.topic}")
23
+ end
24
+ messages << reply(RPL_ENDOFMOTD, ":End of channel list /MOTD")
25
+ end
26
+ end
27
+
28
+ def send_userlist(channel)
29
+ send_messages do |messages|
30
+ display_users = (["@#{@current_user.nickname}"] + channel.users.map(&:nickname)).join("\s")
31
+ messages << reply(RPL_NAMEREPLY, "= #{channel.name} :#{display_users}")
32
+ messages << reply(RPL_ENDOFNAMES, "#{channel.name} :/End of /NAMES list")
33
+ end
34
+ end
35
+
36
+ def send_channel_mode(channel)
37
+ send_message reply(RPL_CHANNELMODEIS, "#{channel.name} #{channel.mode}")
38
+ end
39
+
40
+ def send_pong(hostname)
41
+ send_message "PONG :#{hostname}"
42
+ end
43
+
44
+ def send_user_mode
45
+ send_message reply(RPL_UMODEIS, @current_user.mode)
46
+ end
47
+
48
+ def reply(code, message)
49
+ ":#{@current_user.hostname} #{code} #{@current_user.nickname} #{message}"
50
+ end
51
+
52
+ def error(code)
53
+ ":#{@current_user.hostname} #{code}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ module Flamethrower
2
+ module Irc
3
+ class Message
4
+ def initialize(message)
5
+ @message = message
6
+ end
7
+
8
+ def parse
9
+ terms = @message.split("\s")
10
+ params = terms - [terms.first]
11
+ {:command => terms.first, :params => strip_prefixes(params)}
12
+ end
13
+
14
+ def command
15
+ parse[:command]
16
+ end
17
+
18
+ def parameters
19
+ parse[:params]
20
+ end
21
+
22
+ def to_s
23
+ @message
24
+ end
25
+
26
+ protected
27
+
28
+ def strip_prefixes(params)
29
+ result = []
30
+ params.each_with_index do |param, i|
31
+ if param.match /^:(.*)/
32
+ result << params[i..params.length].join("\s").sub(":", "")
33
+ return result
34
+ else
35
+ result << param
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ module Flamethrower
2
+ module Irc
3
+ class User
4
+
5
+ attr_accessor :username, :nickname, :hostname, :realname, :servername, :modes
6
+
7
+ def initialize(options={})
8
+ @username = options[:username]
9
+ @nickname = options[:nickname]
10
+ @hostname = options[:hostname]
11
+ @realname = options[:realname]
12
+ @servername = options[:servername]
13
+ @modes = ["i"]
14
+ end
15
+
16
+ def nick_set?
17
+ !!@nickname
18
+ end
19
+
20
+ def user_set?
21
+ !!@username && !!@hostname && !!@realname && !!@servername
22
+ end
23
+
24
+ def mode
25
+ "+#{@modes.join}"
26
+ end
27
+
28
+ def to_s
29
+ "#{@nickname}!#{@username}@#{@hostname}"
30
+ end
31
+ end
32
+ end
33
+ end