squab-bot 1.3.2

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