ceiling_cat 0.0.1

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