ceiling_cat 0.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.
data/bin/ceiling_cat ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'ceiling_cat'
4
+
5
+ if ARGV[0] == "setup"
6
+ cc=`gem which ceiling_cat`
7
+ `mkdir ./plugins` unless File.exists?('./plugins')
8
+ if File.exists?(File.join(cc.split("/")[0..-3], "setup", "Rakefile")) && !File.exists?('Rakefile')
9
+ `cp #{File.join(cc.split("/")[0..-3], "setup", "Rakefile")} Rakefile`
10
+ puts "Rakefile created."
11
+ end
12
+ if File.exists?(File.join(cc.split("/")[0..-3], "setup", "Chatfile")) && !File.exists?('Chatfile')
13
+ `cp #{File.join(cc.split("/")[0..-3], "setup", "Chatfile")} Chatfile`
14
+ puts "Chatfile created."
15
+ end
16
+ puts "To create your own plugin, run `rake plugin:create name=[plugin_name]` to get started."
17
+ else
18
+ file = ARGV[0] || './Chatfile'
19
+
20
+ if File.exists?(file)
21
+ load file
22
+ CeilingCat::Setup.new.connect
23
+ else
24
+ puts "Couldn't find a Chatfile at #{file}! Create one at `./Chatfile`, provide a path to one, or run `ceiling_cat setup`"
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ # General
2
+ %w{version setup connection errors event user room plugins/base services/campfire storage/base storage/yaml}.each do |file|
3
+ require "ceiling_cat/#{file}"
4
+ end
@@ -0,0 +1,19 @@
1
+ module CeilingCat
2
+ class Connection
3
+ attr_accessor :config
4
+
5
+ def initialize(config)
6
+ @config=config
7
+ @config.storage ||= CeilingCat::Storage::Hash
8
+ end
9
+
10
+ def plugins
11
+ self.config.plugins
12
+ end
13
+
14
+ def storage
15
+ self.config.storage
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module CeilingCat
2
+ class Error < StandardError
3
+
4
+ def initialize(message)
5
+ super message
6
+ end
7
+ end
8
+
9
+ # Gem Specific Errors
10
+ class CeilingCatError < StandardError; end
11
+
12
+ class UnsupportedChatServiceError < CeilingCatError; end
13
+
14
+ class NotImplementedError < CeilingCatError; end
15
+
16
+ class ReloadException < CeilingCatError; end
17
+ end
@@ -0,0 +1,29 @@
1
+ module CeilingCat
2
+ class Event
3
+ attr_accessor :room, :body, :user, :type, :time
4
+
5
+ def initialize(room, body,user,opts={})
6
+ @room = room
7
+ @body = body.to_s.strip
8
+ @user = user
9
+ @type = opts[:type]
10
+ @time = opts[:time] || Time.now
11
+ end
12
+
13
+ def handle
14
+ @room.plugins.each do |plugin|
15
+ puts "running #{plugin}"
16
+ begin
17
+ response = plugin.new(self).handle
18
+ break if response.present?
19
+ rescue => e
20
+ @room.say("Whoops - there was a problem with #{plugin}: #{e}")
21
+ end
22
+ end
23
+ end
24
+
25
+ def type # assume that all messages are just text unless the specific room type overrides it.
26
+ :chat
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ module CeilingCat
2
+ module Plugin
3
+ class About < CeilingCat::Plugin::Base
4
+ def self.commands
5
+ [{:command => "plugins", :description => "List of installed plugins.", :method => "list_plugins"},
6
+ {:command => "commands", :description => "List of available commands.", :method => "list_commands", :public => true},
7
+ {:command => "users", :description => "List of registered in the room.", :method => "list_users", :public => true},
8
+ {:command => "guests", :description => "List of guests in the room.", :method => "list_guests", :public => true}]
9
+ end
10
+
11
+ def self.description
12
+ "About the currently running Ceiling Cat"
13
+ end
14
+
15
+ def self.name
16
+ "About"
17
+ end
18
+
19
+ def self.public?
20
+ true
21
+ end
22
+
23
+ def list_plugins
24
+ reply room.plugin_descriptions(user.is_registered?)
25
+ end
26
+
27
+ def list_commands
28
+ messages = room.available_commands(user.is_registered?) # Show all commands if the user is registered, otherwise only show private commands
29
+ messages << "Run commands with '![command]' or '#{room.me.name}: [command]'"
30
+ reply messages
31
+ end
32
+
33
+ def list_users
34
+ members = room.list_of_users_in_room(:type => "member")
35
+ reply "#{members} #{pluralize(members.size, "is a", "are")} #{pluralize(members.size, "registered user")}."
36
+ end
37
+
38
+ def list_guests
39
+ guests = room.list_of_users_in_room(:type => "guest")
40
+ reply "#{guests} #{pluralize(members.size, "is a", "are")} #{pluralize(members.size, "guest")}."
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,98 @@
1
+ module CeilingCat
2
+ module Plugin
3
+ class Base
4
+ attr_accessor :event
5
+
6
+ def initialize(event)
7
+ @event = event
8
+ end
9
+
10
+ def handle
11
+ if command = commands.find{|command| body =~ /^(!|#{room.me.name}:?\s*)#{command[:command]}/i}
12
+ begin
13
+ if command[:public] || user.is_registered?
14
+ self.send command[:method]
15
+ end
16
+ rescue => e
17
+ reply "There was an error: #{$!}"
18
+ raise e
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.name
24
+ self.to_s.gsub("CeilingCat::Plugin::","")
25
+ end
26
+
27
+ def self.description
28
+ "No description"
29
+ end
30
+
31
+ def self.public?
32
+ false #assume we don't want people to know a plugin is available.
33
+ end
34
+
35
+ def self.commands
36
+ []
37
+ end
38
+
39
+ def commands
40
+ self.class.commands || []
41
+ end
42
+
43
+ def event
44
+ @event
45
+ end
46
+
47
+ def room
48
+ event.room
49
+ end
50
+
51
+ def store
52
+ self.class.store
53
+ end
54
+
55
+ def self.store
56
+ CeilingCat::Setup.config.storage
57
+ end
58
+
59
+ def body
60
+ event.body
61
+ end
62
+
63
+ def user
64
+ event.user
65
+ end
66
+
67
+ def reply(message)
68
+ room.say(message)
69
+ end
70
+
71
+ def words
72
+ body.split
73
+ end
74
+
75
+ def body_without_command(command,text=body)
76
+ text.sub(/^!#{command}:?\s*/i,"").strip
77
+ end
78
+
79
+ def body_without_nick(text=body)
80
+ text.sub(/^#{room.me.name}:?\s*/i,'').strip
81
+ end
82
+
83
+ def body_without_nick_or_command(command,text=body)
84
+ body_without_command(command, body_without_nick(text).sub(/^#{command}/i,"!#{command}"))
85
+ end
86
+
87
+ def pluralize(n, singular, plural=nil)
88
+ if n == 1
89
+ "#{singular}"
90
+ elsif plural
91
+ "#{plural}"
92
+ else
93
+ "#{singular}s"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,34 @@
1
+ module CeilingCat
2
+ module Plugin
3
+ class Calc < CeilingCat::Plugin::Base
4
+ def self.commands
5
+ [{:command => "calculate", :description => "Performs basic math functions - '!calculate 7*5'", :method => "calculate", :public => true}]
6
+ end
7
+
8
+ def self.description
9
+ "A basic calculator, for all your mathin' needs!"
10
+ end
11
+
12
+ def self.name
13
+ "Calculator"
14
+ end
15
+
16
+ def self.public?
17
+ true
18
+ end
19
+
20
+ def calculate
21
+ begin
22
+ math = body.gsub(/[^\d+-\/*\(\)\.]/,"") # Removing anything but numbers, operators, and parens
23
+ if math.length > 0 && math =~ /^\d.+\d$/
24
+ reply "#{math} = #{eval math}"
25
+ else
26
+ reply "I don't think that's an equation. Want to try something else?"
27
+ end
28
+ rescue => e
29
+ raise e
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,76 @@
1
+ module CeilingCat
2
+ module Plugin
3
+ class CallAndResponse < CeilingCat::Plugin::Base
4
+ def handle
5
+ if event.type == :chat
6
+ super
7
+ if match = self.class.list.find{|car| body =~ Regexp.new(Regexp.escape(car[:call]),true) }
8
+ reply match[:url]
9
+ return nil
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ def self.commands
16
+ [{:command => "list calls", :description => "List current calls and their responses", :method => "list"},
17
+ {:command => "add call", :description => "Add an response to display when a phrase is said, split by a | - '!add call I see what you did there... | You caught me!'", :method => "add"},
18
+ {:command => "remove call", :description => "Remove an call and response by call '!remove call I see what you did there...'", :method => "remove"}]
19
+ end
20
+
21
+ def self.description
22
+ "Watches for certain phrases and inserts a relevant image"
23
+ end
24
+
25
+ def self.name
26
+ "Call and Response"
27
+ end
28
+
29
+ def self.public?
30
+ false
31
+ end
32
+
33
+ def list
34
+ messages = []
35
+ store["call_and_responses"] ||= []
36
+ if store["call_and_responses"].size > 0
37
+ messages << "Current Calls and Responses"
38
+ messages += store["call_and_responses"].collect{|car| "-- #{car[:call]} | #{car[:response]}"}
39
+ else
40
+ messages << "There are no call and respones set up yet! You should add one with '!add call When someone says this | I say this'"
41
+ end
42
+ reply messages
43
+ end
44
+
45
+ def add
46
+ message = body_without_nick_or_command("add call").split
47
+ call, response = message.split("|")
48
+ if self.class.add(:call => call.strip,:response => response.strip)
49
+ reply "Call and Response added."
50
+ else
51
+ reply "Unable to add that call. A call and a response are required, split by a | - 'When someones says this | I say this'"
52
+ end
53
+ end
54
+
55
+ def remove
56
+ self.class.remove(body_without_nick_or_command("remove image phrase"))
57
+ reply "Call removed."
58
+ end
59
+
60
+ def self.list
61
+ store["call_and_responses"] ||= []
62
+ end
63
+
64
+ def self.add(opts={})
65
+ return false unless opts[:call] && opts[:response]
66
+ store["call_and_responses"] ||= []
67
+ store["call_and_responses"] = (store["call_and_responses"] + [{:call => opts[:call], :response => opts[:response]}]).uniq
68
+ end
69
+
70
+ def self.remove(phrase)
71
+ store["call_and_responses"] ||= []
72
+ store["call_and_responses"] = store["call_and_responses"].reject{|car| car[:call].downcase == call.downcase }
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,46 @@
1
+ module CeilingCat
2
+ module Plugin
3
+ class CampfireAccountMonitor < CeilingCat::Plugin::Base
4
+ # See lib/ceiling_cat/plugins/base.rb for the methods available by default.
5
+ # Plugins are run in the order they are listed in the Chatfile.
6
+ # When a plugin returns anything other than nil the plugin execution chain is halted.
7
+ # If you want your plugin to do something but let the remaining plugins execute, return nil at the end of your method.
8
+
9
+ # handle manages a plugin's entire interaction with an event.
10
+ # If you only want to execute commands - "![command]" - leave handle alone (or remove it and define commands below)
11
+ def handle
12
+ if room.connection.config.service.downcase == "campfire" && event.type == :entrance
13
+ user_count = room.connection.total_user_count
14
+ max_users = room.connection.config.max_users || 100
15
+ room.plugin("notifo").new(@event).deliver("#{user_count} of #{max_users} max connections to Campfire.") if room.plugin_installed?("notifo") && user_count > max_users-2
16
+ end
17
+ super
18
+ end
19
+
20
+ # If you want the plugin to run when certain text is sent use commands instead of handle.
21
+ # Ceiling Cat will watch for "![command]" or "[name]: [command" and execute the method for that command.
22
+ def self.commands
23
+ [{:command => "total users", :description => "Gets the total number of current users in chat", :method => "total_users", :public => false}]
24
+ end
25
+
26
+ def self.description
27
+ "For monitoring Campfire connection limits"
28
+ end
29
+
30
+ def self.name
31
+ "Campfire account monitor"
32
+ end
33
+
34
+ def self.public?
35
+ false
36
+ end
37
+
38
+ def total_users
39
+ if room.connection.config.service.downcase == "campfire"
40
+ reply "#{room.connection.total_user_count} connections currently used"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,99 @@
1
+ module CeilingCat
2
+ class NotADateError < CeilingCatError; end
3
+
4
+ module Plugin
5
+ class Days < CeilingCat::Plugin::Base
6
+ def self.commands
7
+ [{:command => "today", :description => "Find out if there's anything special about today.", :method => "about", :public => true},
8
+ {:command => "add holiday", :description => "Add a holiday - '!add holiday 1/19/2011'", :method => "add_to_holidays"}]
9
+ end
10
+
11
+ def about(date=Date.today)
12
+ begin
13
+ if self.class.is_a_holiday?(date)
14
+ reply "Today is a holiday!"
15
+ elsif self.class.is_a_weekend?(date)
16
+ reply "Today is a weekend!"
17
+ else
18
+ reply "Just a normal day today."
19
+ end
20
+ rescue NotADateError
21
+ reply "Sorry, that's not a valid date."
22
+ end
23
+ end
24
+
25
+ def self.description
26
+ "Holidays and times you shouldn't expect to see us in chat."
27
+ end
28
+
29
+ def add_to_holidays
30
+ date = body_without_command("add holiday")
31
+ if date.empty?
32
+ reply "You need to provide a date to add to the holiday list, like 'add holiday 1/19/2011'"
33
+ else
34
+ begin
35
+ self.class.add_to_holidays(body_without_command("add holiday"))
36
+ reply "#{date.to_s} has been added to the holiday list."
37
+ rescue NotADateError
38
+ reply "Sorry, that's not a valid date."
39
+ end
40
+ end
41
+ end
42
+
43
+ def self.is_a_weekend?(date=Date.today)
44
+ if is_a_date?(date)
45
+ date = Date.parse(date.to_s)
46
+ date.wday == 0 || date.wday == 6
47
+ else
48
+ raise NotADateError
49
+ end
50
+ end
51
+
52
+ def self.holidays
53
+ store["holidays"] ||= []
54
+ end
55
+
56
+ def self.add_to_holidays(days)
57
+ dates = Array(days).collect do |day|
58
+ if is_a_date?(day)
59
+ Date.parse(day.to_s)
60
+ else
61
+ raise NotADateError
62
+ end
63
+ end
64
+
65
+ store["holidays"] ||= []
66
+ store["holidays"] = (store["holidays"] + dates).uniq
67
+ end
68
+
69
+ def self.remove_from_holidays(days)
70
+ dates = Array(days).collect do |day|
71
+ if is_a_date?(day)
72
+ Date.parse(day.to_s)
73
+ else
74
+ raise NotADateError
75
+ end
76
+ end
77
+
78
+ store["holidays"] ||= []
79
+ store["holidays"] = store["holidays"] - dates
80
+ end
81
+
82
+ def self.is_a_holiday?(date=Date.today)
83
+ if is_a_date?(date)
84
+ store["holidays"].include? Date.parse(date.to_s)
85
+ else
86
+ raise NotADateError
87
+ end
88
+ end
89
+
90
+ def self.is_a_date?(date_string)
91
+ begin
92
+ return true if Date.parse(date_string.to_s)
93
+ rescue ArgumentError => e
94
+ return false
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,56 @@
1
+ module CeilingCat
2
+ module Plugin
3
+ class General < CeilingCat::Plugin::Base
4
+ # See lib/ceiling_cat/plugins/base.rb for the methods available by default.
5
+ # Plugins are run in the order they are listed in the Chatfile.
6
+ # When a plugin returns anything other than nil the plugin execution chain is halted.
7
+ # If you want your plugin to do something but let the remaining plugins execute, return nil at the end of your method.
8
+
9
+ # handle manages a plugin's entire interaction with an event.
10
+ # If you only want to execute commands - "![command]" - leave handle alone (or remove it and define commands below)
11
+ def handle
12
+ super
13
+ end
14
+
15
+ # If you want the plugin to run when certain text is sent use commands instead of handle.
16
+ # Ceiling Cat will watch for "![command]" or "[name]: [command" and execute the method for that command.
17
+ def self.commands
18
+ [{:command => "debugger", :description => "Load the debugger", :method => "run_debug", :public => false},
19
+ {:command => "enable debug", :description => "Enable debuggers", :method => "enable_debug", :public => false},
20
+ {:command => "disable debug", :description => "Disable debuggers", :method => "disable_debug", :public => false}]
21
+ end
22
+
23
+ def self.description
24
+ "General tasks for development"
25
+ end
26
+
27
+ def self.name
28
+ "General"
29
+ end
30
+
31
+ def self.public?
32
+ false
33
+ end
34
+
35
+ def run_debug
36
+ if room.debug_mode?
37
+ reply "Opening debugger in terminal"
38
+ debugger
39
+ else
40
+ reply "Debugging is disabled - !enable debug to activate it."
41
+ end
42
+ end
43
+
44
+ def enable_debug
45
+ store["debug_mode"] ||= "true"
46
+ reply "debugging enabled"
47
+ end
48
+
49
+ def disable_debug
50
+ store["debug_mode"] ||= "false"
51
+ reply "debugging disabled"
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,30 @@
1
+ module CeilingCat
2
+ module Plugin
3
+ class Greeter < CeilingCat::Plugin::Base
4
+ def handle
5
+ message = []
6
+
7
+ if event.type == :entrance
8
+ members = room.users_in_room(:type => "member")
9
+ guests = room.users_in_room(:type => "guest")
10
+ if user.is_guest?
11
+ messages << "Hey #{user.name}!"
12
+ elsif user.is_registered?
13
+ messages << "Nice to see you again #{user.name}"
14
+ end
15
+ reply message unless message.empty?
16
+ elsif event.type == :chat
17
+ super
18
+ end
19
+ end
20
+
21
+ def self.name
22
+ "Greeter"
23
+ end
24
+
25
+ def self.description
26
+ "Welcomes visitors and memebrs to chat."
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,69 @@
1
+ require 'httparty'
2
+
3
+ module CeilingCat
4
+ module Plugin
5
+ class Notifo < CeilingCat::Plugin::Base
6
+ def self.commands
7
+ [{:command => "notifo", :description => "Send a message with Notifo - '!notifo Hey, get in here!'.", :method => "deliver"},
8
+ {:command => "add notifo users", :description => "Add users to get Notifos - '!add notifo users username1 username2'.", :method => "add_users"},
9
+ {:command => "remove notifo users", :description => "Add users to get Notifos - '!remove notifo users username1 username2'.", :method => "remove_users"},
10
+ {:command => "list notifo users", :description => "List users who get Notifos - '!list notifo users'.", :method => "list_users"}]
11
+ end
12
+
13
+ def deliver(message=nil)
14
+ message ||= body_without_nick_or_command("notifo")
15
+ if active?
16
+ Array(store["notifo_users"]).each do |user|
17
+ HTTParty.post("https://api.notifo.com/v1/send_notification",
18
+ :body => { :to => user, :msg => message },
19
+ :basic_auth => {:username => store["notifo_credentials"][:username], :password => store["notifo_credentials"][:api_secret]})
20
+ end
21
+ else
22
+ debugger
23
+ puts "here"
24
+ end
25
+ end
26
+
27
+ def active?
28
+ store["notifo_credentials"] && store["notifo_credentials"][:username].present? && store["notifo_credentials"][:api_secret].present? && Array(store["notifo_users"]).size > 0
29
+ end
30
+
31
+ def self.name
32
+ "Notifo"
33
+ end
34
+
35
+ def self.description
36
+ "Sends messages to users with Notifo"
37
+ end
38
+
39
+ def self.add_users(users)
40
+ store["notifo_users"] ||= []
41
+ store["notifo_users"] = (Array(store["notifo_users"]) + Array(users)).uniq
42
+ end
43
+
44
+ def self.set_credentials(username, api_secret)
45
+ store["notifo_credentials"] = {:username => username, :api_secret => api_secret}
46
+ end
47
+
48
+ def add_users
49
+ users = body_without_nick_or_command("add notifo users").split(" ")
50
+ self.class.add_users(users)
51
+ reply "#{users.join(" ")} added to notifo alerts."
52
+ end
53
+
54
+ def remove_users
55
+ users = body_without_nick_or_command("remove notifo users").split(" ")
56
+ store["notifo_users"] ||= []
57
+ store["notifo_users"] = store["notifo_users"] - users
58
+ reply "#{users.join(" ")} removed from notifo alerts."
59
+ end
60
+
61
+ def list_users
62
+ messages = []
63
+ messages << "Notifo Users"
64
+ Array(store["notifo_users"]).each{|user| messages << "-- #{user}"}
65
+ reply messages
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,81 @@
1
+ module CeilingCat
2
+ class Room
3
+ attr_accessor :connection, :room, :users_in_room, :me
4
+
5
+ def initialize(opts={})
6
+ @connection = opts[:connection]
7
+ end
8
+
9
+ def watch
10
+ raise NotImplementedError, "Implement in chat service files!"
11
+ end
12
+
13
+ def say
14
+ raise NotImplementedError, "Implement in chat service files!"
15
+ end
16
+
17
+ def me
18
+ raise NotImplementedError, "Implement in chat service files or define config.nickname!" unless config.nickname
19
+ @me ||= CeilingCat::User.new(config.nickname)
20
+ end
21
+
22
+ def users_in_room(type=nil)
23
+ raise NotImplementedError, "Implement in chat service files!"
24
+ end
25
+
26
+ def plugins
27
+ @connection.plugins
28
+ end
29
+
30
+ def config
31
+ @connection.config
32
+ end
33
+
34
+ def plugin_installed?(name)
35
+ !plugin(name).nil?
36
+ end
37
+
38
+ def plugin(name)
39
+ plugins.find{|plugin| plugin.name.downcase == name.to_s.downcase || plugin.class == name}
40
+ end
41
+
42
+ def store
43
+ @connection.storage
44
+ end
45
+
46
+ def debug_mode?
47
+ !store["debug_mode"].nil? && store["debug_mode"] == "true"
48
+ end
49
+
50
+ def plugin_descriptions(show_private=false)
51
+ messages = []
52
+ plugins.each do |plugin|
53
+ messages << "#{plugin.name}: #{plugin.description}" if show_private || plugin.public?
54
+ end
55
+ messages
56
+ end
57
+
58
+ def available_commands(show_private=false)
59
+ messages = []
60
+ plugins.each do |plugin|
61
+ if !plugin.commands.empty? && (plugin.public? || show_private)
62
+ messages << "Commands for #{plugin.name}"
63
+ plugin.commands.each do |command|
64
+ messages << "-- #{command[:command]}: #{command[:description]}" if show_private || command[:public]
65
+ end
66
+ end
67
+ end
68
+ messages
69
+ end
70
+
71
+ def list_of_users_in_room(type=nil)
72
+ users = users_in_room(type)
73
+ last_user = users.pop
74
+ if users.size > 0
75
+ return users.collect{|user| user["name"] }.join(", ") + " and #{ last_user["name"]}"
76
+ else
77
+ return last_user["name"]
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ %w{connection room event}.each do |file|
2
+ require "ceiling_cat/services/campfire/#{file}"
3
+ end
@@ -0,0 +1,24 @@
1
+ require 'tinder'
2
+
3
+ module CeilingCat
4
+ module Campfire
5
+ class Connection < CeilingCat::Connection
6
+ def initialize(config)
7
+ @config=config
8
+ @config.ssl ||= "true"
9
+ end
10
+
11
+ def campfire
12
+ @campfire = Tinder::Campfire.new(self.config.username, :token => self.config.token, :ssl => self.config.ssl)
13
+ end
14
+
15
+ def total_user_count
16
+ users = 0
17
+ @campfire.rooms.each do |room|
18
+ users += room.users.size
19
+ end
20
+ users
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module CeilingCat
2
+ module Campfire
3
+ class Event < CeilingCat::Event
4
+
5
+ def type
6
+ case @type
7
+ when "EnterMessage"
8
+ :entrance
9
+ when "TextMessage"
10
+ :chat
11
+ when "LeaveMessage", "KickMessage"
12
+ :exit
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,110 @@
1
+ module CeilingCat
2
+ module Campfire
3
+ class Room < CeilingCat::Room
4
+
5
+ def initialize(opts={})
6
+ super
7
+ @campfire_room = @connection.campfire.find_room_by_name(opts[:room_name])
8
+ end
9
+
10
+ def watch
11
+ puts "Watching room..."
12
+ setup_interrupts
13
+ begin
14
+ loop do
15
+ begin
16
+ Timeout::timeout(90) do
17
+ @campfire_room.listen do |event|
18
+ begin
19
+ if event[:type] != "TimestampMessage"
20
+ user = CeilingCat::User.new(event[:user][:name], :id => event[:user][:id], :role => event[:user][:type])
21
+
22
+ unless is_me?(user) # Otherwise CC will talk to itself
23
+ event = CeilingCat::Campfire::Event.new(self,event[:body], user, :type => event[:type])
24
+ event.handle
25
+ users_in_room(:reload => true) if event.type != :chat # If someone comes or goes, reload the list.
26
+ end
27
+ end
28
+ rescue => e
29
+ say "An error occurred with Campfire: #{e}" if debug_mode?
30
+ raise e
31
+ end
32
+ end
33
+ end
34
+ rescue Timeout::Error
35
+ puts "timeout! trying again..."
36
+ retry
37
+ end
38
+ end
39
+ rescue Faraday::Error::ParsingError
40
+ puts "Error parsing response. Campfire may be down."
41
+ break
42
+ rescue ReloadException => e
43
+ retry
44
+ rescue NoMethodError => e
45
+ puts "No Method Error"
46
+ e.backtrace.each do |line|
47
+ puts "Backtrace: #{line}"
48
+ end
49
+ retry
50
+ rescue StandardError => e
51
+ puts e.class
52
+ debugger if debug_mode?
53
+ e.backtrace.each do |line|
54
+ puts "Backtrace: #{line}"
55
+ end
56
+ retry
57
+ end
58
+ end
59
+
60
+ def is_me?(user)
61
+ user.id == me.id
62
+ end
63
+
64
+ def me
65
+ @me ||= CeilingCat::User.new(@connection.campfire.me[:name], :id => @connection.campfire.me[:id], :role => @connection.campfire.me[:type])
66
+ end
67
+
68
+ def users_in_room(opts={})
69
+ if opts[:reload] || @users_in_room.nil?
70
+ puts "Requesting user list"
71
+ @users_in_room = @campfire_room.users
72
+ end
73
+
74
+ @users_in_room.find_all do |user|
75
+ unless is_me?(CeilingCat::User.new(user[:name], :id => user[:id], :role => user[:type]))
76
+ if opts[:type].present?
77
+ if user[:type].to_s.downcase == opts[:type].downcase
78
+ CeilingCat::User.new(user[:name], :id => user[:id], :role => user[:type])
79
+ end
80
+ else
81
+ CeilingCat::User.new(user[:name], :id => user[:id], :role => user[:type])
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def say(something_to_say)
88
+ Array(something_to_say).each do |line|
89
+ @campfire_room.speak(line)
90
+ end
91
+ end
92
+
93
+ def setup_interrupts
94
+ trap('INT') do
95
+ puts "Leaving room...."
96
+ @campfire_room.leave if @campfire_room
97
+ exit 1
98
+ end
99
+
100
+ trap('USR1') do
101
+ puts "Leaving room"
102
+ @campfire_room.leave if @campfire_room
103
+ # logger.info "Reloading config"
104
+ # config(true)
105
+ # raise ReloadException.new("Rejoin room please, ceiling cat")
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,47 @@
1
+ require 'ostruct'
2
+
3
+ module CeilingCat
4
+ class Setup
5
+
6
+ attr_accessor :config
7
+
8
+ class << self
9
+ # Class-level config. This is set by the +configure+ class method,
10
+ # and is used if no configuration is passed to the +initialize+
11
+ # method.
12
+ attr_accessor :config
13
+ end
14
+
15
+ # Configures the connection at the class level. When the +ceiling_cat+ bin
16
+ # file is loaded, it evals the file referenced by the first
17
+ # command-line parameter. This file can configure the connection
18
+ # instance later created by +robut+ by setting parameters in the
19
+ # CeilingCat::Connection.configure block.
20
+ def self.configure
21
+ self.config = OpenStruct.new
22
+ yield config
23
+ end
24
+
25
+ # Sets the instance config to +config+, converting it into an
26
+ # OpenStruct if necessary.
27
+ def config=(config)
28
+ @config = config.kind_of?(Hash) ? OpenStruct.new(config) : config
29
+ end
30
+
31
+ def initialize(_config = nil)
32
+ self.config = _config || self.class.config
33
+ end
34
+
35
+ def connect
36
+ case self.config.service.downcase
37
+ when 'campfire'
38
+ connection = CeilingCat::Campfire::Connection.new(self.config)
39
+ room = CeilingCat::Campfire::Room.new(:connection => connection, :room_name => self.config.room)
40
+ room.watch
41
+ else
42
+ raise CeilingCat::UnsupportedChatServiceError.new("#{self.config.service} is not a supported chat service.")
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,18 @@
1
+ module CeilingCat
2
+ module Storage
3
+ class Base
4
+ class << self
5
+
6
+ # Sets the key +k+ to the value +v+ in the current storage system
7
+ def []=(k,v)
8
+ raise NotImplementedError, "Implement in storage type file!"
9
+ end
10
+
11
+ # Returns the value at the key +k+.
12
+ def [](k)
13
+ raise NotImplementedError, "Implement in storage type file!"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,49 @@
1
+ require 'yaml'
2
+
3
+ module CeilingCat
4
+ module Storage
5
+ class Yaml < CeilingCat::Storage::Base
6
+ class << self
7
+ attr_reader :file
8
+
9
+ # Sets the path to the file this store will persist to, and forces
10
+ # a reload of all of the data.
11
+ def file=(f)
12
+ @file = f
13
+ @internal = nil # force reload
14
+ @file
15
+ end
16
+
17
+ # Sets the key +k+ to the value +v+
18
+ def []=(k, v)
19
+ internal[k] = v
20
+ persist!
21
+ v
22
+ end
23
+
24
+ # Returns the value at the key +k+.
25
+ def [](k)
26
+ internal[k]
27
+ end
28
+
29
+ private
30
+
31
+ # The internal in-memory representation of the yaml file
32
+ def internal
33
+ @internal ||= begin
34
+ YAML.load_file(file) rescue {}
35
+ end
36
+ end
37
+
38
+ # Persists the data in this store to disk. Throws an exception if
39
+ # we don't have a file set.
40
+ def persist!
41
+ raise "CeilingCat::Storage::Yaml.file must be set" unless file
42
+ f = File.open(file, "w")
43
+ f.puts internal.to_yaml
44
+ f.close
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ module CeilingCat
2
+ class User
3
+ attr_accessor :name, :role, :id
4
+
5
+ def initialize(name, opts={})
6
+ @name = name
7
+ @id = opts[:id]
8
+ @role = opts[:role]
9
+ end
10
+
11
+ def to_s
12
+ short_name
13
+ end
14
+
15
+ def short_name
16
+ @name.to_s.split.compact.first
17
+ end
18
+
19
+ def is_registered?
20
+ @role.to_s.downcase == 'member'
21
+ end
22
+
23
+ def is_guest?
24
+ @role.to_s.downcase == 'guest' || @type.to_s.nil?
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module CeilingCat
2
+ VERSION = "0.0.1"
3
+ end
data/setup/Chatfile ADDED
@@ -0,0 +1,20 @@
1
+ # Require your plugins here
2
+ require 'ceiling_cat/plugins/about'
3
+ require 'ceiling_cat/plugins/calc'
4
+ require 'ceiling_cat/plugins/greeter'
5
+
6
+ CeilingCat::Setup.configure do |config|
7
+ config.service = 'campfire'
8
+ config.username = 'username'
9
+ config.token = '12345abcde'
10
+ config.room = 'Test Room'
11
+ config.ssl = true
12
+
13
+ config.plugins = [CeilingCat::Plugin::About,
14
+ CeilingCat::Plugin::Greeter,
15
+ CeilingCat::Plugin::Calc]
16
+
17
+ # Some plugins require storage
18
+ CeilingCat::Storage::Yaml.file = "ceilingcat.yml"
19
+ config.storage = CeilingCat::Storage::Yaml
20
+ end
data/setup/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ namespace :plugin do
2
+ desc "Create a new plugin"
3
+ task :create do
4
+ raise "You need to set a name! `rake plugin:create name=[plugin_name]`" unless ENV["name"]
5
+ name = ENV["name"].downcase
6
+ file = File.join("plugins", "#{name}.rb")
7
+
8
+ unless File.exists? file
9
+ contents = plugin_base(name)
10
+ File.open(file, 'w') {|f| f.write(contents) }
11
+ puts "Plugin '#{name}' created at #{file}."
12
+ puts "Make sure you require it in your Chatfile and add it to your config.plugins so it runs."
13
+ else
14
+ puts "A plugin named '#{name}' already exists. Try another name."
15
+ end
16
+ end
17
+ end
18
+
19
+ def plugin_base(name)
20
+ %Q{module CeilingCat
21
+ module Plugin
22
+ class #{name.camelize} < CeilingCat::Plugin::Base
23
+ # See lib/ceiling_cat/plugins/base.rb for the methods available by default.
24
+ # Plugins are run in the order they are listed in the Chatfile.
25
+ # When a plugin returns anything other than nil the plugin execution chain is halted.
26
+ # If you want your plugin to do something but let the remaining plugins execute, return nil at the end of your method.
27
+
28
+ # handle manages a plugin's entire interaction with an event.
29
+ # If you only want to execute commands - "![command]" - leave handle alone (or remove it and define commands below)
30
+ def handle
31
+ super
32
+ end
33
+
34
+ # If you want the plugin to run when certain text is sent use commands instead of handle.
35
+ # Ceiling Cat will watch for "![command]" or "[name]: [command" and execute the method for that command.
36
+ def self.commands
37
+ [{:command => "#{name}", :description => "Does something", :method => "#{name}", :public => false}]
38
+ end
39
+
40
+ def self.description
41
+ "A plugin called #{name.capitalize}"
42
+ end
43
+
44
+ def self.name
45
+ "#{name.gsub("_"," ").capitalize}"
46
+ end
47
+
48
+ def self.public?
49
+ false
50
+ end
51
+
52
+ def #{name}
53
+ reply "You've created the '#{name.capitalize}' plugin. Now make it do something awesome!"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ }
59
+ end
60
+
61
+ class String
62
+ def camelize
63
+ self.split("_").each{|z|z.capitalize!}.join("")
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ceiling_cat
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Chris Warren
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-18 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: tinder
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - "="
27
+ - !ruby/object:Gem::Version
28
+ hash: 15
29
+ segments:
30
+ - 1
31
+ - 4
32
+ - 4
33
+ version: 1.4.4
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: httparty
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - "="
43
+ - !ruby/object:Gem::Version
44
+ hash: 19
45
+ segments:
46
+ - 0
47
+ - 7
48
+ - 8
49
+ version: 0.7.8
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: crack
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - "="
59
+ - !ruby/object:Gem::Version
60
+ hash: 11
61
+ segments:
62
+ - 0
63
+ - 1
64
+ - 8
65
+ version: 0.1.8
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ description: Ceiling Cat is watching you chat. A Campfire chat bot!
69
+ email:
70
+ - chris@zencoder.com
71
+ executables:
72
+ - ceiling_cat
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - lib/ceiling_cat.rb
79
+ - lib/ceiling_cat/setup.rb
80
+ - lib/ceiling_cat/version.rb
81
+ - lib/ceiling_cat/connection.rb
82
+ - lib/ceiling_cat/errors.rb
83
+ - lib/ceiling_cat/event.rb
84
+ - lib/ceiling_cat/user.rb
85
+ - lib/ceiling_cat/room.rb
86
+ - lib/ceiling_cat/storage/base.rb
87
+ - setup/Chatfile
88
+ - setup/Rakefile
89
+ - lib/ceiling_cat/plugins/base.rb
90
+ - lib/ceiling_cat/plugins/about.rb
91
+ - lib/ceiling_cat/plugins/calc.rb
92
+ - lib/ceiling_cat/plugins/call_and_response.rb
93
+ - lib/ceiling_cat/plugins/campfire_account_monitor.rb
94
+ - lib/ceiling_cat/plugins/days.rb
95
+ - lib/ceiling_cat/plugins/general.rb
96
+ - lib/ceiling_cat/plugins/greeter.rb
97
+ - lib/ceiling_cat/plugins/notifo.rb
98
+ - lib/ceiling_cat/storage/yaml.rb
99
+ - lib/ceiling_cat/services/campfire.rb
100
+ - lib/ceiling_cat/services/campfire/connection.rb
101
+ - lib/ceiling_cat/services/campfire/event.rb
102
+ - lib/ceiling_cat/services/campfire/room.rb
103
+ - bin/ceiling_cat
104
+ homepage: http://zencoder.com
105
+ licenses: []
106
+
107
+ post_install_message: " ********************************************************************************\n Run `ceiling_cat setup` to create a Chatfile and a Rakefile - everything you\n need to start watching your chats and making ceiling_cat do your bidding!\n \n Run `rake plugin:create name=plugin_name` to generate a new plugin.\n\n Update your Chatfile with your credentials and you'll be ready to go!\n ********************************************************************************\n"
108
+ rdoc_options: []
109
+
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.8
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Ceiling Cat is watching you chat. A Campfire chat bot.
137
+ test_files: []
138
+