scamp 1.2.0 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5a8f622dff6a60fddd4050cde82ebe3708197d52
4
- data.tar.gz: b5f6e9c1d1f0ecd3278b82cc9bea44bb69d1c045
3
+ metadata.gz: 68ec4dbee8f688bce6bc1b63ea69e53a4a343c0d
4
+ data.tar.gz: 46806e4a3ac13a4c961e09f32cef1354830185c7
5
5
  SHA512:
6
- metadata.gz: ef395384dcb7efb1e37398a4fe0e480001688b2ccd74c2f654ac458a1dc06ff8feaf173e49b181458e8893c6c2e62bd145a3faa06b188a4e7a1cce41b3f90ac0
7
- data.tar.gz: 299820bac823a24117d2d7ce4fb72494783e5e78c07358ab7e51a67062500ffdf3d546bf04f64b27704fccbe9e2d2bbfbf437beb38d393ed357ed8a71cb09cfa
6
+ metadata.gz: f8b6808337113e7ee703ee49536a297f053cb355a1200fb875f348335465f398dcc7f1ff7484b936b748695052bb7e101e9fd6ab54126635247c6f287caeac4c
7
+ data.tar.gz: 7e27db164920d26c8a75404b1c8c710e3addd3810022b06f847e1759cf4e047a298ab6cd69c0b6ad8525c32568f78e3818cfeca7bf60819fb36825db90bf165a
data/.gitignore CHANGED
@@ -1,3 +1,8 @@
1
1
  .rvmrc
2
2
  Gemfile.lock
3
3
  pkg/
4
+ coverage/
5
+ .DS_Store
6
+ bin
7
+ .rbx
8
+ .bundle
data/README.md CHANGED
@@ -10,11 +10,11 @@ If you like or use Scamp I'd love to hear from you. Drop me at line at will at 3
10
10
 
11
11
  ## Requirements
12
12
 
13
- Ruby >= 1.9.2 (At least for the named captures)
13
+ Ruby >= 1.9.2
14
14
 
15
15
  ## Installation
16
16
 
17
- `gem install scamp` or put `gem 'scamp'` in your Gemfile.
17
+ `gem install scamp` or `gem 'scamp'` in your Gemfile.
18
18
 
19
19
  ## Usage and Examples
20
20
 
@@ -22,30 +22,34 @@ Ruby >= 1.9.2 (At least for the named captures)
22
22
 
23
23
  ``` ruby
24
24
  require 'scamp'
25
+ require 'scamp-campfire-adapter'
25
26
 
26
- scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :verbose => true)
27
+ Scamp.new do |scamp|
28
+ scamp.adapter :campfire, Scamp::Campfire::Adapter, api_key: "YOUR API KEY",
29
+ subdomain: "yoursubdomain",
30
+ rooms: [293788]
27
31
 
28
- scamp.behaviour do
29
32
  # Simple matching based on regex or string:
30
- match "ping" do
31
- say "pong"
33
+ scamp.match "ping" do |room, msg|
34
+ room.say "pong"
32
35
  end
33
36
  end
34
-
35
- # Connect and join some rooms
36
- scamp.connect!([293788, "Monitoring"])
37
37
  ```
38
38
 
39
39
  ### Everyone wants an image search
40
40
 
41
41
  ``` ruby
42
42
  require 'scamp'
43
+ require 'scamp-campfire-adapter'
44
+ require 'em-http-request'
43
45
  require 'cgi'
44
46
 
45
- scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :verbose => true)
47
+ Scamp.new do |scamp|
48
+ scamp.adapter :campfire, Scamp::Campfire::Adapter, api_key: "YOUR API KEY",
49
+ subdomain: "yoursubdomain",
50
+ rooms: [293788]
46
51
 
47
- scamp.behaviour do
48
- match /^artme (?<search>\w+)/ do
52
+ scamp.match /^artme (?<search>\w+)/ do |room, msg|
49
53
  url = "http://ajax.googleapis.com/ajax/services/search/images?rsz=large&start=0&v=1.0&q=#{CGI.escape(search)}"
50
54
  http = EventMachine::HttpRequest.new(url).get
51
55
  http.errback { say "Couldn't get #{url}: #{http.response_status.inspect}" }
@@ -53,20 +57,16 @@ scamp.behaviour do
53
57
  if http.response_header.status == 200
54
58
  results = Yajl::Parser.parse(http.response)
55
59
  if results['responseData']['results'].size > 0
56
- say results['responseData']['results'][0]['url']
60
+ room.say results['responseData']['results'][0]['url']
57
61
  else
58
- say "No images matched #{search}"
62
+ room.say "No images matched #{search}"
59
63
  end
60
64
  else
61
- # logger.warn "Couldn't get #{url}"
62
- say "Couldn't get #{url}"
65
+ room.say "Couldn't get #{url}"
63
66
  end
64
67
  }
65
68
  end
66
69
  end
67
-
68
- # Connect and join some rooms
69
- scamp.connect!([293788, "Monitoring"])
70
70
  ```
