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.
- 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
|