wesabot 1.0.1

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 (55) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +20 -0
  4. data/README.md +182 -0
  5. data/Rakefile +13 -0
  6. data/bin/wesabot +111 -0
  7. data/config/wesabot.yml.sample +4 -0
  8. data/lib/campfire/bot.rb +53 -0
  9. data/lib/campfire/configuration.rb +65 -0
  10. data/lib/campfire/message.rb +102 -0
  11. data/lib/campfire/polling_bot/plugin.rb +162 -0
  12. data/lib/campfire/polling_bot/plugins/airbrake/.gitignore +1 -0
  13. data/lib/campfire/polling_bot/plugins/airbrake/airbrake/api.rb +48 -0
  14. data/lib/campfire/polling_bot/plugins/airbrake/airbrake/error.rb +51 -0
  15. data/lib/campfire/polling_bot/plugins/airbrake/airbrake_plugin.rb +63 -0
  16. data/lib/campfire/polling_bot/plugins/airbrake/airbrake_plugin.yml.sample +3 -0
  17. data/lib/campfire/polling_bot/plugins/bookmark/bookmark.rb +15 -0
  18. data/lib/campfire/polling_bot/plugins/bookmark/bookmark_plugin.rb +89 -0
  19. data/lib/campfire/polling_bot/plugins/debug/debug_plugin.rb +23 -0
  20. data/lib/campfire/polling_bot/plugins/deploy/deploy_plugin.rb +155 -0
  21. data/lib/campfire/polling_bot/plugins/deploy/deploy_plugin.yml.sample +9 -0
  22. data/lib/campfire/polling_bot/plugins/deploy/spec/deploy_plugin_spec.rb +153 -0
  23. data/lib/campfire/polling_bot/plugins/greeting/greeting_plugin.rb +146 -0
  24. data/lib/campfire/polling_bot/plugins/greeting/greeting_setting.rb +19 -0
  25. data/lib/campfire/polling_bot/plugins/greeting/spec/greeting_plugin_spec.rb +51 -0
  26. data/lib/campfire/polling_bot/plugins/greeting/spec/greeting_setting_spec.rb +61 -0
  27. data/lib/campfire/polling_bot/plugins/help/help_plugin.rb +53 -0
  28. data/lib/campfire/polling_bot/plugins/history/history_plugin.rb +34 -0
  29. data/lib/campfire/polling_bot/plugins/image_search/displayed_image.rb +8 -0
  30. data/lib/campfire/polling_bot/plugins/image_search/image_search_plugin.rb +106 -0
  31. data/lib/campfire/polling_bot/plugins/interjector/interjector_plugin.rb +58 -0
  32. data/lib/campfire/polling_bot/plugins/kibitz/kibitz_plugin.rb +90 -0
  33. data/lib/campfire/polling_bot/plugins/kibitz/spec/kibitz_plugin_spec.rb +56 -0
  34. data/lib/campfire/polling_bot/plugins/plugin.db +0 -0
  35. data/lib/campfire/polling_bot/plugins/reload/reload_plugin.rb +24 -0
  36. data/lib/campfire/polling_bot/plugins/remind_me/remind_me_plugin.rb +149 -0
  37. data/lib/campfire/polling_bot/plugins/remind_me/reminder.rb +8 -0
  38. data/lib/campfire/polling_bot/plugins/shared/message.rb +37 -0
  39. data/lib/campfire/polling_bot/plugins/shared/user.rb +32 -0
  40. data/lib/campfire/polling_bot/plugins/sms/sms_plugin.rb +88 -0
  41. data/lib/campfire/polling_bot/plugins/sms/sms_setting.rb +7 -0
  42. data/lib/campfire/polling_bot/plugins/time/time_plugin.rb +17 -0
  43. data/lib/campfire/polling_bot/plugins/tweet/tweet.rb +52 -0
  44. data/lib/campfire/polling_bot/plugins/tweet/tweet_plugin.rb +117 -0
  45. data/lib/campfire/polling_bot/plugins/tweet/tweet_plugin.yml.sample +4 -0
  46. data/lib/campfire/polling_bot/plugins/twitter_search/twitter_search_plugin.rb +58 -0
  47. data/lib/campfire/polling_bot.rb +125 -0
  48. data/lib/campfire/sample_plugin.rb +56 -0
  49. data/lib/campfire/version.rb +3 -0
  50. data/lib/wesabot.rb +3 -0
  51. data/spec/.gitignore +1 -0
  52. data/spec/polling_bot_spec.rb +23 -0
  53. data/spec/spec_helper.rb +190 -0
  54. data/wesabot.gemspec +55 -0
  55. metadata +336 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .bundle
