sclemmer-robut 0.5.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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.travis.yml +11 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +65 -0
- data/README.rdoc +199 -0
- data/Rakefile +27 -0
- data/bin/robut +10 -0
- data/examples/Chatfile +31 -0
- data/examples/config.ru +13 -0
- data/lib/rexml_patches.rb +26 -0
- data/lib/robut/connection.rb +124 -0
- data/lib/robut/plugin/alias.rb +109 -0
- data/lib/robut/plugin/calc.rb +26 -0
- data/lib/robut/plugin/echo.rb +9 -0
- data/lib/robut/plugin/google_images.rb +17 -0
- data/lib/robut/plugin/help.rb +16 -0
- data/lib/robut/plugin/later.rb +81 -0
- data/lib/robut/plugin/lunch.rb +76 -0
- data/lib/robut/plugin/meme.rb +32 -0
- data/lib/robut/plugin/pick.rb +18 -0
- data/lib/robut/plugin/ping.rb +9 -0
- data/lib/robut/plugin/quips.rb +60 -0
- data/lib/robut/plugin/say.rb +23 -0
- data/lib/robut/plugin/sayings.rb +37 -0
- data/lib/robut/plugin/stock.rb +45 -0
- data/lib/robut/plugin/twss.rb +19 -0
- data/lib/robut/plugin/weather.rb +126 -0
- data/lib/robut/plugin.rb +201 -0
- data/lib/robut/pm.rb +40 -0
- data/lib/robut/presence.rb +39 -0
- data/lib/robut/room.rb +30 -0
- data/lib/robut/storage/base.rb +21 -0
- data/lib/robut/storage/hash_store.rb +26 -0
- data/lib/robut/storage/yaml_store.rb +57 -0
- data/lib/robut/storage.rb +3 -0
- data/lib/robut/version.rb +4 -0
- data/lib/robut/web.rb +26 -0
- data/lib/robut.rb +9 -0
- data/sclemmer-robut.gemspec +24 -0
- data/test/fixtures/bad_location.xml +1 -0
- data/test/fixtures/las_vegas.xml +1 -0
- data/test/fixtures/seattle.xml +1 -0
- data/test/fixtures/tacoma.xml +1 -0
- data/test/mocks/connection_mock.rb +22 -0
- data/test/mocks/presence_mock.rb +25 -0
- data/test/simplecov_helper.rb +2 -0
- data/test/test_helper.rb +9 -0
- data/test/unit/connection_test.rb +161 -0
- data/test/unit/plugin/alias_test.rb +76 -0
- data/test/unit/plugin/echo_test.rb +27 -0
- data/test/unit/plugin/help_test.rb +46 -0
- data/test/unit/plugin/later_test.rb +40 -0
- data/test/unit/plugin/lunch_test.rb +36 -0
- data/test/unit/plugin/pick_test.rb +35 -0
- data/test/unit/plugin/ping_test.rb +22 -0
- data/test/unit/plugin/quips_test.rb +58 -0
- data/test/unit/plugin/say_test.rb +34 -0
- data/test/unit/plugin/weather_test.rb +101 -0
- data/test/unit/plugin_test.rb +91 -0
- data/test/unit/room_test.rb +51 -0
- data/test/unit/storage/hash_store_test.rb +15 -0
- data/test/unit/storage/yaml_store_test.rb +47 -0
- data/test/unit/storage/yaml_test.yml +1 -0
- data/test/unit/web_test.rb +46 -0
- metadata +162 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 76b46a0e0add81cb61e96c4325b483eb30b9aa26
|
|
4
|
+
data.tar.gz: c1e8ab8df7ed607a16ecbd71492fbd621b5e9d51
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0dec9cb846b2a9c9d52a559012de494719fa403dcbfa3aca3e0519d229e099325b3698f9ae4b39665b2b8391bf1e6a4e060c5e04db0bc60f585ac0e5026c4764
|
|
7
|
+
data.tar.gz: 071bf604bc1b899acbc8c9b2c25a6e63525cb6fd47526da1681a2b8b0fe68a392089515ff85df31eb729299fc981ede1a7361a2e0ad84e9db34a02c8e37bea8c
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
language: ruby
|
|
2
|
+
rvm:
|
|
3
|
+
# - 1.8.7
|
|
4
|
+
- 1.9.3
|
|
5
|
+
- 2.0
|
|
6
|
+
# - jruby-18mode # JRuby in 1.8 mode
|
|
7
|
+
# - jruby-19mode # JRuby in 1.9 mode
|
|
8
|
+
# - rbx-18mode
|
|
9
|
+
# - rbx-19mode # currently in active development, may or may not work for your project
|
|
10
|
+
# uncomment this line if your project needs to run something other than `rake`:
|
|
11
|
+
# script: bundle exec rspec spec
|
data/Gemfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gemspec
|
|
4
|
+
|
|
5
|
+
gem 'rake'
|
|
6
|
+
|
|
7
|
+
group :test do
|
|
8
|
+
gem 'simplecov'
|
|
9
|
+
gem 'webmock'
|
|
10
|
+
gem 'time-warp'
|
|
11
|
+
gem 'mocha'
|
|
12
|
+
gem 'rack-test'
|
|
13
|
+
gem 'minitest'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
group :plugin do
|
|
17
|
+
gem 'calc'
|
|
18
|
+
gem 'twss-classifier'
|
|
19
|
+
gem 'meme_generator'
|
|
20
|
+
gem 'nokogiri'
|
|
21
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
sclemmer-robut (0.5.2)
|
|
5
|
+
sinatra (~> 1.3)
|
|
6
|
+
xmpp4r (~> 0.5.0)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
addressable (2.3.5)
|
|
12
|
+
calc (1.0.0)
|
|
13
|
+
crack (0.4.1)
|
|
14
|
+
safe_yaml (~> 0.9.0)
|
|
15
|
+
docile (1.1.1)
|
|
16
|
+
meme_generator (1.9)
|
|
17
|
+
nokogiri (~> 1.4)
|
|
18
|
+
metaclass (0.0.1)
|
|
19
|
+
mini_portile (0.5.2)
|
|
20
|
+
minitest (5.5.0)
|
|
21
|
+
mocha (0.14.0)
|
|
22
|
+
metaclass (~> 0.0.1)
|
|
23
|
+
multi_json (1.8.2)
|
|
24
|
+
nokogiri (1.6.1)
|
|
25
|
+
mini_portile (~> 0.5.0)
|
|
26
|
+
rack (1.5.2)
|
|
27
|
+
rack-protection (1.5.3)
|
|
28
|
+
rack
|
|
29
|
+
rack-test (0.6.2)
|
|
30
|
+
rack (>= 1.0)
|
|
31
|
+
rake (10.1.1)
|
|
32
|
+
safe_yaml (0.9.7)
|
|
33
|
+
simplecov (0.8.2)
|
|
34
|
+
docile (~> 1.1.0)
|
|
35
|
+
multi_json
|
|
36
|
+
simplecov-html (~> 0.8.0)
|
|
37
|
+
simplecov-html (0.8.0)
|
|
38
|
+
sinatra (1.4.5)
|
|
39
|
+
rack (~> 1.4)
|
|
40
|
+
rack-protection (~> 1.4)
|
|
41
|
+
tilt (~> 1.3, >= 1.3.4)
|
|
42
|
+
tilt (1.4.1)
|
|
43
|
+
time-warp (1.0.13)
|
|
44
|
+
twss-classifier (0.0.1)
|
|
45
|
+
webmock (1.16.1)
|
|
46
|
+
addressable (>= 2.2.7)
|
|
47
|
+
crack (>= 0.3.2)
|
|
48
|
+
xmpp4r (0.5.6)
|
|
49
|
+
|
|
50
|
+
PLATFORMS
|
|
51
|
+
ruby
|
|
52
|
+
|
|
53
|
+
DEPENDENCIES
|
|
54
|
+
calc
|
|
55
|
+
meme_generator
|
|
56
|
+
minitest
|
|
57
|
+
mocha
|
|
58
|
+
nokogiri
|
|
59
|
+
rack-test
|
|
60
|
+
rake
|
|
61
|
+
sclemmer-robut!
|
|
62
|
+
simplecov
|
|
63
|
+
time-warp
|
|
64
|
+
twss-classifier
|
|
65
|
+
webmock
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
= Robut
|
|
2
|
+
|
|
3
|
+
The friendly plugin-enabled HipChat bot.
|
|
4
|
+
|
|
5
|
+
== Installation and usage
|
|
6
|
+
|
|
7
|
+
Robut can be installed by running <tt>gem install robut</tt>. This
|
|
8
|
+
installs the +robut+ binary. When run, +robut+ reads a Chatfile,
|
|
9
|
+
connects to the specified HipChat server and chatroom, and feeds every
|
|
10
|
+
line said in the chatroom through the plugins configured by the
|
|
11
|
+
Chatfile.
|
|
12
|
+
|
|
13
|
+
Once robut is running, the plugins listen to what's being said in the
|
|
14
|
+
chatroom. Most plugins listen for @replies to robut:
|
|
15
|
+
|
|
16
|
+
@robut lunch? # => "Banh Mi!"
|
|
17
|
+
@robut calc 1 + 1 # => 2
|
|
18
|
+
|
|
19
|
+
Others listen to everything, and don't require an @reply.
|
|
20
|
+
|
|
21
|
+
Some of the included plugins require extra gems to be installed:
|
|
22
|
+
|
|
23
|
+
[Robut::Plugin::TWSS] requires the <tt>twss</tt> gem.
|
|
24
|
+
[Robut::Plugin::Calc] requires the <tt>calc</tt> gem.
|
|
25
|
+
[Robut::Plugin::Weather] requires the <tt>nokogiri</tt> gem.
|
|
26
|
+
[Robut::Plugin::GoogleImages] requires the <tt>google-search</tt> gem.
|
|
27
|
+
|
|
28
|
+
A list of known 3rd-party plugins is available on the wiki: https://github.com/justinweiss/robut/wiki/Robut-Plugins
|
|
29
|
+
Feel free to add your own creations!
|
|
30
|
+
|
|
31
|
+
== The Chatfile
|
|
32
|
+
|
|
33
|
+
When the +robut+ command runs, it looks for and evals ruby code in a
|
|
34
|
+
file called +Chatfile+ in the current directory. You can override the
|
|
35
|
+
configuration file by passing +robut+ a path to a Chatfile as the
|
|
36
|
+
first parameter:
|
|
37
|
+
|
|
38
|
+
robut /path/to/Chatfile
|
|
39
|
+
|
|
40
|
+
The Chatfile is just ruby code. A simple example can be found here: Chatfile[https://github.com/justinweiss/robut/blob/master/examples/Chatfile]
|
|
41
|
+
|
|
42
|
+
== Robut::Web
|
|
43
|
+
|
|
44
|
+
You can run Robut as a rack application, which allows it to accept
|
|
45
|
+
incoming post requests (e.g. Heroku deploy hooks, GitHub post-recieve hooks, etc.)
|
|
46
|
+
that can send messages to all connected rooms. Have a look at
|
|
47
|
+
config.ru[https://github.com/justinweiss/robut/blob/master/examples/config.ru] for an example.
|
|
48
|
+
|
|
49
|
+
=== Adding and configuring plugins
|
|
50
|
+
|
|
51
|
+
Plugins are ruby classes, so enabling a plugin just requires requiring
|
|
52
|
+
the plugin file, optionally configuring the plugin class, and adding
|
|
53
|
+
the class to the global plugin list:
|
|
54
|
+
|
|
55
|
+
require 'robut/plugin/lunch'
|
|
56
|
+
Robut::Plugin::Lunch.places = ["Banh Mi", "Mad Oven", "Mod Pizza", "Taphouse"]
|
|
57
|
+
Robut::Plugin.plugins << Robut::Plugin::Lunch
|
|
58
|
+
|
|
59
|
+
Each plugin can be configured differently, or not at all. It's best to
|
|
60
|
+
look at the docs for the plugins you want to use to figure out what
|
|
61
|
+
kind of configuration they support.
|
|
62
|
+
|
|
63
|
+
Some plugins might require storage (like the `lunch` plugin). You can
|
|
64
|
+
configure the type of storage you want to use based on the need for
|
|
65
|
+
persistence. The default is the HashStore which is in-memory only. Below
|
|
66
|
+
is an example of using the YamlStore.
|
|
67
|
+
|
|
68
|
+
Robut::Connection.configure do |config|
|
|
69
|
+
# ...
|
|
70
|
+
Robut::Storage::YamlStore.file = "~/.robut_store"
|
|
71
|
+
config.store = Robut::Storage::YamlStore
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
=== Configuring the HipChat connection
|
|
76
|
+
|
|
77
|
+
The Chatfile also configures the HipChat connection. This is done in a
|
|
78
|
+
Robut::Connection.configure block:
|
|
79
|
+
|
|
80
|
+
# Configure the robut jabber connection and you're good to go!
|
|
81
|
+
Robut::Connection.configure do |config|
|
|
82
|
+
config.jid = '...@chat.hipchat.com/bot'
|
|
83
|
+
config.password = 'password'
|
|
84
|
+
config.nick = 'Bot Nick'
|
|
85
|
+
config.rooms = ['1234_room_1@conf.hipchat.com', '1234_room_2@conf.hipchat.com']
|
|
86
|
+
|
|
87
|
+
# Custom @mention name
|
|
88
|
+
config.mention_name = 'Bot'
|
|
89
|
+
|
|
90
|
+
# Example of the YamlStore which uses a yaml file for persistence
|
|
91
|
+
Robut::Storage::YamlStore.file = "~/.robut_store"
|
|
92
|
+
config.store = Robut::Storage::YamlStore
|
|
93
|
+
|
|
94
|
+
# Add a logger if you want to debug the connection
|
|
95
|
+
# config.logger = Logger.new(STDOUT)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
This block usually goes at the end of the Chatfile.
|
|
99
|
+
|
|
100
|
+
== Built-in plugins
|
|
101
|
+
|
|
102
|
+
Robut includes a few plugins that we've found useful:
|
|
103
|
+
|
|
104
|
+
[Robut::Plugin::Calc] a simple calculator.
|
|
105
|
+
|
|
106
|
+
@robut calc 1 + 1 # => 2
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
[Robut::Plugin::Lunch] a random decider for lunch locations.
|
|
110
|
+
|
|
111
|
+
@robut lunch? # => "Banh Mi!"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
[Robut::Plugin::Meme] generates meme images using memecaptain.
|
|
115
|
+
|
|
116
|
+
@robut meme all_the_things drink; all the beer
|
|
117
|
+
|
|
118
|
+
[Robut::Plugin::GoogleImages] does a google image search for a query and returns the first result.
|
|
119
|
+
|
|
120
|
+
@robut image ship it
|
|
121
|
+
|
|
122
|
+
[Robut::Plugin::Sayings] a simple regex listener and responder.
|
|
123
|
+
|
|
124
|
+
You're the worst robot ever, @robut. # => I know.
|
|
125
|
+
|
|
126
|
+
[Robut::Plugin::TWSS] an interface to the TWSS gem. Listens to anything said in the chat room and responds "That's what she said!" where appropriate.
|
|
127
|
+
|
|
128
|
+
well hurry up, you're not going fast enough # => "That's what she said!"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
[Robut::Plugin::Echo] echo back whatever it gets.
|
|
132
|
+
|
|
133
|
+
@robut echo hello world # => "hello world"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
[Robut::Plugin::Say] invokes the "say" command (text-to-speech).
|
|
137
|
+
|
|
138
|
+
@robut say this rocks # (make sure robut is running on a machine with speakers :)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
[Robut::Plugin::Ping] responds with "pong".
|
|
142
|
+
|
|
143
|
+
@robut ping # => "pong"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
[Robut::Plugin::Later] performs the given command after waiting an arbitrary amount of time.
|
|
147
|
+
|
|
148
|
+
@robut in 5 minutes echo @justin wake up! # => (5 minutes later) "@justin wake up!"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
[Robut::Plugin::Weather] uses Google Weather to fetch for the weather for a given location and day.
|
|
152
|
+
|
|
153
|
+
@robut seattle weather saturday? # => "Forecast for Seattle, WA on Sat: Sunny, High: 77F, Low: 55F"
|
|
154
|
+
|
|
155
|
+
[Robut::Plugin::Alias] creates aliases to other robut commands.
|
|
156
|
+
|
|
157
|
+
@robut alias "cowboy" "@robut play bon jovi wanted dead or alive" # Cuz somtimes you need it.
|
|
158
|
+
@robut alias w weather? # less typing for common stuff
|
|
159
|
+
|
|
160
|
+
[Robut::Plugin::Quips] stores and posts Bugzilla-style quips.
|
|
161
|
+
|
|
162
|
+
@robut add quip It was great when I wrote it!
|
|
163
|
+
@robut quip # => It was great when I wrote it!
|
|
164
|
+
@robut remove quip It was great when I wrote it!
|
|
165
|
+
|
|
166
|
+
[Robut::Plugin::Help] lists usage for all the plugins loaded into robut
|
|
167
|
+
|
|
168
|
+
@robut help # => command usage
|
|
169
|
+
|
|
170
|
+
== Writing custom plugins
|
|
171
|
+
|
|
172
|
+
You can supply your own plugins to Robut. To create a plugin, include
|
|
173
|
+
the Robut::Plugin module and implement the <tt>handle(time,
|
|
174
|
+
sender_nick, message)</tt> to perform any plugin-specific logic.
|
|
175
|
+
|
|
176
|
+
Robut::Plugin provides a few helper methods that are documented
|
|
177
|
+
in its class definition.
|
|
178
|
+
|
|
179
|
+
== Contributing
|
|
180
|
+
|
|
181
|
+
To test your changes:
|
|
182
|
+
|
|
183
|
+
1. Install Bundler[http://gembundler.com]
|
|
184
|
+
2. Run `bundle install`
|
|
185
|
+
3. Make your changes and run `bundle exec ruby -Ilib bin/robut ~/MyTestingChatfile`
|
|
186
|
+
4. Add tests and verify by running `bundle exec rake test`
|
|
187
|
+
|
|
188
|
+
Once your changes are ready:
|
|
189
|
+
|
|
190
|
+
1. Fork robut
|
|
191
|
+
2. Create a topic branch: `git checkout -b my_branch`
|
|
192
|
+
3. Commit your changes
|
|
193
|
+
4. Push to your branch: `git push origin my_branch`
|
|
194
|
+
5. Send me a pull request
|
|
195
|
+
6. That's it!
|
|
196
|
+
|
|
197
|
+
== Todo
|
|
198
|
+
|
|
199
|
+
* More plugins!
|
data/Rakefile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'rake/testtask'
|
|
2
|
+
require 'rdoc/task'
|
|
3
|
+
require 'bundler'
|
|
4
|
+
Bundler::GemHelper.install_tasks
|
|
5
|
+
|
|
6
|
+
task :default => :test
|
|
7
|
+
task :build => :test
|
|
8
|
+
|
|
9
|
+
Rake::TestTask.new do |t|
|
|
10
|
+
t.libs << "test"
|
|
11
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
12
|
+
t.verbose = true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Rake::TestTask.new(:coverage) do |t|
|
|
16
|
+
t.libs << "test"
|
|
17
|
+
t.ruby_opts = ["-rsimplecov_helper"]
|
|
18
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
19
|
+
t.verbose = true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Rake::RDocTask.new do |rd|
|
|
23
|
+
rd.main = "README.rdoc"
|
|
24
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
|
25
|
+
rd.rdoc_dir = 'doc'
|
|
26
|
+
end
|
|
27
|
+
|
data/bin/robut
ADDED
data/examples/Chatfile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Require your plugins here
|
|
2
|
+
require 'robut/plugin/twss'
|
|
3
|
+
require 'robut/storage/yaml_store'
|
|
4
|
+
|
|
5
|
+
# Add the plugin classes to the Robut plugin list.
|
|
6
|
+
# Plugins are handled in the order that they appear in this array.
|
|
7
|
+
Robut::Plugin.plugins << Robut::Plugin::TWSS
|
|
8
|
+
|
|
9
|
+
# Configure the robut jabber connection and you're good to go!
|
|
10
|
+
Robut::Connection.configure do |config|
|
|
11
|
+
# Note that the jid must end with /bot if you don't want robut to
|
|
12
|
+
# spam the channel, as described by the last bullet point on this
|
|
13
|
+
# page: https://www.hipchat.com/help/category/xmpp
|
|
14
|
+
config.jid = '...@chat.hipchat.com/bot'
|
|
15
|
+
config.password = 'password'
|
|
16
|
+
config.nick = 'Bot Nick'
|
|
17
|
+
config.room = '...@conf.hipchat.com'
|
|
18
|
+
|
|
19
|
+
# Custom @mention name
|
|
20
|
+
# config.mention_name = 'Bot'
|
|
21
|
+
|
|
22
|
+
# Ignore personal messages
|
|
23
|
+
# config.enable_private_messaging = false
|
|
24
|
+
|
|
25
|
+
# Some plugins require storage
|
|
26
|
+
Robut::Storage::YamlStore.file = ".robut"
|
|
27
|
+
config.store = Robut::Storage::YamlStore
|
|
28
|
+
|
|
29
|
+
# Add a logger if you want to debug the connection
|
|
30
|
+
# config.logger = Logger.new(STDOUT)
|
|
31
|
+
end
|
data/examples/config.ru
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Encoding patch, stolen from https://github.com/ln/xmpp4r/issues/3#issuecomment-1739952
|
|
2
|
+
require 'socket'
|
|
3
|
+
class TCPSocket
|
|
4
|
+
def external_encoding
|
|
5
|
+
Encoding::BINARY
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'rexml/source'
|
|
10
|
+
class REXML::IOSource
|
|
11
|
+
alias_method :encoding_assign, :encoding=
|
|
12
|
+
def encoding=(value)
|
|
13
|
+
encoding_assign(value) if value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
# OpenSSL is optional and can be missing
|
|
19
|
+
require 'openssl'
|
|
20
|
+
class OpenSSL::SSL::SSLSocket
|
|
21
|
+
def external_encoding
|
|
22
|
+
Encoding::BINARY
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
rescue
|
|
26
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require 'xmpp4r'
|
|
2
|
+
require 'xmpp4r/muc/helper/simplemucclient'
|
|
3
|
+
require 'xmpp4r/roster/helper/roster'
|
|
4
|
+
require 'ostruct'
|
|
5
|
+
|
|
6
|
+
if defined?(Encoding)
|
|
7
|
+
# Monkey-patch an incompatibility between ejabberd and rexml
|
|
8
|
+
require 'rexml_patches'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Handles opening a connection to the HipChat server, and feeds all
|
|
12
|
+
# messages through our Robut::Plugin list.
|
|
13
|
+
class Robut::Connection
|
|
14
|
+
|
|
15
|
+
# The configuration used by the Robut connection.
|
|
16
|
+
#
|
|
17
|
+
# Parameters:
|
|
18
|
+
#
|
|
19
|
+
# [+jid+, +password+, +nick+] The HipChat credentials given on
|
|
20
|
+
# https://www.hipchat.com/account/xmpp
|
|
21
|
+
#
|
|
22
|
+
# [+rooms+] The chat room(s) to join, with each in the format <tt>jabber_name</tt>@<tt>conference_server</tt>
|
|
23
|
+
#
|
|
24
|
+
# [+logger+] a logger instance to use for debug output.
|
|
25
|
+
attr_accessor :config
|
|
26
|
+
|
|
27
|
+
# The Jabber::Client that's connected to the HipChat server.
|
|
28
|
+
attr_accessor :client
|
|
29
|
+
|
|
30
|
+
# The storage instance that's available to plugins
|
|
31
|
+
attr_accessor :store
|
|
32
|
+
|
|
33
|
+
# The roster of currently available people
|
|
34
|
+
attr_accessor :roster
|
|
35
|
+
|
|
36
|
+
# The rooms that robut is connected to.
|
|
37
|
+
attr_accessor :rooms
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Class-level config. This is set by the +configure+ class method,
|
|
41
|
+
# and is used if no configuration is passed to the +initialize+
|
|
42
|
+
# method.
|
|
43
|
+
attr_accessor :config
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Configures the connection at the class level. When the +robut+ bin
|
|
47
|
+
# file is loaded, it evals the file referenced by the first
|
|
48
|
+
# command-line parameter. This file can configure the connection
|
|
49
|
+
# instance later created by +robut+ by setting parameters in the
|
|
50
|
+
# Robut::Connection.configure block.
|
|
51
|
+
def self.configure
|
|
52
|
+
self.config = OpenStruct.new
|
|
53
|
+
yield config
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Sets the instance config to +config+, converting it into an
|
|
57
|
+
# OpenStruct if necessary.
|
|
58
|
+
def config=(config)
|
|
59
|
+
@config = config.kind_of?(Hash) ? OpenStruct.new(config) : config
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Initializes the connection. If no +config+ is passed, it defaults
|
|
63
|
+
# to the class_level +config+ instance variable.
|
|
64
|
+
def initialize(_config = nil)
|
|
65
|
+
self.config = _config || self.class.config
|
|
66
|
+
|
|
67
|
+
self.client = Jabber::Client.new(self.config.jid)
|
|
68
|
+
self.store = self.config.store || Robut::Storage::HashStore # default to in-memory store only
|
|
69
|
+
self.config.rooms ||= Array(self.config.room) # legacy support?
|
|
70
|
+
self.config.enable_private_messaging = true if self.config.enable_private_messaging.nil?
|
|
71
|
+
|
|
72
|
+
if self.config.logger
|
|
73
|
+
Jabber.logger = self.config.logger
|
|
74
|
+
Jabber.debug = true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Connects to the specified room with the given credentials, and
|
|
79
|
+
# enters an infinite loop. Any messages sent to the room will pass
|
|
80
|
+
# through all the included plugins.
|
|
81
|
+
def connect
|
|
82
|
+
client.connect
|
|
83
|
+
client.auth(config.password)
|
|
84
|
+
client.send(Jabber::Presence.new.set_type(:available))
|
|
85
|
+
|
|
86
|
+
self.roster = Jabber::Roster::Helper.new(client)
|
|
87
|
+
roster.wait_for_roster
|
|
88
|
+
|
|
89
|
+
self.rooms = self.config.rooms.collect do |room_name|
|
|
90
|
+
Robut::Room.new(self, room_name).tap {|r| r.join }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if self.config.enable_private_messaging
|
|
94
|
+
Robut::PM.new(self, rooms)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
trap_signals
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Send a message to all rooms.
|
|
102
|
+
def reply(*args, &block)
|
|
103
|
+
self.rooms.each do |room|
|
|
104
|
+
room.reply(*args, &block)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
# Since we're entering an infinite loop, we have to trap TERM and
|
|
110
|
+
# INT. If something like the Rdio plugin has started a server that
|
|
111
|
+
# has already trapped those signals, we want to run those signal
|
|
112
|
+
# handlers first.
|
|
113
|
+
def trap_signals
|
|
114
|
+
old_signal_callbacks = {}
|
|
115
|
+
signal_callback = Proc.new do |signal|
|
|
116
|
+
old_signal_callbacks[signal].call if old_signal_callbacks[signal]
|
|
117
|
+
exit
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
[:INT, :TERM].each do |sig|
|
|
121
|
+
old_signal_callbacks[sig] = trap(sig) { signal_callback.call(sig) }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
|
|
3
|
+
# Alias robut commands:
|
|
4
|
+
#
|
|
5
|
+
# @robut alias "something" "some long message"
|
|
6
|
+
#
|
|
7
|
+
# Later if @robut receives the message "@robut something" he will
|
|
8
|
+
# repond as if he received "@robut some long message"
|
|
9
|
+
#
|
|
10
|
+
#
|
|
11
|
+
# Valid use:
|
|
12
|
+
#
|
|
13
|
+
# @robut alias this "something long"
|
|
14
|
+
# @robut alias "this thing" "something long"
|
|
15
|
+
# @robut alias this something_long
|
|
16
|
+
#
|
|
17
|
+
# Listing all aliases
|
|
18
|
+
#
|
|
19
|
+
# @robut aliases
|
|
20
|
+
#
|
|
21
|
+
# Removing aliases
|
|
22
|
+
#
|
|
23
|
+
# @robut remove alias "this alias"
|
|
24
|
+
# @robut remove alias this
|
|
25
|
+
# @robut remove clear aliases # removes everything
|
|
26
|
+
#
|
|
27
|
+
# Note: you probably want the Alias plugin as one of the first things
|
|
28
|
+
# in the plugin array (since plugins are executed in order).
|
|
29
|
+
class Robut::Plugin::Alias
|
|
30
|
+
include Robut::Plugin
|
|
31
|
+
|
|
32
|
+
# Returns a description of how to use this plugin
|
|
33
|
+
def usage
|
|
34
|
+
[
|
|
35
|
+
"#{at_nick} alias <words> <expansion> - when #{nick} sees <words>, pretend it saw <expansion> instead",
|
|
36
|
+
"#{at_nick} aliases - show all aliases",
|
|
37
|
+
"#{at_nick} remove alias <words> - remove <words> as an alias",
|
|
38
|
+
"#{at_nick} clear aliases - remove all aliases"
|
|
39
|
+
]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Perform the calculation specified in +message+, and send the
|
|
43
|
+
# result back.
|
|
44
|
+
def handle(time, sender_nick, message)
|
|
45
|
+
if new_message = get_alias(message)
|
|
46
|
+
# Apply the alias
|
|
47
|
+
fake_message Time.now, sender_nick, new_message
|
|
48
|
+
elsif sent_to_me?(message)
|
|
49
|
+
message = without_nick message
|
|
50
|
+
if message =~ /^remove alias (.*)/
|
|
51
|
+
# Remove the alias
|
|
52
|
+
key = parse_alias_key($1)
|
|
53
|
+
remove_alias key
|
|
54
|
+
return true
|
|
55
|
+
elsif message =~ /^clear aliases$/
|
|
56
|
+
self.aliases = {}
|
|
57
|
+
return true
|
|
58
|
+
elsif message =~ /^alias (.*)/
|
|
59
|
+
# Create a new alias
|
|
60
|
+
message = $1
|
|
61
|
+
key, value = parse_alias message
|
|
62
|
+
store_alias key, value
|
|
63
|
+
return true # hault plugin execution chain
|
|
64
|
+
elsif words(message).first == 'aliases'
|
|
65
|
+
# List all aliases
|
|
66
|
+
m, a = [], aliases # create reference to avoid going to the store every time
|
|
67
|
+
a.keys.sort.each { |key| m << "#{key} => #{a[key]}" }
|
|
68
|
+
reply m.join("\n")
|
|
69
|
+
return true
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Given a message, returns what it is aliased to (or nil)
|
|
75
|
+
def get_alias(msg)
|
|
76
|
+
(store['aliases'] || {})[msg]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def store_alias(key, value)
|
|
80
|
+
aliases[key] = value
|
|
81
|
+
store['aliases'] = aliases
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def remove_alias(key)
|
|
85
|
+
new_aliases = aliases
|
|
86
|
+
new_aliases.delete(key)
|
|
87
|
+
store['aliases'] = new_aliases
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def aliases
|
|
91
|
+
store['aliases'] ||= {}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def aliases=(v)
|
|
95
|
+
store['aliases'] = v
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns alias and command
|
|
99
|
+
def parse_alias(str)
|
|
100
|
+
r = Shellwords.shellwords str
|
|
101
|
+
return r[0], r[1] if r.length == 2
|
|
102
|
+
return r[0], r[1..-1].join(' ')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def parse_alias_key(str)
|
|
106
|
+
Shellwords.shellwords(str).join(' ')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'calc'
|
|
2
|
+
|
|
3
|
+
# A simple calculator. This delegates all calculations to the 'calc'
|
|
4
|
+
# gem.
|
|
5
|
+
class Robut::Plugin::Calc
|
|
6
|
+
include Robut::Plugin
|
|
7
|
+
|
|
8
|
+
# Returns a description of how to use this plugin
|
|
9
|
+
def usage
|
|
10
|
+
"#{at_nick} calc <calculation> - replies with the result of <calculation>"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Perform the calculation specified in +message+, and send the
|
|
14
|
+
# result back.
|
|
15
|
+
def handle(time, sender_nick, message)
|
|
16
|
+
if sent_to_me?(message) && words(message).first == 'calc'
|
|
17
|
+
calculation = words(message, 'calc').join(' ')
|
|
18
|
+
begin
|
|
19
|
+
reply("#{calculation} = #{::Calc.evaluate(calculation)}")
|
|
20
|
+
rescue
|
|
21
|
+
reply("Can't calculate that.")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# A plugin that tells robut to repeat whatever he's told.
|
|
2
|
+
class Robut::Plugin::Echo
|
|
3
|
+
include Robut::Plugin
|
|
4
|
+
|
|
5
|
+
desc "echo <message> - replies to the channel with <message>"
|
|
6
|
+
match /^echo (.*)/, :sent_to_me => true do |phrase|
|
|
7
|
+
reply(phrase) unless phrase.empty?
|
|
8
|
+
end
|
|
9
|
+
end
|