wesabot 1.0.1

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