flamethrower 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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