71
71
 
72
72
  ### A more in-depth run through
@@ -77,139 +77,53 @@ Matchers are tested in order and all that satisfy the match and conditions will
77
77
  require 'scamp'
78
78
 
79
79
  # Add :verbose => true to get debug output, otherwise the logger will output INFO
80
- scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :verbose => true)
80
+ Scamp.new do |scamp|
81
+ scamp.adapter :campfire, Scamp::Campfire::Adapter, api_key: "YOUR API KEY",
82
+ subdomain: "yoursubdomain",
83
+ rooms: [293788],
84
+ verbose: true
81
85
 
82
- scamp.behaviour do
83
86
  #
84
87
  # Simple matching based on regex or string:
85
88
  #
86
- match /^repeat (\w+), (\w+)$/ do
87
- say "You said #{matches[0]} and #{matches[1]}"
88
- end
89
-
90
- #
91
- # You can specifically paste text:
92
- #
93
-
94
- match "paste stuff" do
95
- paste "Awesome texts"
96
-
97
- # say()'ing multiline strings will paste automatically however:
98
- say <<-EOS
99
- This will be pasted
100
- even though you called say
101
- EOS
102
- end
103
-
104
- #
105
- # A special user and room method is available in match blocks.
106
- #
107
- match "a user said" do
108
- say "#{user} said something in room #{room}"
109
- end
110
-
111
- match "Hello!" do
112
- say "Hi there"
113
- end
114
-
115
- #
116
- # You can play awesome sounds
117
- #
118
- match "ohmy" do
119
- play "yeah"
120
- end
121
-
122
- #
123
- # Limit the match to certain rooms, users or both.
124
- #
125
- match /^Lets match (.+)$/, :conditions => {:room => "Some Room"} do
126
- say "Only said if room name matches 'Some Room'"
127
- end
128
-
129
- match "some text", :conditions => {:user => "Some User"} do
130
- say "Only said if user name matches 'Some User'"
131
- end
132
-
133
- match /some other text/, :conditions => {:user => "Some User", :room => 123456} do
134
- say "You can mix conditions"
135
- end
136
-
137
- match "some text", :conditions => {:room => ["Some Room", "Some Other Room"]} do
138
- say "You can list multiple rooms"
89
+ scamp.match /^repeat (\w+), (\w+)$/ do |room, msg|
90
+ room.say "You said #{matches[0]} and #{matches[1]}"
139
91
  end
140
92
 
141
93
  #
142
94
  # Named captures become available in your match block
143
95
  #
144
- match /^say (?<yousaid>.+)$/ do
145
- say "You said #{yousaid}"
146
- end
147
-
148
- #
149
- # You can say multiple times, and you can specify an alternate room.
150
- # Default behaviour is to 'say' in the room that caused the match.
151
- #
152
- match "something" do
153
- say "#{user} said something in room #{room}"
154
- say "#{user} said something in room #{room}", 237872
155
- say "#{user} said something in room #{room}", "System Administration"
156
- end
157
-
158
- #
159
- # A list of commands is available as command_list this matcher uses it
160
- # to format a help text
161
- #
162
- match "help" do
163
- max_command_length = command_list.map{|cl| cl.first.to_s }.max_by(&:size).size
164
- format_string = "%#{max_command_length + 1}s"
165
- formatted_commands = command_list.map{|action, conds| "#{sprintf(format_string, action)} | #{conds.size == 0 ? '' : conds.inspect}"}
166
- say <<-EOS
167
- #{sprintf("%-#{max_command_length + 1}s", "Command match")} | Conditions
168
- --------------------------------------------------------------------------------
169
- #{formatted_commands.join("\n")}
170
- EOS
96
+ scamp.match /^say (?<yousaid>.+)$/ do |room, msg|
97
+ room.say "You said #{msg.matches.yousaid}"
171
98
  end
