campfire-bot_alexchee 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +11 -0
- data/.gitignore +6 -0
- data/Gemfile +12 -0
- data/README.textile +62 -0
- data/TODO +72 -0
- data/bin/campfire-bot +59 -0
- data/campfire-bot.gemspec +24 -0
- data/cfbot-stop.sh +8 -0
- data/config.example.yml +32 -0
- data/lib/bot.rb +194 -0
- data/lib/event.rb +114 -0
- data/lib/message.rb +30 -0
- data/lib/plugin.rb +77 -0
- data/lib/version.rb +3 -0
- data/plugins-example/fun.rb +95 -0
- data/spec/command_spec.rb +96 -0
- data/spec/plugin_spec.rb +43 -0
- data/spec/spec.opts +1 -0
- data/tmp/.gitignore +0 -0
- data/vendor/escape/ChangeLog +30 -0
- data/vendor/escape/Makefile +5 -0
- data/vendor/escape/README +81 -0
- data/vendor/escape/VERSION +1 -0
- data/vendor/escape/escape.rb +302 -0
- data/vendor/escape/install.rb +109 -0
- data/vendor/escape/misc/README.erb +85 -0
- data/vendor/escape/rdoc/classes/Escape/HTMLAttrValue.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/HTMLEscaped.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/PercentEncoded.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/ShellEscaped.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.html +242 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000029.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000030.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000031.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000032.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000033.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000035.html +18 -0
- data/vendor/escape/rdoc/classes/Escape.html +427 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000022.html +19 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000023.html +32 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000024.html +24 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000025.html +19 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000026.html +48 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000027.html +19 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000028.html +19 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.html +182 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000008.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000009.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000010.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000011.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.html +182 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000012.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000013.html +19 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000014.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000015.html +22 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.html +167 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000016.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000017.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000018.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.html +167 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000019.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000020.html +24 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000021.html +22 -0
- data/vendor/escape/rdoc/files/escape_rb.html +136 -0
- data/vendor/escape/rdoc/files/install_rb.html +250 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000001.html +23 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000002.html +31 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000003.html +27 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000004.html +27 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000005.html +21 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000006.html +23 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000007.html +21 -0
- data/vendor/escape/rdoc/files/test/test-escape_rb.html +109 -0
- data/vendor/escape/rdoc/fr_class_index.html +36 -0
- data/vendor/escape/rdoc/fr_file_index.html +29 -0
- data/vendor/escape/rdoc/fr_method_index.html +61 -0
- data/vendor/escape/rdoc/index.html +24 -0
- data/vendor/escape/rdoc/rdoc-style.css +208 -0
- data/vendor/escape/test/test-escape.rb +90 -0
- metadata +209 -0
data/.autotest
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.textile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
h1. Campfire Bot
|
2
|
+
|
3
|
+
This is a bot for 37 Signals' Campfire chat service.
|
4
|
+
|
5
|
+
It has plugin support so you can make your bot do whatever you want.
|
6
|
+
|
7
|
+
h2. Installation
|
8
|
+
|
9
|
+
@gem install campfire-bot@
|
10
|
+
|
11
|
+
h3. Download
|
12
|
+
|
13
|
+
Download the source from "github":http://github.com/joshwand/campfire-bot
|
14
|
+
|
15
|
+
|
16
|
+
h3. Configuration
|
17
|
+
|
18
|
+
Create a @config.yml@ in the root of the source directory. Use @config.example.yml@ as an example.
|
19
|
+
|
20
|
+
h2. Usage
|
21
|
+
|
22
|
+
To run the bot, run @campfire-bot@ with the following parameters:
|
23
|
+
|
24
|
+
-c config FILE path to config file
|
25
|
+
-e ENVIRONMENT_NAME environment name to load from config file
|
26
|
+
-p plugin PLUGIN_PATH path to your plugins
|
27
|
+
|
28
|
+
Example:
|
29
|
+
|
30
|
+
@campfire-bot -c config.yml -e production -p ./plugins@
|
31
|
+
|
32
|
+
h2. Plugins
|
33
|
+
|
34
|
+
See plugins-example/fun.rb for an example plugin.
|
35
|
+
|
36
|
+
The old plugins that used to be bundled with campfire-bot are now in their own repo: "campfire-bot-plugins":https://github.com/joshwand/campfire-bot-plugins
|
37
|
+
|
38
|
+
|
39
|
+
h2. Known issues
|
40
|
+
|
41
|
+
h3. [BUG] Bus Error
|
42
|
+
|
43
|
+
This is really an issue with EventMachine. If you are using this on a machine that has MacPorts, you'll need to make sure that you build both ruby and eventmachine linked against the openssl in /opt/local, not /usr/local.
|
44
|
+
|
45
|
+
h3. warning: peer certificate won't be verified in this SSL session
|
46
|
+
|
47
|
+
A bug in net/http. Noisy, but harmless.
|
48
|
+
|
49
|
+
h2. Original Author
|
50
|
+
|
51
|
+
Tim Riley - "github":http://github.com/timriley | "www":http://openmonkey.com/ | "email":mailto:tim@openmonkey.com
|
52
|
+
|
53
|
+
h2. Maintainer
|
54
|
+
|
55
|
+
* Josh Wand - "github":http://github.com/joshwand
|
56
|
+
|
57
|
+
h3. Contributors
|
58
|
+
|
59
|
+
* Marcel M. Cary - "github":http://github.com/mcary
|
60
|
+
* Hugh Evans - "github":http://github.com/artpop
|
61
|
+
* Sean O'Dowd - "github":http://github.com/seanodowd
|
62
|
+
* Andrew Erickson - "github":http://github.com/aerickson
|
data/TODO
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
- add chronic integration for interval-based events: at_time("every day at 9am", :method_name)
|
2
|
+
|
3
|
+
- per-plugin config files (inside a config-specific dir) - best not to pollute a single YAML file.
|
4
|
+
|
5
|
+
- get it to work with the newest version of mechanize.
|
6
|
+
|
7
|
+
/opt/local/lib/ruby/gems/1.8/gems/timriley-tinder-1.1.9/lib/tinder/mechanize_ext.rb:6: undefined method `set_headers' for class `WWW::Mechanize' (NameError)
|
8
|
+
from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
|
9
|
+
from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:27:in `require'
|
10
|
+
from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:510:in `require'
|
11
|
+
from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:355:in `new_constants_in'
|
12
|
+
from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:510:in `require'
|
13
|
+
from /opt/local/lib/ruby/gems/1.8/gems/timriley-tinder-1.1.9/lib/tinder.rb:10
|
14
|
+
from /opt/local/lib/ruby/gems/1.8/gems/timriley-tinder-1.1.9/lib/tinder.rb:10:in `each'
|
15
|
+
from /opt/local/lib/ruby/gems/1.8/gems/timriley-tinder-1.1.9/lib/tinder.rb:10
|
16
|
+
from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:32:in `gem_original_require'
|
17
|
+
from /opt/local/lib/ruby/vendor_ruby/1.8/rubygems/custom_require.rb:32:in `require'
|
18
|
+
from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:510:in `require'
|
19
|
+
from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:355:in `new_constants_in'
|
20
|
+
from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:510:in `require'
|
21
|
+
from ./script/../lib/bot.rb:13
|
22
|
+
from ./script/bot:7:in `require'
|
23
|
+
from ./script/bot:7
|
24
|
+
|
25
|
+
- need flood control for bot plugins
|
26
|
+
|
27
|
+
- plugins need callbacks, like on_join, bot_loaded, etc. Especially one that is run whenever control+c is caught (for plugins to clean up)
|
28
|
+
|
29
|
+
- gem-ify the bot and have it provide an executable to generate an application directory
|
30
|
+
like rails
|
31
|
+
|
32
|
+
- move the plugins into their own repository and provide a way for them to be installed
|
33
|
+
into the app dir above (like script/plugin install)
|
34
|
+
|
35
|
+
- use a message handler chain in the same way that activesupport uses callback chains.
|
36
|
+
|
37
|
+
- provide a way for plugins to buffer themselves, so they don't run too many times
|
38
|
+
|
39
|
+
- buffer the "speak" calls so that they can be uniq'ed before printed to the channel?
|
40
|
+
|
41
|
+
- plugins should be able to register respond_to blocks with a notion of priority.
|
42
|
+
eg. a low-priority command can say it doesn't need to be processed if something else matches.
|
43
|
+
conversely, a high-priority command can cause to bot to stop processing the message once it has matched.
|
44
|
+
|
45
|
+
- create a plugin that pulls in unfuddle ticket reports, eg. !unfuddle project_name active_tickets
|
46
|
+
|
47
|
+
- modify the austin powers plugin to be a generic imdb quote puller. eg. !imdb austin_powers
|
48
|
+
|
49
|
+
- unit tests!
|
50
|
+
|
51
|
+
- help plugin-- extend plugin class to provide help for each registered command
|
52
|
+
|
53
|
+
- room.log method, can be called from plugins - store logs in ./log/*.log
|
54
|
+
|
55
|
+
DONE
|
56
|
+
|
57
|
+
- multi-room support
|
58
|
+
|
59
|
+
- need to catch timeout errors and do something sensible, like wait and retry the connection
|
60
|
+
|
61
|
+
http://www.google.com/search?q=ruby%20rescue%20Timeout::Error
|
62
|
+
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/136555
|
63
|
+
http://jerith.livejournal.com/40063.html
|
64
|
+
http://rails.lighthouseapp.com/projects/8994/tickets/6-patch-activeresource-connection-should-rescue-from-timeout-error
|
65
|
+
|
66
|
+
- alternative addressing syntax (!command)
|
67
|
+
|
68
|
+
- need to catch no-network errors and return a sensible error message:
|
69
|
+
|
70
|
+
/opt/local/lib/ruby/1.8/net/http.rb:560:in `initialize': Network is unreachable - connect(2) (Errno::ENETUNREACH)
|
71
|
+
|
72
|
+
- plugin: svn commit notification (multiple repos)
|
data/bin/campfire-bot
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Run this script with the environment as the only argument. eg. ./script/bot development
|
4
|
+
# BOT_ENVIRONMENT = ARGV.first
|
5
|
+
# BOT_ROOT = File.join(File.dirname(__FILE__), '..')
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
# require 'bundler/setup'
|
9
|
+
require 'optparse'
|
10
|
+
require 'bot'
|
11
|
+
# require File.join(BOT_ROOT, 'lib', 'bot')
|
12
|
+
|
13
|
+
|
14
|
+
options = {}
|
15
|
+
|
16
|
+
|
17
|
+
begin
|
18
|
+
optparse = OptionParser.new do |opts|
|
19
|
+
opts.banner = "Usage: campfire-bot [options]"
|
20
|
+
|
21
|
+
opts.on("-cCONFIG_FILE", "--config CONFIG_FILE", "path to your config file") do |config|
|
22
|
+
options[:config_file] = config
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-eENVIRONMENT", "--environment ENVIRONMENT", "the environment name as specified in the config file") do |env|
|
26
|
+
options[:env] = env
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on( "-pPLUGIN_PATH", "--plugin_path PLUGIN_PATH",
|
30
|
+
"path to your plugins folder" ) do |pp|
|
31
|
+
options[:plugin_path] = pp
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on_tail("-h", "--help", "prints this help text") do
|
35
|
+
p opts
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
optparse.parse!
|
41
|
+
|
42
|
+
mandatory = [:config_file, :env, :plugin_path]
|
43
|
+
missing = mandatory.select{ |param| options[param].nil? }
|
44
|
+
if not missing.empty?
|
45
|
+
puts "Missing options: #{missing.join(', ')}"
|
46
|
+
puts optparse
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
50
|
+
puts $!.to_s
|
51
|
+
puts optparse
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
bot.create(options[:config_file], options[:env], options[:plugin_path])
|
57
|
+
|
58
|
+
bot.connect
|
59
|
+
bot.run
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "campfire-bot_alexchee"
|
7
|
+
s.version = CampfireBot::VERSION
|
8
|
+
s.authors = ["Josh Wand", "Tim Riley"]
|
9
|
+
s.email = ["josh@joshwand.com"]
|
10
|
+
s.homepage = "https://github.com/alexchee/campfire-bot"
|
11
|
+
s.summary = %q{This is a bot for 37 Signals’ Campfire chat service.}
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency 'twitter-stream', '~> 0.1'
|
20
|
+
s.add_dependency 'tinder', '>= 1.4.3'
|
21
|
+
s.add_dependency 'activesupport', '>= 3.0.3'
|
22
|
+
s.add_dependency 'logging', '~> 1.4.3'
|
23
|
+
s.add_dependency 'i18n', '~> 0.5.0'
|
24
|
+
end
|
data/cfbot-stop.sh
ADDED
data/config.example.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
development:
|
2
|
+
site: campfire_subdomain_name
|
3
|
+
use_ssl: true
|
4
|
+
rooms:
|
5
|
+
- Main room
|
6
|
+
- Other room
|
7
|
+
api_key: your_api_key
|
8
|
+
nickname: Bot
|
9
|
+
fullname: Bot Name
|
10
|
+
password: not_actually_needed
|
11
|
+
twitter_feed: 'http://search.twitter.com/search.atom?q=from%3Atimriley+OR+from%3Ahughevans+OR+from%3Aschlick+OR+from%3Aseanodowd'
|
12
|
+
twitter_hide_replies: false
|
13
|
+
enable_plugins:
|
14
|
+
- xkcd
|
15
|
+
- basecamp
|
16
|
+
production:
|
17
|
+
site: campfire_subdomain_name
|
18
|
+
use_ssl: true
|
19
|
+
rooms:
|
20
|
+
- Company Chat
|
21
|
+
username: foo@bar.com
|
22
|
+
nickname: Bot Name
|
23
|
+
password: password
|
24
|
+
twitter_feed: 'http://search.twitter.com/search.atom?q=from%3Atimriley+OR+from%3Ahughevans+OR+from%3Aschlick+OR+from%3Aseanodowd'
|
25
|
+
twitter_hide_replies: false
|
26
|
+
enable_plugins:
|
27
|
+
- xkcd
|
28
|
+
- basecamp
|
29
|
+
svn_urls:
|
30
|
+
- http://your.svn/url/
|
31
|
+
svn_username: your_svn_username
|
32
|
+
svn_password: your_svn_password
|
data/lib/bot.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
# External Libs
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/hash_with_indifferent_access'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
require 'yaml'
|
7
|
+
require 'eventmachine'
|
8
|
+
require 'logging'
|
9
|
+
require 'fileutils'
|
10
|
+
|
11
|
+
# Local Libs
|
12
|
+
require "message"
|
13
|
+
require "event"
|
14
|
+
require "plugin"
|
15
|
+
|
16
|
+
require 'tinder'
|
17
|
+
|
18
|
+
module CampfireBot
|
19
|
+
class Bot
|
20
|
+
# this is necessary so the room and campfire objects can be accessed by plugins.
|
21
|
+
include Singleton
|
22
|
+
|
23
|
+
# FIXME - these will be invalid if disconnected. handle this.
|
24
|
+
attr_reader :campfire, :rooms, :config, :log, :environment_name
|
25
|
+
|
26
|
+
def create(config_file, environment_name, plugin_path)
|
27
|
+
@config = YAML::load(File.read(config_file))[environment_name]
|
28
|
+
@plugin_path = plugin_path
|
29
|
+
@environment_name = environment_name
|
30
|
+
|
31
|
+
@root_logger = Logging.logger["CampfireBot"]
|
32
|
+
@log = Logging.logger[self]
|
33
|
+
|
34
|
+
# TODO much of this should be configurable per environment
|
35
|
+
|
36
|
+
@root_logger.add_appenders Logging.appenders.rolling_file("#{@environment_name}.log",
|
37
|
+
:layout => Logging.layouts.pattern(:pattern => "%d | %-6l | %-12c | %m\n"),
|
38
|
+
:age => 'daily',
|
39
|
+
:keep => 7)
|
40
|
+
@root_logger.level = @config['log_level'] rescue :info
|
41
|
+
|
42
|
+
@timeouts = 0
|
43
|
+
|
44
|
+
@rooms = {}
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# def initialize
|
49
|
+
# end
|
50
|
+
|
51
|
+
def connect
|
52
|
+
load_plugins unless !@config['enable_plugins']
|
53
|
+
begin
|
54
|
+
join_rooms
|
55
|
+
rescue Errno::ENETUNREACH, SocketError => e
|
56
|
+
@log.fatal "We had trouble connecting to the network: #{e.class}: #{e.message}"
|
57
|
+
abort "We had trouble connecting to the network: #{e.class}: #{e.message}"
|
58
|
+
rescue Exception => e
|
59
|
+
@log.fatal "Unhandled exception while joining rooms: #{e.class}: #{e.message} \n #{$!.backtrace.join("\n")}"
|
60
|
+
abort "Unhandled exception while joining rooms: #{e.class}: #{e.message} \n #{$!.backtrace.join("\n")}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def run(interval = 5)
|
65
|
+
catch(:stop_listening) do
|
66
|
+
trap('INT') { throw :stop_listening }
|
67
|
+
|
68
|
+
# since room#listen blocks, stick it in its own thread
|
69
|
+
@rooms.each_pair do |room_name, room|
|
70
|
+
Thread.new do
|
71
|
+
begin
|
72
|
+
room.listen(:timeout => 8) do |raw_msg|
|
73
|
+
handle_message(CampfireBot::Message.new(raw_msg.merge({:room => room})))
|
74
|
+
end
|
75
|
+
rescue Exception => e
|
76
|
+
trace = e.backtrace.join("\n")
|
77
|
+
abort "something went wrong! #{e.message}\n #{trace}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
loop do
|
83
|
+
begin
|
84
|
+
@rooms.each_pair do |room_name, room|
|
85
|
+
|
86
|
+
# I assume if we reach here, all the network-related activity has occured successfully
|
87
|
+
# and that we're outside of the retry-cycle
|
88
|
+
@timeouts = 0
|
89
|
+
|
90
|
+
# Here's how I want it to look
|
91
|
+
# @room.listen.each { |m| EventHandler.handle_message(m) }
|
92
|
+
# EventHanlder.handle_time(optional_arg = Time.now)
|
93
|
+
|
94
|
+
# Run time-oriented events
|
95
|
+
Plugin.registered_intervals.each do |handler|
|
96
|
+
begin
|
97
|
+
handler.run(CampfireBot::Message.new(:room => room))
|
98
|
+
rescue
|
99
|
+
@log.error "error running #{handler.inspect}: #{$!.class}: #{$!.message} \n #{$!.backtrace.join("\n")}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
Plugin.registered_times.each_with_index do |handler, index|
|
104
|
+
begin
|
105
|
+
Plugin.registered_times.delete_at(index) if handler.run
|
106
|
+
rescue
|
107
|
+
@log.error "error running #{handler.inspect}: #{$!.class}: #{$!.message}, \n #{$!.backtrace.join("\n")}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
STDOUT.flush
|
113
|
+
sleep interval
|
114
|
+
rescue Timeout::Error => e
|
115
|
+
if @timeouts < 5
|
116
|
+
sleep(5 * @timeouts)
|
117
|
+
@timeouts += 1
|
118
|
+
retry
|
119
|
+
else
|
120
|
+
raise e.message
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def join_rooms
|
130
|
+
join_rooms_as_user
|
131
|
+
@log.info "Joined all rooms."
|
132
|
+
end
|
133
|
+
|
134
|
+
def join_rooms_as_user
|
135
|
+
@campfire = Tinder::Campfire.new(@config['site'], :token => @config['api_key'])
|
136
|
+
|
137
|
+
@config['rooms'].each do |room_name|
|
138
|
+
@rooms[room_name] = @campfire.find_room_by_name(room_name)
|
139
|
+
raise Exception.new("couldn't find a room named #{room_name}!") if @rooms[room_name].nil?
|
140
|
+
res = @rooms[room_name].join
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def load_plugins
|
145
|
+
@config['enable_plugins'].each do |plugin_name|
|
146
|
+
load "#{@plugin_path}/#{plugin_name}.rb"
|
147
|
+
end
|
148
|
+
|
149
|
+
# And instantiate them
|
150
|
+
Plugin.registered_plugins.each_pair do |name, klass|
|
151
|
+
@log.info "loading plugin: #{name}"
|
152
|
+
STDOUT.flush
|
153
|
+
Plugin.registered_plugins[name] = klass.new
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def handle_message(message)
|
158
|
+
# puts message.inspect
|
159
|
+
|
160
|
+
case message[:type]
|
161
|
+
when "KickMessage"
|
162
|
+
if message[:user][:id] == @campfire.me[:id]
|
163
|
+
@log.info "got kicked... rejoining after 10 seconds"
|
164
|
+
sleep 10
|
165
|
+
join_rooms_as_user
|
166
|
+
@log.info "rejoined room."
|
167
|
+
return
|
168
|
+
end
|
169
|
+
when "TimestampMessage", "AdvertisementMessage"
|
170
|
+
return
|
171
|
+
when "TextMessage", "PasteMessage"
|
172
|
+
# only process non-bot messages
|
173
|
+
unless message[:user][:id] == @campfire.me[:id]
|
174
|
+
@log.info "#{message[:person]} | #{message[:message]}"
|
175
|
+
%w(commands speakers messages).each do |type|
|
176
|
+
Plugin.send("registered_#{type}").each do |handler|
|
177
|
+
begin
|
178
|
+
handler.run(message)
|
179
|
+
rescue Exception => e
|
180
|
+
@log.error "error running #{handler.inspect}: #{$!.class}: #{$!.message} \n #{$!.backtrace.join("\n")}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
else
|
186
|
+
@log.debug "got message of type #{message['type']} -- discarding"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def bot
|
193
|
+
CampfireBot::Bot.instance
|
194
|
+
end
|
data/lib/event.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module CampfireBot
|
2
|
+
module Event
|
3
|
+
|
4
|
+
# This is an abstract base class for all event types, not to be used directly.
|
5
|
+
class EventHandler
|
6
|
+
|
7
|
+
attr_reader :kind, :matcher, :plugin, :method
|
8
|
+
|
9
|
+
def self.handles(event_type)
|
10
|
+
@kind = event_type
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(matcher, plugin_name, method_name)
|
14
|
+
@matcher = matcher
|
15
|
+
@plugin = plugin_name
|
16
|
+
@method = method_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(msg, force = false)
|
20
|
+
if force || match?(msg)
|
21
|
+
Plugin.registered_plugins[@plugin].send(@method, filter_message(msg))
|
22
|
+
else
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def filter_message(msg)
|
30
|
+
msg
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Command < EventHandler
|
35
|
+
handles :messages
|
36
|
+
|
37
|
+
def match?(msg)
|
38
|
+
(
|
39
|
+
msg[:message][0..0] == '!' ||
|
40
|
+
msg[:message] =~ Regexp.new("^#{bot.config['nickname']}(,|:)", Regexp::IGNORECASE)
|
41
|
+
) &&
|
42
|
+
msg[:message].gsub(/^\!/, '').gsub(Regexp.new("^#{bot.config['nickname']}(,|:)?\\s*", Regexp::IGNORECASE), '').split(' ')[0].to_s.downcase == @matcher.downcase
|
43
|
+
# FIXME - the above should be just done with one regexp to pull out the first non-! non-<bot name> word.
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def filter_message(msg)
|
49
|
+
msg[:message] = msg[:message].gsub(Regexp.new("^(!|#{bot.config['nickname']}[,:]\\s+)#{@matcher.downcase}\\s?", Regexp::IGNORECASE), '')
|
50
|
+
msg
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Speaker < EventHandler
|
55
|
+
handles :messages
|
56
|
+
|
57
|
+
def match?(msg)
|
58
|
+
msg[:person] == @matcher
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Message < EventHandler
|
63
|
+
handles :messages
|
64
|
+
|
65
|
+
def match?(msg)
|
66
|
+
msg[:message] =~ @matcher
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Interval < EventHandler
|
71
|
+
handles :times
|
72
|
+
|
73
|
+
def initialize(*args)
|
74
|
+
@last_run = ::Time.now
|
75
|
+
super(*args)
|
76
|
+
end
|
77
|
+
|
78
|
+
def match?
|
79
|
+
@last_run < ::Time.now - @matcher
|
80
|
+
end
|
81
|
+
|
82
|
+
def run(msg, force = false)
|
83
|
+
if match?
|
84
|
+
@last_run = ::Time.now
|
85
|
+
Plugin.registered_plugins[@plugin].send(@method, msg)
|
86
|
+
else
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class Time < EventHandler
|
93
|
+
handles :times
|
94
|
+
|
95
|
+
def initialize(*args)
|
96
|
+
@run = false
|
97
|
+
super(*args)
|
98
|
+
end
|
99
|
+
|
100
|
+
def match?
|
101
|
+
@matcher <= ::Time.now && !@run
|
102
|
+
end
|
103
|
+
|
104
|
+
def run(force = false)
|
105
|
+
if match?
|
106
|
+
@run = true
|
107
|
+
Plugin.registered_plugins[@plugin].send(@method)
|
108
|
+
else
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/message.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module CampfireBot
|
2
|
+
class Message < ActiveSupport::HashWithIndifferentAccess
|
3
|
+
def initialize(attributes)
|
4
|
+
self.merge!(attributes)
|
5
|
+
self[:message] = self['body'] if !!self['body']
|
6
|
+
self[:person] = self['user']['name'] if !!self['user']
|
7
|
+
self[:room] = attributes[:room]
|
8
|
+
end
|
9
|
+
|
10
|
+
def reply(str)
|
11
|
+
speak(str)
|
12
|
+
end
|
13
|
+
|
14
|
+
def speak(str)
|
15
|
+
self[:room].speak(str)
|
16
|
+
end
|
17
|
+
|
18
|
+
def paste(str)
|
19
|
+
self[:room].paste(str)
|
20
|
+
end
|
21
|
+
|
22
|
+
def upload(file_path)
|
23
|
+
self[:room].upload(file_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def play(str)
|
27
|
+
self[:room].play(str)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|