squab-bot 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/LICENSE.md +13 -0
  2. data/README.md +4 -0
  3. data/bin/squab-bot +338 -0
  4. data/bin/squab-bot.yaml +40 -0
  5. metadata +114 -0
@@ -0,0 +1,13 @@
1
+ Copyright 2013 Square Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,4 @@
1
+ Squab IRC Bot
2
+ =====
3
+
4
+ This is the Squab IRC Bot directory. Please see the root level directory for information on squab, using squab, and developing squab.
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/ruby20
2
+ require 'net/http'
3
+ require 'yaml'
4
+ require 'squab-client'
5
+
6
+ # Global bot, but populate the namespace local
7
+ # Needed to work with threads
8
+ require 'isaac/bot'
9
+ $bot = Isaac::Bot.new
10
+ %w(configure helpers on).each do |method|
11
+ eval(<<-EOF)
12
+ def #{method}(*args, &block)
13
+ $bot.#{method}(*args, &block)
14
+ end
15
+ EOF
16
+ end
17
+
18
+ squab_confs = %w(/etc/squab/squab-bot.yaml /etc/squab-bot.yaml)
19
+ local_squab_conf = File.join(File.dirname(__FILE__), 'squab-bot.yaml')
20
+ squab_confs.push(local_squab_conf)
21
+
22
+ $config = nil
23
+ squab_confs.each do |cf|
24
+ begin
25
+ puts "Looking at " + cf
26
+ if File.exists?(cf)
27
+ $config = YAML.load_file(cf)
28
+ break
29
+ end
30
+ rescue
31
+ next
32
+ end
33
+ end
34
+
35
+ if $config.nil?
36
+ puts "No parsable config found"
37
+ exit()
38
+ end
39
+
40
+ def mainloop()
41
+ configure do |c|
42
+ c.nick = $config['irc_nick']
43
+ c.server = $config['irc_server']
44
+ c.port = $config['irc_port'].to_i
45
+ c.password = $config['irc_password']
46
+ c.ssl = $config['irc_ssl']
47
+ c.verbose = $config['irc_verbose']
48
+ end
49
+
50
+ on :connect do
51
+ if not $config['sub_channels'].member?($config['home_channel'])
52
+ $config['sub_channels'][$config['home_channel']] = /.*/
53
+ end
54
+ for irc_chan in $config['sub_channels'].keys
55
+ $config['sub_channels'][irc_chan] = /#{$config['sub_channels'][irc_chan]}/
56
+ join irc_chan
57
+ end
58
+ for irc_chan in $config['join_channels']
59
+ join irc_chan
60
+ end
61
+ end
62
+
63
+ on :channel, /^#{$config['irc_nick']}: help/i do
64
+ msg channel, "You can post things to Squab by typing:"
65
+ msg channel, ".sq <thing to say>"
66
+ msg channel, "Other commands:"
67
+ msg channel, "info -- state what channels and topics are being output"
68
+ msg channel, 'speak [on|off] -- Output messages from squab to the channel'
69
+ msg channel, 'filter <regex> -- Filter the output to this channel based'
70
+ msg channel, ' on a regex. Use .* for everything'
71
+ msg channel, 'post <msg> -- Post a message to squab'
72
+ msg channel, 'search <thing> -- search for a thing in a squab message'
73
+ msg channel, 'join <channel> -- Join a channel'
74
+ msg channel, 'leave -- Leave this channel'
75
+ end
76
+
77
+ on :channel, /^#{$config['irc_nick']}: info/i do
78
+ if $config['sub_channels'].member?(channel)
79
+ msg channel, "Your filter is " + $config['sub_channels'][channel].source
80
+ sc = Squab::Client.new()
81
+ sources = JSON.parse(sc.list_sources)
82
+ source_list = []
83
+ sources.each do |s|
84
+ if $config['sub_channels'][channel].match(s)
85
+ source_list.push(s)
86
+ end
87
+ end
88
+ msg channel, "Based on the filter regex I will output the following sources to this channel:"
89
+ msg channel, source_list.join(', ')
90
+ else
91
+ msg channel, "I don't currently output anything to this channel"
92
+ end
93
+ end
94
+
95
+ on :channel, /^#{$config['irc_nick']}: speak (\S+)/i do
96
+ if match[0] =~ /^on$/i
97
+ if $config['sub_channels'].member?(channel)
98
+ msg channel, "I already speak in this channel."
99
+ else
100
+ $config['sub_channels'][channel] = /.*/
101
+ msg channel, "Ok, I'll start sending all squab output to this channel."
102
+ msg channel, "Use 'filter' if you'd like to limit what I output."
103
+ end
104
+ elsif match[0] =~ /^off$/i
105
+ if channel == $config['home_channel']
106
+ msg channel, "Sorry, I can't stop talking in my home channel"
107
+ else
108
+ $config['sub_channels'].delete(channel)
109
+ msg channel, "Ok, I'll stop talking"
110
+ end
111
+ else
112
+ msg channel, "I accept 'on' and 'off' for this command, you said '#{match[0]}'"
113
+ end
114
+ end
115
+
116
+ on :channel, /^#{$config['irc_nick']}: filter (.*)/i do
117
+ if channel == $config['home_channel']
118
+ msg channel, "#{channel} always outputs all sources"
119
+ else
120
+ filter_re = /#{match[0]}/
121
+ $config['sub_channels'][channel] = filter_re
122
+ msg channel, "Adding filter #{match[0]} to #{channel}"
123
+ end
124
+ end
125
+
126
+ on :channel, /^(\.sq|#{$config['irc_nick']}: post)\s+(.*)$/i do
127
+ sc = Squab::Client.new()
128
+ sc.source = channel
129
+ sc.uid = nick
130
+ try_count = 0
131
+ begin
132
+ sc.send(match[1])
133
+ msg channel, "Posted to Squab"
134
+ rescue SendEventFailed
135
+ try_count += 1
136
+ if try_count > 3
137
+ msg channel, "Couldn't send event right now, please retry later"
138
+ else
139
+ sleep 1
140
+ retry
141
+ end
142
+ end
143
+ end
144
+
145
+ on :channel, /^#{$config['irc_nick']}: search\s+(.*)$/i do
146
+ sc = Squab::Client.new()
147
+ begin
148
+ res = JSON.parse(sc.simple_search(match[1]))
149
+ src = event['source']
150
+ usr = event['uid']
151
+ val = event['value'][0..255]
152
+ url = event['url']
153
+ res.each do |event|
154
+ msg channel, "[#{src}][#{usr}] #{value} #{url}"
155
+ end
156
+ rescue => e
157
+ msg channel, "Search failed: #{e}"
158
+ end
159
+ end
160
+
161
+ on :channel, /^#{$config['irc_nick']}: leave$/i do
162
+ if channel === $config['home_channel']
163
+ msg channel, "I can't leave this channel, it's where I say stuff"
164
+ else
165
+ msg channel, "Bye!"
166
+ part channel
167
+ end
168
+ end
169
+
170
+ on :private, /leave (\S+)/i do
171
+ if match[0] == $config['home_channel']
172
+ msg nick, "I can't leave that channel, that's my home!"
173
+ elsif match[0] =~ /^#/
174
+ msg match[0], "#{nick} asked me to leave, bye!"
175
+ msg nick, "Alright, leaving #{match[0]}"
176
+ part match[0]
177
+ else
178
+ msg channel, "That doesn't seem like a channel name"
179
+ end
180
+ end
181
+
182
+ on :channel, /^#{$config['irc_nick']}: join (\S+)/ do
183
+ if match[0] =~ /^#/
184
+ msg channel, "Alright, heading over to #{match[0]}"
185
+ join match[0]
186
+ else
187
+ msg channel, "That doesn't seem like a channel name"
188
+ end
189
+ end
190
+
191
+ on :private, /^join (\S+)/ do
192
+ if match[0] =~ /^#/
193
+ msg nick, "Alright, heading over to #{match[0]}"
194
+ join match[0]
195
+ else
196
+ msg nick, "That doesn't seem like a channel name"
197
+ end
198
+ end
199
+
200
+ on :channel, /^#{$config['irc_nick']}:/ do
201
+ msg channel, "Hey, #{nick}, I'm a bot. You can ask me for 'help'"
202
+ end
203
+
204
+ # This will repeat everything said in the channel so you can see what
205
+ # squab-bot is actually matching on. Be careful with this, it will ruin
206
+ # real channels, so use a test channel
207
+ on :channel, /^(.*)$/ do
208
+ if $config['debug']
209
+ msg channel, "DEBUG: " + match[0]
210
+ end
211
+ end
212
+
213
+ on :private do
214
+ msg nick, "Hi! I'm a bot! I live in #{$config['home_channel']}"
215
+ msg nick, "You can go there and ask me for 'help'"
216
+ msg nick, "I can also join another channel if you say 'join <channel>'"
217
+ msg nick, "I can also leave a channel if you say 'leave <channel>'"
218
+ end
219
+
220
+ on :extern_message do
221
+ msg channel, @message
222
+ end
223
+
224
+ $bot.start
225
+
226
+ end
227
+
228
+ # This is a parallel thread that calls to squab and gets recent messages
229
+ # to play back into channels. The home channel recieves everything and
230
+ # subscribed channels may apply filters for what sources are output there
231
+ def secondloop()
232
+ puts "Starting Squab Reader Loop"
233
+
234
+ sub_channels = $config['sub_channels']
235
+ message_file = $config['message_file']
236
+ api_url = $config['api_url']
237
+ strict_ssl = $config.fetch('strict_ssl', true)
238
+ squab_client = Squab::Client.new(:api => api_url)
239
+ latest_msg = 0
240
+ messages = []
241
+ if File.exists?(message_file)
242
+ File.open(message_file, 'r').each_line do |line|
243
+ latest_msg = line.strip.to_i
244
+ end
245
+ end
246
+
247
+ # Wait for the other thread to join irc
248
+ sleep 30
249
+ loop do
250
+ puts "Polling Squab..."
251
+ begin
252
+ if latest_msg == 0
253
+ # Replay the default number of messages if we don't have a latest
254
+ messages = squab_client.get
255
+ else
256
+ messages = squab_client.get_from_event(latest_msg)
257
+ end
258
+ messages = JSON.parse(messages)
259
+ rescue OpenSSL::SSL::SSLError => e
260
+ puts "SSL Error: #{e}"
261
+ if not strict_ssl
262
+ # Disable ssl if we're not being strict about it
263
+ puts "Disabling strict ssl verification"
264
+ squab_client.ssl_verify = false
265
+ else
266
+ sleep 10
267
+ end
268
+ retry
269
+ rescue => e
270
+ puts "Couldn't get messages from squab: #{e}"
271
+ sleep 10
272
+ retry
273
+ end
274
+
275
+ messages.reverse.each do |message|
276
+ if message.has_key?('id')
277
+ id = message['id']
278
+ if id.to_i >= latest_msg
279
+ latest_msg = id.to_i + 1
280
+ end
281
+ value = message['value']
282
+ source = message['source']
283
+ uid = message['uid']
284
+ url = message['url']
285
+
286
+ # Limit to Tweet sized
287
+ # IRC message size is usually 510 bytes, but you don't want
288
+ # giant messages in your channels. Add a url if you need more
289
+ # details
290
+ if value.length > 140
291
+ value = value[0,140] + ' [message too long]'
292
+ end
293
+
294
+ irc_m = nil
295
+
296
+ # Only print source if it's the same case-insensitive string as uid
297
+ id_tag = if uid =~ /^#{Regexp.escape(source)}$/i
298
+ "[#{source}]"
299
+ else
300
+ "[#{uid}][#{source}]"
301
+ end
302
+
303
+ irc_m = if url.nil?
304
+ "#{id_tag} #{value}"
305
+ else
306
+ "#{id_tag} #{value} (#{url})"
307
+ end
308
+
309
+ begin
310
+ sub_channels.each do |ch, filter|
311
+ if source =~ filter
312
+ m = Isaac::Message.new(":squab PRIVMSG " + ch + " :" + irc_m)
313
+ $bot.dispatch(:extern_message, m)
314
+ puts "Posting to #{ch}: #{message}"
315
+ end
316
+ end
317
+ # Don't flood the channel
318
+ sleep 1
319
+ rescue
320
+ puts "Can't send to bot"
321
+ sleep 10
322
+ retry
323
+ end
324
+ end
325
+ end
326
+ File.open('/tmp/squab.msg', 'w') {|fh| fh.write(latest_msg)}
327
+ sleep 10
328
+ end
329
+ end
330
+
331
+
332
+ t1 = Thread.new{mainloop()}
333
+ t2 = Thread.new{secondloop()}
334
+
335
+ t1.join
336
+ t2.join
337
+
338
+ puts "ok its done"
@@ -0,0 +1,40 @@
1
+ ---
2
+ # Subchannels will be joined, with bot notifications on. The filters will
3
+ # be applied as regex matches to the sources
4
+ sub_channels:
5
+ "#subchannel":
6
+ - "this|that"
7
+ "#subchannel2":
8
+ - "onlythis"
9
+ "#subchannel2":
10
+ - "com(pli|cat)ed.*regex"
11
+ # The squab bot will join these channels, but not speak
12
+ # This allows channels to post, but not have bot chatter in them
13
+ join_channels:
14
+ - "#posthere"
15
+ - "#orhere"
16
+ # The home channel is where the bot always lives. The bot will not leave
17
+ # this channel and always outputs all messages to here regardless of source
18
+ home_channel: "#squab"
19
+ # Where to save the most recently seen messages. If this file is missing
20
+ # the bot replays the last 5 messages (if the filter applies) and records
21
+ # the latest ID
22
+ message_file: /tmp/squab.msg
23
+ # This should be the address of your squab web interface
24
+ api_url: http://squab.example.com:8082/
25
+ # If your IRC server has a password, put it here
26
+ irc_password: secret_password
27
+ # What the bot should call itself. It makes sense to call it squab, but
28
+ # if you hate that, don't!
29
+ irc_nick: squab
30
+ # If your IRC server is protected by SSL, use this
31
+ irc_ssl: "true"
32
+ # The port to your irc server
33
+ irc_port: "6697"
34
+ # The name of your IRC server
35
+ irc_server: irc.example.com
36
+ # If you should output all the IRC stuff that the bot sees to the console
37
+ irc_verbose: "true"
38
+ # If you want debug on... the bot will output everything it sees in a channel
39
+ # you probably only want this for development purposes
40
+ debug: "false"
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: squab-bot
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 3
9
+ - 2
10
+ version: 1.3.2
11
+ platform: ruby
12
+ authors:
13
+ - Grier Johnson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2013-12-02 00:00:00 -05:00
19
+ default_executable: squab-bot
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: isaac
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: squab-client
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rangeclient
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ description: An IRC bot that allows reading and posting to Squab
64
+ email:
65
+ - github@squareup.com
66
+ executables:
67
+ - squab-bot
68
+ extensions: []
69
+
70
+ extra_rdoc_files:
71
+ - LICENSE.md
72
+ files:
73
+ - bin/squab-bot
74
+ - bin/squab-bot.yaml
75
+ - README.md
76
+ - LICENSE.md
77
+ has_rdoc: true
78
+ homepage: http://github.com/square/squab
79
+ licenses:
80
+ - Apache 2.0
81
+ post_install_message:
82
+ rdoc_options:
83
+ - --charset=UTF-8
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 23
101
+ segments:
102
+ - 1
103
+ - 3
104
+ - 6
105
+ version: 1.3.6
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.6.2
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: An IRC interface to Squab
113
+ test_files: []
114
+