172
99
  end
173
100
 
174
- # Connect and join some rooms
175
- scamp.connect!([293788, "Monitoring"])
176
101
  ```
177
102
 
178
- In the room/user conditions and say/play commands you can use the name or ID of a user or room, eg:
103
+ Scamp will also run _all_ match blocks that an input string matches, you can make Scamp only run the first block it matches by passing in :first\_match\_only => true:
179
104
 
180
105
  ``` ruby
181
- :conditions => {:room => "some string"}
182
- :conditions => {:room => 123456}
183
-
184
- :conditions => {:user => "some string"}
185
- :conditions => {:user => 123456}
186
-
187
- say "#{user} said something in room #{room}", 237872
188
- say "#{user} said something in room #{room}", "System Administration"
106
+ Scamp.new :first_match_only => true do |scamp|
107
+ end
189
108
  ```
190
109
 
191
- By default Scamp listens to itself. This could either be fun, or dangerous, you decide. You can turn this off by passing :ignore\_self => true in the initialisation options:
110
+ ## Adapters
192
111
 
193
- ``` ruby
194
- scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :ignore_self => true)
195
- ```
112
+ ### Adapter channels
196
113
 
197
- Scamp will also run _all_ match blocks that an input string matches, you can make Scamp only run the first block it matches by passing in :first\_match\_only => true:
114
+ ### Writing an adapter
198
115
 
199
- ``` ruby
200
- scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :first_match_only => true)
201
- ```
202
116
 
203
- 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:
117
+ ## Plugins
204
118
 
205
- ``` ruby
206
- scamp = Scamp.new(:api_key => "YOUR API KEY", :subdomain => "yoursubdomain", :required_prefix => 'Bot: ')
207
- ```
119
+ ### Writing plugins
120
+
121
+ * TODO
208
122
 
209
- Scamp will now require commands to begin with 'Bot: ' (or whatever you have specified), and will strip out this prefix before handing the message onto your match block.
210
123
 
211
124
  ## TODO
212
125
 
126
+ * Get messages working back to adapters
213
127
  * Allow multiple values for conditions, eg: :conditions => {:user => ["Some User", "Some Other User"]}
214
128
 
215
129
  ## How to contribute
@@ -230,7 +144,8 @@ Take a look at the TODO list or known issues for some inspiration if you need it
230
144
 
231
145
  ## Authors
232
146
 
233
- * Will Jessop (will@willj.net)
147
+ * [Will Jessop](http://willj.net/)
148
+ * [Adam Holt](http://adamholt.co.uk/)
234
149
 
235
150
  ## Thanks
236
151
 
@@ -238,8 +153,6 @@ First class support, commits and pull requests, thanks guys!
238
153
 
239
154
  * [Caius Durling](http://caius.name/)
240
155
  * Sudara Williams of [Ramen Music](http://ramenmusic.com)
241
- * [Dom Hodgson](http://www.thehodge.co.uk/) (for the name)
242
- * Pull requests: @mheffner
243
156
 
244
157
  ## License
245
158
 
data/examples/v2.rb ADDED
@@ -0,0 +1,39 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '../lib')
2
+
3
+ require 'scamp'
4
+ require 'scamp/adapter'
5
+ require 'scamp/message'
6
+ require 'scamp/plugin'
7
+
8
+ class PingPlugin < Scamp::Plugin
9
+ match /^ping/, :say_pong
10
+
11
+ def say_pong channel, msg
12
+ channel.say "pong"
13
+ end
14
+ end
15
+
16
+ class TestAdapter < Scamp::Adapter
17
+ class Context
18
+ def say msg
19
+ puts msg
20
+ end
21
+ end
22
+
23
+ def connect!
24
+ EventMachine::PeriodicTimer.new(@opts[:delay]) do
25
+ msg = Scamp::Message.new(self, :body => "ping")
26
+ context = TestAdapter::Context.new
27
+ push [context, msg]
28
+ end
29
+ end
30
+ end
31
+
32
+ Scamp.new do |bot|
33
+ bot.adapter :test, TestAdapter, :delay => 1
34
+ bot.adapter :another, TestAdapter, :delay => 5
35
+
36
+ bot.plugin PingPlugin, :on => [:test]
37
+
38
+ bot.connect!
39
+ end
@@ -0,0 +1,47 @@
1
+ class Scamp
2
+ class Adapter
3
+ attr_accessor :bot
4
+
5
+ def initialize(bot, opts={})
6
+ @bot = bot
7
+ @opts = opts
8
+ end
9
+
10
+ def matches_required_format?(msg)
11
+ return true unless msg
12
+ return true unless bot.required_format
13
+ if bot.required_format.is_a? String
14
+ msg.index(bot.required_format) == 0
15
+ elsif bot.required_format.is_a? Regexp
16
+ msg.match bot.required_format
17
+ else
18
+ raise ArgumentError, "You passed a :required_format that isn't a string or regexp, dont't know how to match it!"
19
+ end
20
+ end
21
+
22
+ def strip_prefix(msg)
23
+ # We only strip required prefxes if they are strings, and strip_prefix is set
24
+ return msg unless bot.required_format.is_a?(String) && bot.strip_prefix
25
+ msg.sub(bot.required_format, '').strip unless msg.nil?
26
+ end
27
+
28
+ def subscribe &block
29
+ channel.subscribe &block
30
+ end
31
+
32
+ def push(msg)
33
+ channel.push msg
34
+ end
35
+ alias_method :<<, :push
36
+
37
+ def connect!
38
+ raise NotImplementedError, "connect! must be implemented"
39
+ end
40
+
41
+ private
42
+
43
+ def channel
44
+ @channel ||= EM::Channel.new
45
+ end
46
+ end
47
+ end
data/lib/scamp/matcher.rb CHANGED
@@ -1,106 +1,30 @@
1
1
  class Scamp
2
2
  class Matcher
3
- attr_accessor :conditions, :trigger, :action, :bot, :required_prefix
4
-
3
+ attr_accessor :on, :conditions, :trigger, :action, :bot, :required_prefix
4
+
5
5
  def initialize(bot, params = {})
6
- params ||= {}
7
6
  params[:conditions] ||= {}
7
+ params[:on] ||= bot.adapters.keys
8
+ raise ArgumentError, "matcher must have a trigger" unless params[:trigger]
9
+ raise ArgumentError, "matcher must have a action" unless params[:action]
8
10
  params.each { |k,v| send("#{k}=", v) }
9
11
  @bot = bot
10
12
  end
11
-
12
- def attempt(msg)
13
- return false unless conditions_satisfied_by(msg)
14
- match = triggered_by(msg[:body])
15
- if match
16
- if match.is_a? MatchData
17
- run(msg, match)
18
- else
19
- run(msg)
20
- end
13
+
14
+ def attempt(channel, context, msg)
15
+ if listening?(channel) && msg.matches?(trigger) && msg.valid?(conditions)
16
+ run(context, msg)
21
17
  return true
22
18
  end
23
- false
24
- end
25
-
26
- private
27
-
28
- def triggered_by(message_text)
29
- if message_text && required_prefix
30
- message_text = handle_prefix(message_text)
31
- return false unless message_text
32
- end
33
- if trigger.is_a? String
34
- return true if trigger == message_text
35
- elsif trigger.is_a? Regexp
36
- return trigger.match message_text
37
- else
38
- bot.logger.warn "Don't know what to do with #{trigger.inspect} at #{__FILE__}:#{__LINE__}"
39
- end
40
- false
19
+ return false
41
20
  end
42
-
43
- def handle_prefix(message_text)
44
- return false unless message_text
45
- if required_prefix.is_a? String
46
- if required_prefix == message_text[0...required_prefix.length]
47
- message_text.gsub(required_prefix,'')
48
- else
49
- false
50
- end
51
- elsif required_prefix.is_a? Regexp
52
- if required_prefix.match message_text
53
- message_text.gsub(required_prefix,'')
54
- else
55
- false
56
- end
57
- else
58
- false
59
- end
60
- end
61
-
62
- def run(msg, match = nil)
63
- action_run = Action.new(bot, action, msg)
64
- action_run.matches = match if match
65
- action_run.run
21
+
22
+ def run(context, msg)
23
+ action.call(context, msg)
66
24
  end
67
-
68
- def conditions_satisfied_by(msg)
69
- bot.logger.debug "Checking message against #{conditions.inspect}"
70
25
 
71
- # item will be :user or :room
72
- # cond is the int or string value.
73
- conditions.each do |item, cond|
74
- bot.logger.debug "Checking #{item} against #{cond}"
75
- bot.logger.debug "msg is #{msg.inspect}"
76
- if cond.is_a? Integer
77
- # bot.logger.debug "item is #{msg[{:room => :room_id, :user => :user_id}[item]]}"
78
- return false unless msg[{:room => :room_id, :user => :user_id}[item]] == cond
79
- elsif cond.is_a? String
80
- case item
81
- when :room
82
- return false unless bot.room_name_for(msg[:room_id]) == cond
83
- when :user
84
- return false unless bot.username_for(msg[:user_id]) == cond
85
- end
86
- bot.logger.error "Don't know how to deal with a match item of #{item}, cond #{cond}"
87
- elsif cond.is_a? Array
88
- case item
89
- when :room, :rooms
90
- return cond.select {|e| e.is_a? Integer }.include?(msg[{:room => :room_id}[item]]) ||
91
- cond.select {|e| e.is_a? String }.include?(bot.room_name_for(msg[:room_id]))
92
- end
93
- bot.logger.error "Don't know how to deal with a match item of #{item}, cond #{cond}"
94
- elsif cond.is_a? Symbol
95
- case item
96
- when :type
97
- return (cond == :text && msg[:type] == "TextMessage") ||
98
- (cond == :paste && msg[:type] == "PasteMessage")
99
- end
100
- bot.logger.error "Don't know how to deal with a match item of #{item}, cond #{cond}"
101
- end
102
- end
103
- true
26
+ def listening?(channel)
27
+ on.include?(channel)
104
28
  end
105
29
  end
106
30
  end
@@ -0,0 +1,22 @@
1
+ class Scamp
2
+ class Matches
3
+ include Enumerable
4
+
5
+ def initialize matches
6
+ @matches = matches
7
+ @matches.names.each do |name|
8
+ self.define_singleton_method name.to_sym do
9
+ matches[name.to_sym]
10
+ end
11
+ end
12
+ end
13
+
14
+ def [] index
15
+ @matches[1..-1][index]
16
+ end
17
+
18
+ def each
19
+ @matches[1..-1].each {|match| yield match }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ require 'scamp/matches'
2
+
3
+ class Scamp
4
+ class Message
5
+ attr_reader :adapter, :match
6
+
7
+ def initialize(adapter, args={})
8
+ @adapter = adapter
9
+ args.each do |arg,value|
10
+ self.define_singleton_method arg do
11
+ value
12
+ end
13
+ end
14
+ end
15
+
16
+ def valid? conditions
17
+ true
18
+ end
19
+
20
+ def matches? trigger
21
+ match? trigger, body
22
+ end
23
+
24
+ def matches
25
+ Scamp::Matches.new(match) if match
26
+ end
27
+
28
+ protected
29
+
30
+ def match?(trigger, message)
31
+ if trigger.is_a? String
32
+ return true if trigger == message
33
+ elsif trigger.is_a? Regexp
34
+ return true if (@match = trigger.match message)
35
+ end
36
+ return false
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ class Scamp
2
+ class Plugin
3
+ def self.matchers
4
+ @matchers ||= []
5
+ end
6
+
7
+ def self.match trigger, method
8
+ self.matchers << [trigger, method]
9
+ end
10
+
11
+ attr_reader :bot, :options
12
+
13
+ def initialize bot, opts={}
14
+ @bot = bot
15
+ @options = opts
16
+
17
+ attach_matchers
18
+ end
19
+
20
+ private
21
+ def matcher_options
22
+ {
23
+ :on => options[:on]
24
+ }
25
+ end
26
+
27
+ def attach_matchers
28
+ self.class.matchers.each do |trigger, method_name|
29
+ @bot.match trigger, matcher_options, &Proc.new(&method(method_name))
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/scamp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Scamp
2
- VERSION = "1.2.0"
2
+ VERSION = "2.0.0.pre"
3
3
  end