2
+ *.gem
3
+ Gemfile.lock
4
+ pkg/*
5
+ config/*.yml
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://www.rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006-2008 Wesabe
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # Wesabot
2
+
3
+ Wesabot, the Campfire bot framework.
4
+
5
+ ## Description
6
+
7
+ Wesabot is a Campfire bot framework we've been using and developing at Wesabe
8
+ since not long after our inception. It started as a way to avoid parking tickets
9
+ near our office ("Wes, remind me in 2 hours to move my car"), and has evolved
10
+ into an essential work aid. When you enter the room, Wes greets you with a link
11
+ to the point in the transcript where you last left. You can also ask him to
12
+ bookmark points in the transcript, send an sms message (well, an email) to
13
+ someone, or even post a tweet, among other things. His functionality is easily
14
+ extendable via plugins.
15
+
16
+ To give Wes new powers, simply drop a plugin file in the plugins directory and
17
+ restart Wes (or, via the ReloadPlugin, he can be told to reload himself). See
18
+ `campfire/polling_bot/sample_plugin.rb` for more information, or just browse
19
+ the included plugins. Some of the plugins are somewhat Wesabe-specific (like
20
+ DeployPlugin, which lets us see what commits are on deck to be deployed), but
21
+ can be adapted or ignored as you see fit.
22
+
23
+ If any of your plugins need to use a database, just drop a Datamapper model in
24
+ the plugins/models directory and it will be automatically loaded.
25
+
26
+ ## Installation
27
+
28
+ ### Prerequisites
29
+
30
+ Wesabot can be installed as a gem, but [Nokogiri](http://nokogiri.org/) is
31
+ required, and it has rather complicated prerequisites that vary by platform, so
32
+ rather than reproducing that information here, please see
33
+ <http://nokogiri.org/tutorials/installing_nokogiri.html>.
34
+
35
+ You'll also need to install [SQLite3](http://www.sqlite.org/), and, at least
36
+ on Ubuntu, the sqlite3 development libraries
37
+ (`sudo apt-get install libsqlite3-dev`). If you have specific installation
38
+ instructions for other platforms, please feel free to send a pull request
39
+ with changes to this README.
40
+
41
+ ### Installing as a Gem
42
+
43
+ gem install wesabot
44
+
45
+ ### Installing from Git
46
+
47
+ Clone this repository, then `cd` to it and run `bundle install`.
48
+
49
+ ## Usage
50
+
51
+ The first thing you need to do is to create a Campfire user for your bot,
52
+ and make sure that user has access to whichever room(s) you would like the bot
53
+ to appear in. Log into Campfire as that user and get the API token (click on
54
+ "My info" in the upper right corner).
55
+
56
+ Wesabot has four required parameters:
57
+
58
+ * subdomain - your Campfire subdomain
59
+ * api_token - the Campfire API token
60
+ * room - the room in which you'd like the bot to appear
61
+ * datauri - the sqlite3 database uri/path
62
+
63
+ These parameters can be passed in either via a YAML config file, or on the
64
+ command-line. See `config/wesabot.yml.sample` for a sample configuration file.
65
+
66
+ Examples:
67
+
68
+ bin/wesabot -c config/wesabot.yml
69
+ bin/wesabot -d example -r Sandbox -t ecca8793813bd3e5720d9d562285db --database /path/to/database.db
70
+
71
+ Run `wesabot --help` for usage information.
72
+
73
+ **A few important things to note:**
74
+
75
+ * The SQLite database does not need to exist initially--wesabot will create one
76
+ at the path you specify.
77
+ * On some systems (Ubuntu, at least) you will need to use the `--ssl-no-verify`
78
+ (`-k`) option, at least until the next release of [Faraday](https://github.com/technoweenie/faraday),
79
+ which includes [a fix](https://github.com/technoweenie/faraday/commit/2b9b798d07b95b3a3348e95c513fe42f5e21c6ee)
80
+ for looking up SSL certs in the system store. If you run wesabot and get the
81
+ error:
82
+
83
+ /usr/lib/ruby/1.8/net/http.rb:1060:in `request': undefined method `closed?' for nil:NilClass (NoMethodError)
84
+
85
+ That is the cause. (Run it again in verbose mode to see the SSL error.)
86
+
87
+
88
+ Once Wes (or whatever you decide to name your bot) is running, you can see a list of available commands by entering into Campfire:
89
+
90
+ Wes, help
91
+
92
+ That list currently looks like:
93
+
94
+ AirbrakePlugin:
95
+ - resolve <error number>
96
+ mark an error as resolved
97
+ - unresolve <error number>
98
+ mark an error as unresolved
99
+
100
+ BookmarkPlugin:
101
+ - bookmark: <name>
102
+ bookmark the current location
103
+
104
+ DebugPlugin:
105
+ - <enable|disable> debugging
106
+ enable or disable debug mode
107
+
108
+ DeployPlugin:
109
+ - what's on deck for <project>?
110
+ shortlog of changes not yet deployed to production
111
+ - what's on deck for <project> staging?
112
+ shortlog of changes not yet deployed to staging
113
+
114
+ GreetingPlugin:
115
+ - (disable|turn off) greetings
116
+ don't say hi when you log in (you grump)
117
+ - (enable|turn on) greetings
118
+ say hi when you log in
119
+ - toggle greetings
120
+ disable greetings if enabled, enable if disabled. You know--toggle.
121
+ - catch me up|ketchup
122
+ gives you a link to the point in the transcript where you last logged out
123
+
124
+ HelpPlugin:
125
+ - help
126
+ this message
127
+
128
+ ImageSearchPlugin:
129
+ - (photo|image|picture) of <subject>
130
+ find a new picture of <subject>
131
+ - search (google|flickr) for a (photo|image|picture) of <subject>
132
+ search the stated service for a new picture of <subject>
133
+
134
+ ReloadPlugin:
135
+ - reload
136
+ update and reload Wes
137
+
138
+ RemindMePlugin:
139
+ - remind (me|<person>) [in] <time string> to <message>
140
+ set up a reminder
141
+ - remind (me|<person>) to <message> (in|on|at|next|this) <time string>
142
+ set up a reminder
143
+ - [list|show] [person]['s] reminders
144
+ display current reminders for yourself or person
145
+ - delete reminder <n>
146
+ delete your reminder #n
147
+
148
+ SMSPlugin:
149
+ - set my sms address to: <address>
150
+ set your sms address
151
+ - set <person>'s sms address to
152
+ set someone else's sms address
153
+ - (sms|text|txt) <person>: <message>
154
+ send an sms message
155
+ - list sms addresses
156
+ list all sms addresses
157
+
158
+ TimePlugin:
159
+ - time
160
+ say the current time
161
+
162
+ TweetPlugin:
163
+ - tweet: <message>
164
+ post <message> to the configured user's twitter account
165
+ - save tweet: <message>
166
+ save <message> for later
167
+ - show tweets
168
+ shows the queued tweets for the configured user's twitter account
169
+ - show next tweet
170
+ shows the oldest queued twitter message
171
+ - post next tweet
172
+ sends the oldest queued twitter message
173
+ - post tweet <n>
174
+ sends the <n>th tweet from the list
175
+ - delete tweet <n>
176
+ deletes the <n>th tweet from the list
177
+
178
+ TwitterSearchPlugin:
179
+ - what are (people|is everyone) saying about <subject>
180
+ search twitter for tweets on <subject>
181
+ - what's the word on <subject>
182
+ search twitter for tweets on <subject>
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec'
5
+ require 'rspec/core/rake_task'
6
+
7
+ desc "Run the specs for wesabot"
8
+ RSpec::Core::RakeTask.new do |t|
9
+ t.pattern = 'spec/**/*_spec.rb'
10
+ end
11
+
12
+ task :cruise => :spec
13
+ task :default => :spec
data/bin/wesabot ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+
4
+ # get the real path of this file so we can load the library
5
+ file = Pathname.new(__FILE__).realpath
6
+
7
+ # save the original invocation so we can restart ourselves later
8
+ INVOCATION = [file.to_s, *ARGV]
9
+
10
+ # chdir to this project's directory so bundler will be able to find
11
+ # the .bundle directory itself
12
+ oldpwd = Dir.pwd
13
+ Dir.chdir(File.expand_path('../..', file))
14
+
15
+ # load dependencies
16
+ $:.push File.expand_path("../../lib", file)
17
+ require 'rubygems'
18
+ require 'wesabot'
19
+ require 'optparse'
20
+ require 'openssl'
21
+
22
+ config = Campfire::Configuration.new
23
+ daemonize = false
24
+ pidfile = nil
25
+
26
+ # get configuration from ARGV
27
+ optparse = OptionParser.new do |opts|
28
+ opts.banner = "Usage: #{File.basename($0)} [options]"
29
+
30
+ opts.separator ""
31
+ opts.separator "Options:"
32
+
33
+ opts.on("-c", "--config FILE", "Configuration file") do |path|
34
+ config = Campfire::FileConfiguration.new(File.expand_path(path, oldpwd))
35
+ end
36
+
37
+ opts.separator "OR"
38
+
39
+ opts.on("-t", "--token TOKEN", "API token (required)") do |api_token|
40
+ config.api_token = api_token
41
+ end
42
+
43
+ opts.on("-d", "--subdomain SUBDOMAIN", "Campfire subdomain (required)") do |subdomain|
44
+ config.subdomain = subdomain
45
+ end
46
+
47
+ opts.on("-r", "--room ROOM", "Campfire room (required)") do |room|
48
+ config.room = room
49
+ end
50
+
51
+ opts.on("--database PATH", "The sqlite3 database to use (required)") do |path|
52
+ config.datauri = "sqlite3://#{Pathname(File.expand_path(path, oldpwd)).expand_path}"
53
+ end
54
+
55
+ opts.on("-D", "--daemonize", "Daemonize the bot (run in the background)") do
56
+ daemonize = true
57
+ end
58
+
59
+ opts.on("--pid-file PATH", "PID file to write the daemon's PID to") do |path|
60
+ pidfile = path
61
+ end
62
+
63
+ opts.on("-o", "--log-file PATH", "The file to log STDOUT and STDERR to") do |path|
64
+ config.logger = Logger.new(path)
65
+ end
66
+
67
+ opts.on("-k", "--ssl-no-verify", "Don't verify SSL certs") do
68
+ config.ssl_verify = false
69
+ end
70
+
71
+ opts.on("-v", "--verbose", "Be verbose") do
72
+ config.verbose = true
73
+ end
74
+
75
+ opts.on("--version", "Print version") do
76
+ puts Campfire::VERSION
77
+ exit
78
+ end
79
+
80
+ opts.on_tail("-h", "--help", "Show this message") do
81
+ puts opts
82
+ exit
83
+ end
84
+ end
85
+
86
+ # check for required options
87
+ begin
88
+ optparse.parse!
89
+ config.validate!
90
+
91
+ config.logger.level = config.verbose?? Logger::DEBUG : Logger::INFO
92
+
93
+ if daemonize
94
+ require 'daemons'
95
+ puts "Daemonizing..."
96
+ Daemons.daemonize
97
+ end
98
+
99
+ File.open(pidfile, 'w') {|f| f << $$.to_s } if pidfile
100
+ rescue Campfire::ConfigurationError, OptionParser::InvalidOption, OptionParser::MissingArgument => e
101
+ puts "#{File.basename($0)}: #{e}"
102
+ puts optparse
103
+ exit(1)
104
+ end
105
+
106
+ OpenSSL::debug = config.verbose?
107
+
108
+ bot = Campfire::PollingBot.new(config)
109
+ trap(:INT) { bot.leave; exit }
110
+ trap(:TERM) { bot.leave; exit }
111
+ bot.run
@@ -0,0 +1,4 @@
1
+ api_token: ecca8793813bd3e5720d9d562285db
2
+ subdomain: example
3
+ room: Development
4
+ datauri: sqlite3:///path/to/database
@@ -0,0 +1,53 @@
1
+ require "tinder"
2
+
3
+ module Campfire
4
+ class Bot
5
+ attr_accessor :config, :room, :name, :campfire, :debug
6
+
7
+ def initialize(config)
8
+ self.config = config
9
+ options = {:token => config.api_token, :ssl_verify => config.ssl_verify }
10
+ self.campfire = Tinder::Campfire.new(config.subdomain, options)
11
+ begin
12
+ self.name = campfire.me['name']
13
+ self.room = campfire.find_room_by_name(config.room) or
14
+ raise ConfigurationError, "Could not find a room named '#{config.room}'"
15
+ rescue Tinder::AuthenticationFailed => e
16
+ raise # maybe do some friendlier error handling later
17
+ end
18
+ room.join
19
+ say("hey guys")
20
+ end
21
+
22
+ def base_uri
23
+ campfire.connection.uri.to_s
24
+ end
25
+
26
+ # convenience method so I don't have to change all the old #say method to #speak
27
+ def say(*args)
28
+ room.speak(*args)
29
+ end
30
+
31
+ # pick something at random from an array of sayings
32
+ def say_random(sayings)
33
+ say(sayings[rand(sayings.size)])
34
+ end
35
+
36
+ # return a random person out of the list of users logged in to this room
37
+ def other_person(exclude = nil)
38
+ # don't choose the excluded person or ourself
39
+ options = room.users.reject{|u| u[:name] =~ /^(#{exclude}|#{self.name})/ }
40
+ # return the other person's first name, or nil if we didn't find one
41
+ options.any? ? options[rand(options.size)][:name].split(' ').first : nil
42
+ end
43
+
44
+ def logger
45
+ config.logger
46
+ end
47
+
48
+ # Proxy everything to the room.
49
+ def method_missing(m, *args)
50
+ room.send(m, *args)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,65 @@
1
+ require 'logger'
2
+
3
+ module Campfire
4
+ class ConfigurationError < RuntimeError; end
5
+
6
+ class Configuration
7
+ protected
8
+
9
+ def initialize(data={})
10
+ load data
11
+ end
12
+
13
+ def load(data)
14
+ data = data.inject({}) {|h,(k,v)| h[k.to_sym] = v; h }
15
+ self.api_token = data[:api_token]
16
+ self.subdomain = data[:subdomain]
17
+ self.room = data[:room]
18
+ self.verbose = data[:verbose] || false
19
+ self.datauri = data[:datauri]
20
+ self.logger = data[:logger] || data[:logfile] || Logger.new(STDOUT)
21
+ self.google_api_key = data[:google_api_key]
22
+ self.ssl_verify = data[:ssl_verify] != false
23
+ end
24
+
25
+ public
26
+
27
+ attr_accessor :api_token, :subdomain, :room, :verbose, :datauri, :logger,
28
+ :google_api_key, :ssl_verify
29
+
30
+ alias_method :verbose?, :verbose
31
+
32
+ def logger=(logger)
33
+ logger = Logger.new(logger) unless logger.is_a?(Logger)
34
+ @logger = logger
35
+ end
36
+
37
+ def validate!
38
+ api_token or raise ConfigurationError, 'no api token given'
39
+ subdomain or raise ConfigurationError, 'no subdomain given'
40
+ room or raise ConfigurationError, 'no room given'
41
+ datauri or raise ConfigurationError, 'no datauri given'
42
+ logger or raise ConfigurationError, 'no logger given'
43
+ end
44
+
45
+ def reload!
46
+ end
47
+ end
48
+
49
+ class FileConfiguration < Configuration
50
+ protected
51
+
52
+ attr_reader :path
53
+
54
+ def initialize(path)
55
+ @path = path
56
+ reload!
57
+ end
58
+
59
+ public
60
+
61
+ def reload!
62
+ load YAML.load_file(path)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,102 @@
1
+ # Classes to encapsulate the various campfire message types
2
+ require 'cgi'
3
+
4
+ module Campfire
5
+ # base message class. All messages have a message_id, timestamp, person (first name of the user generating
6
+ # the message), person_full_name, and body, which is the text of the message
7
+ class Message
8
+ attr_accessor :message_id, :timestamp, :user, :person, :person_full_name, :body, :type
9
+
10
+ def initialize(params)
11
+ self.message_id = params[:id]
12
+ self.timestamp = params[:created_at] || Time.now
13
+ self.body = params[:body]
14
+ self.type = params[:type].gsub(/(.*?)Message$/, '\1') if params[:type]
15
+ end
16
+
17
+ def person_full_name=(name)
18
+ @person_full_name = name
19
+ @person = @person_full_name.split(' ').first # just get first name
20
+ end
21
+
22
+ def user=(user)
23
+ self.person_full_name = user.name
24
+ @user = user
25
+ end
26
+ end
27
+
28
+ # TextMessage - normal user text message
29
+ # #command - if the message is addressed to the bot ("<bot name>, ..." or "..., <bot name>"), the
30
+ # the part of the body minus the bot name (and comma) is returned by #command
31
+ class TextMessage < Message
32
+ attr_accessor :command
33
+
34
+ def addressed_to_me?
35
+ not command.nil?
36
+ end
37
+ end
38
+
39
+ # a PasteMessage is sent when a paste block appears. #link contains the link to the full text of the
40
+ # pasted block (TODO: grab the full paste from the link and have it available)
41
+ class PasteMessage < Message
42
+ attr_accessor :link
43
+
44
+ def initialize(params)
45
+ super
46
+ # FIXME: link is no longer available as a param. If we want the link, we
47
+ # need to construct it from https://#{subdomain}.campfirenow.com/room/#{params[:room_id]}/paste/#{params[:id]}
48
+ # self.link = params[:link]
49
+ end
50
+ end
51
+
52
+ # an UploadMessage is sent when a user uploads a file. #link contains the link to the file
53
+ class UploadMessage < Message
54
+ attr_accessor :link
55
+
56
+ def initialize(params)
57
+ super
58
+ # FIXME: link is no longer available as a param. If we want the link, we
59
+ # need to construct it from https://#{subdomain}.campfirenow.com/room/#{params[:room_id]}/uploads/#{params[:id]}/#{params[:filename]} (?)
60
+ # self.link = params[:link]
61
+ end
62
+ end
63
+
64
+ # EnterMessage - sent when a user enters the room
65
+ class EnterMessage < Message; end
66
+
67
+ # LeaveMessage - sent when a user leaves the room
68
+ class LeaveMessage < Message; end
69
+
70
+ # KickMessage - sent when a user times out and is booted from the room
71
+ class KickMessage < Message; end
72
+
73
+ # LockMessage - sent when a user locks the room
74
+ class LockMessage < Message; end
75
+
76
+ # UnlockMessage - sent when the room is unlocked
77
+ class UnlockMessage < Message; end
78
+
79
+ # AllowGuestsMessage - sent when guest access is turned on
80
+ class AllowGuestsMessage < Message; end
81
+
82
+ # DisallowGuestsMessage - sent when guest access is turned off
83
+ class DisallowGuestsMessage < Message; end
84
+
85
+ # TopicChangeMessage - sent when the room's topic is changed
86
+ class TopicChangeMessage < Message; end
87
+
88
+ # TimestampMessage - sent when a timestamp is posted to the room
89
+ class TimestampMessage < Message; end
90
+
91
+ # AdvertisementMessage - ads
92
+ class AdvertisementMessage < Message; end
93
+
94
+ # SoundMessage - when a user plays a sound
95
+ class SoundMessage < Message; end
96
+
97
+ # ConferenceCreatedMessage - when a conference call is started
98
+ class ConferenceCreatedMessage < Message; end
99
+
100
+ # TweetMessage - when a tweet is posted
101
+ class TweetMessage < Message; end
102
+ end