campfire-bot_alexchee 1.0.0

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 (80) hide show
  1. data/.autotest +11 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +12 -0
  4. data/README.textile +62 -0
  5. data/TODO +72 -0
  6. data/bin/campfire-bot +59 -0
  7. data/campfire-bot.gemspec +24 -0
  8. data/cfbot-stop.sh +8 -0
  9. data/config.example.yml +32 -0
  10. data/lib/bot.rb +194 -0
  11. data/lib/event.rb +114 -0
  12. data/lib/message.rb +30 -0
  13. data/lib/plugin.rb +77 -0
  14. data/lib/version.rb +3 -0
  15. data/plugins-example/fun.rb +95 -0
  16. data/spec/command_spec.rb +96 -0
  17. data/spec/plugin_spec.rb +43 -0
  18. data/spec/spec.opts +1 -0
  19. data/tmp/.gitignore +0 -0
  20. data/vendor/escape/ChangeLog +30 -0
  21. data/vendor/escape/Makefile +5 -0
  22. data/vendor/escape/README +81 -0
  23. data/vendor/escape/VERSION +1 -0
  24. data/vendor/escape/escape.rb +302 -0
  25. data/vendor/escape/install.rb +109 -0
  26. data/vendor/escape/misc/README.erb +85 -0
  27. data/vendor/escape/rdoc/classes/Escape/HTMLAttrValue.html +113 -0
  28. data/vendor/escape/rdoc/classes/Escape/HTMLEscaped.html +113 -0
  29. data/vendor/escape/rdoc/classes/Escape/PercentEncoded.html +113 -0
  30. data/vendor/escape/rdoc/classes/Escape/ShellEscaped.html +113 -0
  31. data/vendor/escape/rdoc/classes/Escape/StringWrapper.html +242 -0
  32. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000029.html +18 -0
  33. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000030.html +18 -0
  34. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000031.html +18 -0
  35. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000032.html +18 -0
  36. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000033.html +18 -0
  37. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000035.html +18 -0
  38. data/vendor/escape/rdoc/classes/Escape.html +427 -0
  39. data/vendor/escape/rdoc/classes/Escape.src/M000022.html +19 -0
  40. data/vendor/escape/rdoc/classes/Escape.src/M000023.html +32 -0
  41. data/vendor/escape/rdoc/classes/Escape.src/M000024.html +24 -0
  42. data/vendor/escape/rdoc/classes/Escape.src/M000025.html +19 -0
  43. data/vendor/escape/rdoc/classes/Escape.src/M000026.html +48 -0
  44. data/vendor/escape/rdoc/classes/Escape.src/M000027.html +19 -0
  45. data/vendor/escape/rdoc/classes/Escape.src/M000028.html +19 -0
  46. data/vendor/escape/rdoc/classes/TestEscapeHTML.html +182 -0
  47. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000008.html +18 -0
  48. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000009.html +18 -0
  49. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000010.html +18 -0
  50. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000011.html +18 -0
  51. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.html +182 -0
  52. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000012.html +18 -0
  53. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000013.html +19 -0
  54. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000014.html +20 -0
  55. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000015.html +22 -0
  56. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.html +167 -0
  57. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000016.html +18 -0
  58. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000017.html +20 -0
  59. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000018.html +20 -0
  60. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.html +167 -0
  61. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000019.html +20 -0
  62. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000020.html +24 -0
  63. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000021.html +22 -0
  64. data/vendor/escape/rdoc/files/escape_rb.html +136 -0
  65. data/vendor/escape/rdoc/files/install_rb.html +250 -0
  66. data/vendor/escape/rdoc/files/install_rb.src/M000001.html +23 -0
  67. data/vendor/escape/rdoc/files/install_rb.src/M000002.html +31 -0
  68. data/vendor/escape/rdoc/files/install_rb.src/M000003.html +27 -0
  69. data/vendor/escape/rdoc/files/install_rb.src/M000004.html +27 -0
  70. data/vendor/escape/rdoc/files/install_rb.src/M000005.html +21 -0
  71. data/vendor/escape/rdoc/files/install_rb.src/M000006.html +23 -0
  72. data/vendor/escape/rdoc/files/install_rb.src/M000007.html +21 -0
  73. data/vendor/escape/rdoc/files/test/test-escape_rb.html +109 -0
  74. data/vendor/escape/rdoc/fr_class_index.html +36 -0
  75. data/vendor/escape/rdoc/fr_file_index.html +29 -0
  76. data/vendor/escape/rdoc/fr_method_index.html +61 -0
  77. data/vendor/escape/rdoc/index.html +24 -0
  78. data/vendor/escape/rdoc/rdoc-style.css +208 -0
  79. data/vendor/escape/test/test-escape.rb +90 -0
  80. metadata +209 -0
data/.autotest ADDED
@@ -0,0 +1,11 @@
1
+ require 'autotest/redgreen'
2
+ Autotest.add_hook :initialize do |at|
3
+ # Ignore files in tmp/
4
+ at.add_exception %r%^\./tmp%
5
+
6
+ at.add_mapping(/plugins\/(.+).rb/) do |f, _|
7
+ at.files_matching(/.*spec.rb$/)
8
+ end
9
+
10
+ end
11
+
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ config.yml
2
+ tmp/*
3
+ var/*
4
+ .bundle
5
+ .rvmrc
6
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source :gemcutter
2
+
3
+ gem 'twitter-stream'
4
+ gem 'tinder', ">= 1.4.3"
5
+ gem 'mime-types'
6
+ gem 'activesupport'
7
+ gem 'logging'
8
+ gem 'i18n'
9
+
10
+ group :development do
11
+ gem 'rspec'
12
+ end
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
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ # run as cfbot-stop.sh BOT_ENV_NAME
4
+ for i in $(ps ajx | awk "/[b]ot ${1}/ {print \$2;}")
5
+ do
6
+ echo "killing pid $i"
7
+ kill -9 $i
8
+ done
@@ -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