campfire-bot_alexchee 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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