flamethrower 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +29 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +36 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/bin/flamethrower +59 -0
- data/flamethrower.gemspec +106 -0
- data/lib/flamethrower.rb +25 -0
- data/lib/flamethrower/campfire/connection.rb +38 -0
- data/lib/flamethrower/campfire/message.rb +42 -0
- data/lib/flamethrower/campfire/rest_api.rb +40 -0
- data/lib/flamethrower/campfire/room.rb +142 -0
- data/lib/flamethrower/campfire/user.rb +17 -0
- data/lib/flamethrower/dispatcher.rb +102 -0
- data/lib/flamethrower/irc/channel.rb +55 -0
- data/lib/flamethrower/irc/codes.rb +17 -0
- data/lib/flamethrower/irc/commands.rb +57 -0
- data/lib/flamethrower/irc/message.rb +41 -0
- data/lib/flamethrower/irc/user.rb +33 -0
- data/lib/flamethrower/server.rb +43 -0
- data/lib/flamethrower/server/event_server.rb +28 -0
- data/lib/flamethrower/server/mock_server.rb +13 -0
- data/spec/fixtures/enter_message.json +1 -0
- data/spec/fixtures/kick_message.json +1 -0
- data/spec/fixtures/leave_message.json +1 -0
- data/spec/fixtures/message.json +1 -0
- data/spec/fixtures/paste_message.json +1 -0
- data/spec/fixtures/room.json +1 -0
- data/spec/fixtures/room_update.json +1 -0
- data/spec/fixtures/rooms.json +1 -0
- data/spec/fixtures/speak_message.json +1 -0
- data/spec/fixtures/streaming_message.json +1 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit/campfire/connection_spec.rb +37 -0
- data/spec/unit/campfire/message_spec.rb +85 -0
- data/spec/unit/campfire/room_spec.rb +296 -0
- data/spec/unit/campfire/user_spec.rb +32 -0
- data/spec/unit/dispatcher_spec.rb +255 -0
- data/spec/unit/irc/channel_spec.rb +56 -0
- data/spec/unit/irc/message_spec.rb +32 -0
- data/spec/unit/irc/user_spec.rb +63 -0
- data/spec/unit/server/event_server_spec.rb +16 -0
- data/spec/unit/server_spec.rb +164 -0
- 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
|