diff --git a/README.md b/README.md index 629f8fb..5b3bbbb 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ Matchers are tested in order and all that satisfy the match and conditions will end # - # A special user and channel method is available in match blocks. + # A special user and room method is available in match blocks. # match "a user said" do - say "#{user} said something in channel #{channel}" + say "#{user} said something in room #{room}" end match "Hello!" do @@ -48,17 +48,17 @@ Matchers are tested in order and all that satisfy the match and conditions will end # - # Limit the match to certain channels, users or both. + # Limit the match to certain rooms, users or both. # - match /^Lets match (.+)$/, :conditions => {:channel => "Some Channel"} do - say "Only said if channel name mathces /someregex/" + match /^Lets match (.+)$/, :conditions => {:room => "Some Room"} do + say "Only said if room name mathces /someregex/" end match "some text", :conditions => {:user => "Some User"} do say "Only said if user name mathces /someregex/" end - match /some other text/, :conditions => {:user => "Some User", :channel => 123456} do + match /some other text/, :conditions => {:user => "Some User", :room => 123456} do say "You can mix conditions" end @@ -70,13 +70,13 @@ Matchers are tested in order and all that satisfy the match and conditions will end # - # You can say multiple times, and you can specify an alternate channel. - # Default behaviour is to 'say' in the channel that caused the match. + # You can say multiple times, and you can specify an alternate room. + # Default behaviour is to 'say' in the room that caused the match. # match "something" do - say "#{user} said something in channel #{channel}" - say "#{user} said something in channel #{channel}", 237872 - say "#{user} said something in channel #{channel}", "System Administration" + say "#{user} said something in room #{room}" + say "#{user} said something in room #{room}", 237872 + say "#{user} said something in room #{room}", "System Administration" end # @@ -95,27 +95,27 @@ Matchers are tested in order and all that satisfy the match and conditions will end end - # Connect and join some channels + # Connect and join some rooms scamp.connect!([293788, "Monitoring"]) -In the channel/user conditions you can use the name, regex or ID of a user or channel, in say you can ise a string or ID, eg: +In the room/user conditions you can use the name, regex or ID of a user or room, in say you can ise a string or ID, eg: - :conditions => {:channel => /someregex/} - :conditions => {:channel => "some string"} - :conditions => {:channel => 123456} + :conditions => {:room => /someregex/} + :conditions => {:room => "some string"} + :conditions => {:room => 123456} :conditions => {:user => /someregex/} :conditions => {:user => "some string"} :conditions => {:user => 123456} - say "#{user} said something in channel #{channel}", 237872 - say "#{user} said something in channel #{channel}", "System Administration" + say "#{user} said something in room #{room}", 237872 + say "#{user} said something in room #{room}", "System Administration" By default Scamp listens to itself. This could either be fun, or dangerous, you decide. You can turn this off by passing :first\_match\_only => true in the initialisation options scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :first_match_only => true) -Scamp will listen to all messages that are sent on the channels it is listening on and doesn't need to be addressed by name. If you prefer to only trigger bot commands when you address your bot directly add the :required\_prefix initialisation option: +Scamp will listen to all messages that are sent on the rooms it is listening on and doesn't need to be addressed by name. If you prefer to only trigger bot commands when you address your bot directly add the :required\_prefix initialisation option: scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :required_prefix => 'Bot: ') @@ -124,7 +124,7 @@ Scamp will now require commands to begin with 'Bot: ' (or whatever you have spec ## TODO * Write more tests -* Allow multiple values for conditions, eg: :conditions => {:channel => ["This channel", "Some channel"]} +* Allow multiple values for conditions, eg: :conditions => {:room => ["This room", "Some room"]} * Add paste support ## Known issues diff --git a/examples/bot.rb b/examples/bot.rb index 8aaccda..f2a1927 100755 --- a/examples/bot.rb +++ b/examples/bot.rb @@ -7,15 +7,15 @@ scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "37s") scamp.behaviour do - # Match some regex limited to a channel condition based on a channel id - match /^channel id (.+)$/, :conditions => {:channel => 401839} do - # Reply in the current channel - say "Match some regex limited to a channel condition based on a channel id" + # Match some regex limited to a room condition based on a room id + match /^room id (.+)$/, :conditions => {:room => 401839} do + # Reply in the current room + say "Match some regex limited to a room condition based on a room id" end - # Limit a match to a channel condition based on a string - match "channel name check", :conditions => {:channel => "Monitoring"} do - say "Limit a match to a channel condition based on a string" + # Limit a match to a room condition based on a string + match "room name check", :conditions => {:room => "Monitoring"} do + say "Limit a match to a room condition based on a string" end # Limit a match to a user condition based on a string @@ -28,10 +28,10 @@ say "Limit a match to a user condition based on an ID" end - # Limit a match to a channel & user condition combined - match /^something (.+)$/, :conditions => {:channel => "Monitoring", :user => "Will Jessop"} do - # Reply in the current channel - say "Limit a match to a channel & user condition combined" + # Limit a match to a room & user condition combined + match /^something (.+)$/, :conditions => {:room => "Monitoring", :user => "Will Jessop"} do + # Reply in the current room + say "Limit a match to a room & user condition combined" end # Match text with a regex, access the captures from the match object @@ -44,16 +44,16 @@ say "You said #{yousaid}" end - # Simple string match, interpolating the channel and user in response. + # Simple string match, interpolating the room and user in response. match "something" do |data| - # Send the response to a different channel - say "#{user} said something in channel #{channel}", "Robot Army" + # Send the response to a different room + say "#{user} said something in room #{room}", "Robot Army" - # Send the response to a different channel, using the channel ID - say "#{user} said something in channel #{channel}", 293788 + # Send the response to a different room, using the room ID + say "#{user} said something in room #{room}", 293788 - # Send the response to the originating channel - say "#{user} said something in channel #{channel}" + # Send the response to the originating room + say "#{user} said something in room #{room}" end # Play some sounds @@ -62,11 +62,11 @@ play "drama" end - match "multi-condition match", :conditions => {:channel => [401839, "Monitoring"], :user => ["Will Jessop", "Noah Lorang"]} do - # Reply in the current channel + match "multi-condition match", :conditions => {:room => [401839, "Monitoring"], :user => ["Will Jessop", "Noah Lorang"]} do + # Reply in the current room say "multi-condition match" end end -# FIXME: this does if the channel doesn't exist. Need a better error. +# FIXME: this does if the room doesn't exist. Need a better error. scamp.connect!([293788, "Monitoring"]) diff --git a/lib/scamp.rb b/lib/scamp.rb index 705d651..1f5d03b 100644 --- a/lib/scamp.rb +++ b/lib/scamp.rb @@ -5,7 +5,7 @@ require "scamp/version" require 'scamp/connection' -require 'scamp/channels' +require 'scamp/rooms' require 'scamp/users' require 'scamp/matcher' require 'scamp/action' @@ -13,12 +13,12 @@ class Scamp include Connection - include Channels + include Rooms include Users include Messages - attr_accessor :channels, :user_cache, :channel_cache, :matchers, :api_key, :subdomain, - :logger, :verbose, :first_match_only, :required_prefix, :channels_to_join + attr_accessor :rooms, :user_cache, :room_cache, :matchers, :api_key, :subdomain, + :logger, :verbose, :first_match_only, :required_prefix, :rooms_to_join def initialize(options = {}) options ||= {} @@ -34,10 +34,10 @@ def initialize(options = {}) end end - @channels_to_join = [] - @channels = {} + @rooms_to_join = [] + @rooms = {} @user_cache = {} - @channel_cache = {} + @room_cache = {} @matchers ||= [] end @@ -45,9 +45,9 @@ def behaviour &block instance_eval &block end - def connect!(channel_list) + def connect!(room_list) logger.info "Starting up" - connect(api_key, channel_list) + connect(api_key, room_list) end def command_list diff --git a/lib/scamp/action.rb b/lib/scamp/action.rb index fa34cd0..b99b86e 100644 --- a/lib/scamp/action.rb +++ b/lib/scamp/action.rb @@ -1,6 +1,6 @@ # # Actions are run in the context of a Scamp::Action. -# This allows us to make channel, user etc. methods +# This allows us to make room, user etc. methods # available on a per-message basis # @@ -27,12 +27,12 @@ def matches=(match) end if match.respond_to?(:names) # 1.8 doesn't support named captures end - def channel_id + def room_id @message[:room_id] end - def channel - bot.channel_name_for @message[:room_id] + def room + bot.room_name_for @message[:room_id] end def user @@ -57,12 +57,12 @@ def command_list bot.command_list end - def say(msg, channel_id_or_name = channel_id) - bot.say(msg, channel_id_or_name) + def say(msg, room_id_or_name = room_id) + bot.say(msg, room_id_or_name) end - def play(sound, channel_id_or_name = channel_id) - bot.play(sound, channel_id_or_name) + def play(sound, room_id_or_name = room_id) + bot.play(sound, room_id_or_name) end end end diff --git a/lib/scamp/connection.rb b/lib/scamp/connection.rb index 5b721e5..317aeb8 100644 --- a/lib/scamp/connection.rb +++ b/lib/scamp/connection.rb @@ -2,19 +2,19 @@ class Scamp module Connection private - def connect(api_key, channel_list) + def connect(api_key, room_list) EventMachine.run do - # Check for channels to join, and join them + # Check for rooms to join, and join them EventMachine::add_periodic_timer(5) do - while id = @channels_to_join.pop + while id = @rooms_to_join.pop join_and_stream(id) end end - populate_channel_list do - logger.debug "Adding #{channel_list.join ', '} to list of channels to join" - @channels_to_join = channel_list.map{|c| channel_id(c) } + populate_room_list do + logger.debug "Adding #{room_list.join ', '} to list of rooms to join" + @rooms_to_join = room_list.map{|c| room_id(c) } end end diff --git a/lib/scamp/matcher.rb b/lib/scamp/matcher.rb index 9661e66..484a805 100644 --- a/lib/scamp/matcher.rb +++ b/lib/scamp/matcher.rb @@ -68,18 +68,18 @@ def run(msg, match = nil) def conditions_satisfied_by(msg) bot.logger.debug "Checking message against #{conditions.inspect}" - # item will be :user or :channel + # item will be :nick or :room # cond is the int or string value. conditions.each do |item, cond| bot.logger.debug "Checking #{item} against #{cond}" bot.logger.debug "msg is #{msg.inspect}" if cond.is_a? Integer - # bot.logger.debug "item is #{msg[{:channel => :room_id, :user => :user_id}[item]]}" - return false unless msg[{:channel => :room_id, :user => :user_id}[item]] == cond + # bot.logger.debug "item is #{msg[{:room => :room_id, :user => :user_id}[item]]}" + return false unless msg[{:room => :room_id, :user => :user_id}[item]] == cond elsif cond.is_a? String case item - when :channel - return false unless bot.channel_name_for(msg[:room_id]) == cond + when :room + return false unless bot.room_name_for(msg[:room_id]) == cond when :user return false unless bot.username_for(msg[:user_id]) == cond end diff --git a/lib/scamp/messages.rb b/lib/scamp/messages.rb index 615db97..c590791 100644 --- a/lib/scamp/messages.rb +++ b/lib/scamp/messages.rb @@ -1,22 +1,22 @@ class Scamp module Messages - def say(message, channel_id_or_name) - send_message(channel_id_or_name, message, "Textmessage") + def say(message, room_id_or_name) + send_message(room_id_or_name, message, "Textmessage") end - def play(sound, channel_id_or_name) - send_message(channel_id_or_name, sound, "SoundMessage") + def play(sound, room_id_or_name) + send_message(room_id_or_name, sound, "SoundMessage") end private # curl -vvv -H 'Content-Type: application/json' -d '{"message":{"body":"Yeeeeeaaaaaahh", "type":"Textmessage"}}' -u API_KEY:X https://37s.campfirenow.com/room/293788/speak.json - def send_message(channel_id_or_name, payload, type) + def send_message(room_id_or_name, payload, type) # post 'speak', :body => {:message => {:body => message, :type => type}}.to_json - url = "https://#{subdomain}.campfirenow.com/room/#{channel_id(channel_id_or_name)}/speak.json" + url = "https://#{subdomain}.campfirenow.com/room/#{room_id(room_id_or_name)}/speak.json" http = EventMachine::HttpRequest.new(url).post :head => {'Content-Type' => 'application/json', 'authorization' => [api_key, 'X']}, :body => Yajl::Encoder.encode({:message => {:body => payload, :type => type}}) - http.errback { logger.error "Error speaking: '#{message}' to #{channel_id(channel_id_or_name)}" } + http.errback { logger.error "Error speaking: '#{message}' to #{room_id(room_id_or_name)}" } end end diff --git a/lib/scamp/channels.rb b/lib/scamp/rooms.rb similarity index 50% rename from lib/scamp/channels.rb rename to lib/scamp/rooms.rb index f901568..fb46e13 100644 --- a/lib/scamp/channels.rb +++ b/lib/scamp/rooms.rb @@ -1,75 +1,76 @@ class Scamp - module Channels + module Rooms # TextMessage (regular chat message), # PasteMessage (pre-formatted message, rendered in a fixed-width font), # SoundMessage (plays a sound as determined by the message, which can be either “rimshot”, “crickets”, or “trombone”), # TweetMessage (a Twitter status URL to be fetched and inserted into the chat) - def paste(text, channel) + def paste(text, room) end def upload end - def join(channel_id) - logger.info "Joining channel #{channel_id}" - url = "https://#{subdomain}.campfirenow.com/room/#{channel_id}/join.json" + def join(room_id) + logger.info "Joining room #{room_id}" + url = "https://#{subdomain}.campfirenow.com/room/#{room_id}/join.json" http = EventMachine::HttpRequest.new(url).post :head => {'Content-Type' => 'application/json', 'authorization' => [api_key, 'X']} - http.errback { logger.error "Error joining channel: #{channel_id}" } + http.errback { logger.error "Error joining room: #{room_id}" } http.callback { yield if block_given? } end - def channel_id(channel_id_or_name) - if channel_id_or_name.is_a? Integer - return channel_id_or_name + def room_id(room_id_or_name) + if room_id_or_name.is_a? Integer + return room_id_or_name else - return channel_id_from_channel_name(channel_id_or_name) + return room_id_from_room_name(room_id_or_name) end end - def channel_name_for(channel_id) - data = channel_cache_data(channel_id) + def room_name_for(room_id) + data = room_cache_data(room_id) return data["name"] if data - channel_id.to_s + room_id.to_s end private - def channel_cache_data(channel_id) - return channel_cache[channel_id] if channel_cache.has_key? channel_id - fetch_channel_data(channel_id) + def room_cache_data(room_id) + return room_cache[room_id] if room_cache.has_key? room_id + fetch_room_data(room_id) return false end - def populate_channel_list + def populate_room_list url = "https://#{subdomain}.campfirenow.com/rooms.json" http = EventMachine::HttpRequest.new(url).get :head => {'authorization' => [api_key, 'X']} - http.errback { logger.error "Error populating the channel list: #{http.status.inspect}" } + http.errback { logger.error "Error populating the room list: #{http.status.inspect}" } http.callback { - new_channels = {} + new_rooms = {} Yajl::Parser.parse(http.response)['rooms'].each do |c| - new_channels[c["name"]] = c + new_rooms[c["name"]] = c end - # No idea why using the "channels" accessor here doesn't + # No idea why using the "rooms" accessor here doesn't # work but accessing the ivar directly does. There's # Probably a bug. - @channels = new_channels # replace existing channel list + @rooms = new_rooms # replace existing room list yield if block_given? } end - def fetch_channel_data(channel_id) - logger.debug "Fetching channel data for #{channel_id}" - url = "https://#{subdomain}.campfirenow.com/room/#{channel_id}.json" + def fetch_room_data(room_id) + logger.debug "Fetching room data for #{room_id}" + url = "https://#{subdomain}.campfirenow.com/room/#{room_id}.json" http = EventMachine::HttpRequest.new(url).get :head => {'authorization' => [api_key, 'X']} - http.errback { logger.error "Couldn't get data for channel #{channel_id} at url #{url}" } + http.errback { logger.error "Couldn't get data for room #{room_id} at url #{url}" } http.callback { - logger.debug "Fetched channel data for #{channel_id}" + logger.debug "Fetched room data for #{room_id}" room = Yajl::Parser.parse(http.response)['room'] - channel_cache[room["id"]] = room + room_cache[room["id"]] = room + room['users'].each do |u| update_user_cache_with(u["id"], u) end @@ -78,27 +79,27 @@ def fetch_channel_data(channel_id) def join_and_stream(id) join(id) do - logger.info "Joined channel #{id} successfully" - fetch_channel_data(id) + logger.info "Joined room #{id} successfully" + fetch_room_data(id) stream(id) end end - def stream(channel_id) + def stream(room_id) json_parser = Yajl::Parser.new :symbolize_keys => true json_parser.on_parse_complete = method(:process_message) - url = "https://streaming.campfirenow.com/room/#{channel_id}/live.json" + url = "https://streaming.campfirenow.com/room/#{room_id}/live.json" # Timeout per https://github.com/igrigorik/em-http-request/wiki/Redirects-and-Timeouts http = EventMachine::HttpRequest.new(url, :connect_timeout => 20, :inactivity_timeout => 0).get :head => {'authorization' => [api_key, 'X']} - http.errback { logger.error "Couldn't stream channel #{channel_id} at url #{url}" } - http.callback { logger.info "Disconnected from #{url}"; channels_to_join << channel_id} + http.errback { logger.error "Couldn't stream room #{room_id} at url #{url}" } + http.callback { logger.info "Disconnected from #{url}"; rooms_to_join << room_id} http.stream {|chunk| json_parser << chunk } end - def channel_id_from_channel_name(channel_name) - logger.debug "Looking for channel id for #{channel_name}" - channels[channel_name]["id"] + def room_id_from_room_name(room_name) + logger.debug "Looking for room id for #{room_name}" + rooms[room_name]["id"] end end end diff --git a/spec/lib/scamp_spec.rb b/spec/lib/scamp_spec.rb index 8b2119b..40142dd 100644 --- a/spec/lib/scamp_spec.rb +++ b/spec/lib/scamp_spec.rb @@ -5,8 +5,8 @@ @valid_params = {:api_key => "6124d98749365e3db2c9e5b27ca04db6", :subdomain => "oxygen"} @valid_user_cache_data = {123 => {"name" => "foo"}, 456 => {"name" => "bar"}} - # Stub fetch for channel data - @valid_channel_cache_data = { + # Stub fetch for room data + @valid_room_cache_data = { 123 => { "id" => 123, "name" => "foo", @@ -19,8 +19,8 @@ } } - @valid_channel_cache_data.keys.each do |id| - json_response = Yajl::Encoder.encode(:room => @valid_channel_cache_data[id]) + @valid_room_cache_data.keys.each do |id| + json_response = Yajl::Encoder.encode(:room => @valid_room_cache_data[id]) stub_request(:get, "https://#{@valid_params[:subdomain]}.campfirenow.com/room/#{id}.json"). with(:headers => {'Authorization'=>[@valid_params[:api_key], 'X']}). to_return(:status => 200, :body => json_response) @@ -113,32 +113,32 @@ describe "matching" do context "with conditions" do - it "should limit matches by channel id" do + it "should limit matches by room id" do canary = mock canary.expects(:lives).once canary.expects(:bang).never bot = a Scamp bot.behaviour do - match("a string", :conditions => {:channel => 123}) {canary.lives} - match("a string", :conditions => {:channel => 456}) {canary.bang} + match("a string", :conditions => {:room => 123}) {canary.lives} + match("a string", :conditions => {:room => 456}) {canary.bang} end bot.send(:process_message, {:room_id => 123, :body => "a string"}) end - it "should limit matches by channel name" do + it "should limit matches by room name" do canary = mock canary.expects(:lives).once canary.expects(:bang).never bot = a Scamp bot.behaviour do - match("a string", :conditions => {:channel => "foo"}) {canary.lives} - match("a string", :conditions => {:channel => "bar"}) {canary.bang} + match("a string", :conditions => {:room => "foo"}) {canary.lives} + match("a string", :conditions => {:room => "bar"}) {canary.bang} end - bot.channel_cache = @valid_channel_cache_data + bot.room_cache = @valid_room_cache_data bot.send(:process_message, {:room_id => 123, :body => "a string"}) end @@ -173,18 +173,18 @@ bot.send(:process_message, {:user_id => 123, :body => "a string"}) end - it "should limit matches by channel and user" do + it "should limit matches by room and user" do canary = mock canary.expects(:lives).once canary.expects(:bang).never bot = a Scamp bot.behaviour do - match("a string", :conditions => {:channel => 123, :user => 123}) {canary.lives} - match("a string", :conditions => {:channel => 456, :user => 456}) {canary.bang} + match("a string", :conditions => {:room => 123, :user => 123}) {canary.lives} + match("a string", :conditions => {:room => 456, :user => 456}) {canary.bang} end - bot.channel_cache = @valid_channel_cache_data + bot.room_cache = @valid_room_cache_data bot.user_cache = @valid_user_cache_data bot.send(:process_message, {:room_id => 123, :user_id => 123, :body => "a string"}) bot.send(:process_message, {:room_id => 123, :user_id => 456, :body => "a string"}) @@ -314,20 +314,20 @@ end describe "match block" do - it "should make the channel details available to the action block" do + it "should make the room details available to the action block" do canary = mock canary.expects(:id).with(123) - canary.expects(:name).with(@valid_channel_cache_data[123]["name"]) + canary.expects(:name).with(@valid_room_cache_data[123]["name"]) bot = a Scamp bot.behaviour do match("a string") { - canary.id(channel_id) - canary.name(channel) + canary.id(room_id) + canary.name(room) } end - bot.channel_cache = @valid_channel_cache_data + bot.room_cache = @valid_room_cache_data bot.send(:process_message, {:room_id => 123, :body => "a string"}) end @@ -364,21 +364,21 @@ it "should provide a command list" do canary = mock - canary.expects(:commands).with([["Hello world", {}], ["Hello other world", {:channel=>123}], [/match me/, {:user=>123}]]) + canary.expects(:commands).with([["Hello world", {}], ["Hello other world", {:room=>123}], [/match me/, {:user=>123}]]) bot = a Scamp bot.behaviour do match("Hello world") { canary.commands(command_list) } - match("Hello other world", :conditions => {:channel => 123}) {} + match("Hello other world", :conditions => {:room => 123}) {} match(/match me/, :conditions => {:user => 123}) {} end bot.send(:process_message, {:body => "Hello world"}) end - it "should be able to play a sound to the channel the action was triggered in" do + it "should be able to play a sound to the room the action was triggered in" do bot = a Scamp bot.behaviour do match("Hello world") { @@ -398,19 +398,19 @@ } end - it "should be able to play a sound to an arbitrary channel" do - play_channel = 456 + it "should be able to play a sound to an arbitrary room" do + play_room = 456 bot = a Scamp bot.behaviour do match("Hello world") { - play "yeah", play_channel + play "yeah", play_room } end EM.run_block { room_id = 123 - stub_request(:post, "https://#{@valid_params[:subdomain]}.campfirenow.com/room/#{play_channel}/speak.json"). + stub_request(:post, "https://#{@valid_params[:subdomain]}.campfirenow.com/room/#{play_room}/speak.json"). with( :body => "{\"message\":{\"body\":\"yeah\",\"type\":\"SoundMessage\"}}", :headers => {'Authorization'=>[@valid_params[:api_key], 'X'], 'Content-Type'=>'application/json'} @@ -420,7 +420,7 @@ } end - it "should be able to say a message to the channel the action was triggered in" do + it "should be able to say a message to the room the action was triggered in" do bot = a Scamp bot.behaviour do match("Hello world") { @@ -440,19 +440,19 @@ } end - it "should be able to say a message to an arbitrary channel" do - play_channel = 456 + it "should be able to say a message to an arbitrary room" do + play_room = 456 bot = a Scamp bot.behaviour do match("Hello world") { - say "yeah", play_channel + say "yeah", play_room } end EM.run_block { room_id = 123 - stub_request(:post, "https://#{@valid_params[:subdomain]}.campfirenow.com/room/#{play_channel}/speak.json"). + stub_request(:post, "https://#{@valid_params[:subdomain]}.campfirenow.com/room/#{play_room}/speak.json"). with( :body => "{\"message\":{\"body\":\"yeah\",\"type\":\"Textmessage\"}}", :headers => {'Authorization'=>[@valid_params[:api_key], 'X'], 'Content-Type'=>'application/json'}