messed 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/Gemfile +8 -0
- data/README.rdoc +22 -0
- data/Rakefile +76 -0
- data/VERSION +1 -0
- data/application_spec/Rakefile +12 -0
- data/application_spec/app/runner.rb +3 -0
- data/application_spec/bin/runner +18 -0
- data/application_spec/bin/status +36 -0
- data/application_spec/bin/web +29 -0
- data/application_spec/config/environment.rb +8 -0
- data/application_spec/config/environments/development.rb +0 -0
- data/application_spec/log/development.log +6 -0
- data/application_spec/spec/application_spec.rb +12 -0
- data/application_spec/spec/spec.opts +7 -0
- data/application_spec/spec/spec_helper.rb +42 -0
- data/bin/messed +9 -0
- data/lib/messed/booter.rb +114 -0
- data/lib/messed/configuration.rb +94 -0
- data/lib/messed/controller/helper.rb +8 -0
- data/lib/messed/controller/processing.rb +22 -0
- data/lib/messed/controller/respond.rb +48 -0
- data/lib/messed/controller.rb +17 -0
- data/lib/messed/em_runner.rb +50 -0
- data/lib/messed/interface/adapter/sms.rb +4 -0
- data/lib/messed/interface/adapter/twitter_consumer.rb +35 -0
- data/lib/messed/interface/adapter/twitter_search.rb +111 -0
- data/lib/messed/interface/adapter/twitter_sender.rb +44 -0
- data/lib/messed/interface/adapter/twitter_streaming.rb +50 -0
- data/lib/messed/interface/adapter.rb +68 -0
- data/lib/messed/interface/runner.rb +20 -0
- data/lib/messed/interface.rb +74 -0
- data/lib/messed/logger.rb +50 -0
- data/lib/messed/matcher.rb +50 -0
- data/lib/messed/message/twitter.rb +23 -0
- data/lib/messed/message.rb +35 -0
- data/lib/messed/queue/beanstalk.rb +56 -0
- data/lib/messed/queue.rb +17 -0
- data/lib/messed/session/memcache.rb +40 -0
- data/lib/messed/session.rb +7 -0
- data/lib/messed/tasks/console.rb +70 -0
- data/lib/messed/tasks/generation.rb +12 -0
- data/lib/messed/tasks/runner.rb +50 -0
- data/lib/messed/tasks/status.rb +37 -0
- data/lib/messed/tasks.rb +9 -0
- data/lib/messed/util/remote_status.rb +45 -0
- data/lib/messed.rb +235 -0
- data/patterns/messed/Pattern +24 -0
- data/patterns/messed/Rakefile +12 -0
- data/patterns/messed/app/runner.rb +5 -0
- data/patterns/messed/bin/console +3 -0
- data/patterns/messed/bin/runner +3 -0
- data/patterns/messed/bin/status +3 -0
- data/patterns/messed/bin/web +13 -0
- data/patterns/messed/config/environment.rb +15 -0
- data/patterns/messed/config/environments/development.rb +2 -0
- data/patterns/messed/config/environments/production.rb +0 -0
- data/patterns/messed/config/environments/test.rb +0 -0
- data/patterns/messed/spec/application_spec.rb +12 -0
- data/patterns/messed/spec/spec.opts +7 -0
- data/patterns/messed/spec/spec_helper.rb +44 -0
- data/spec/adapter/applications/twitter_search/app/application.rb +3 -0
- data/spec/adapter/applications/twitter_search/app/runner.rb +7 -0
- data/spec/adapter/applications/twitter_search/bin/incoming +89 -0
- data/spec/adapter/applications/twitter_search/bin/runner +21 -0
- data/spec/adapter/applications/twitter_search/bin/status +36 -0
- data/spec/adapter/applications/twitter_search/config/environment.rb +11 -0
- data/spec/adapter/applications/twitter_search/config/environments/development.rb +0 -0
- data/spec/adapter/applications/twitter_search/log/development.log +1006 -0
- data/spec/adapter/applications/twitter_sender/app/application.rb +0 -0
- data/spec/adapter/applications/twitter_sender/app/runner.rb +0 -0
- data/spec/adapter/applications/twitter_sender/bin/incoming +89 -0
- data/spec/adapter/applications/twitter_sender/bin/runner +21 -0
- data/spec/adapter/applications/twitter_sender/bin/status +36 -0
- data/spec/adapter/applications/twitter_sender/config/environment.rb +11 -0
- data/spec/adapter/applications/twitter_sender/config/environments/development.rb +0 -0
- data/spec/adapter/applications/twitter_sender/log/development.log +146 -0
- data/spec/adapter/http/direct_message +0 -0
- data/spec/adapter/http/twitter_search +20 -0
- data/spec/adapter/http/update +0 -0
- data/spec/adapter/twitter_search_spec.rb +36 -0
- data/spec/adapter/twitter_sender_spec.rb +64 -0
- data/spec/booter_spec.rb +7 -0
- data/spec/fixtures/booter/app/application.rb +3 -0
- data/spec/fixtures/booter/app/runner.rb +7 -0
- data/spec/fixtures/booter/bin/incoming +89 -0
- data/spec/fixtures/booter/bin/runner +21 -0
- data/spec/fixtures/booter/bin/status +36 -0
- data/spec/fixtures/booter/config/environment.rb +15 -0
- data/spec/fixtures/booter/config/environments/development.rb +0 -0
- data/spec/fixtures/booter/log/development.log +11 -0
- data/spec/message_spec.rb +52 -0
- data/spec/session_spec.rb +34 -0
- data/spec/spec.opts +7 -0
- data/spec/spec_helper.rb +8 -0
- data/test/app/runner.rb +5 -0
- data/test/config/environment.rb +15 -0
- data/test/config/environments/development.rb +2 -0
- data/test/config/environments/production.rb +0 -0
- data/test/config/environments/test.rb +0 -0
- data/test/spec/application_spec.rb +12 -0
- data/test/spec/spec_helper.rb +44 -0
- metadata +291 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class Messed
|
|
2
|
+
class Interface
|
|
3
|
+
class Adapter
|
|
4
|
+
class TwitterConsumer < Adapter
|
|
5
|
+
|
|
6
|
+
attr_accessor :started_at, :packets_processed, :errors, :last_error, :last_ok, :last_status
|
|
7
|
+
|
|
8
|
+
def init
|
|
9
|
+
@started_at = Time.new
|
|
10
|
+
@packets_processed = 0
|
|
11
|
+
@errors = 0
|
|
12
|
+
@last_error = nil
|
|
13
|
+
@last_ok = nil
|
|
14
|
+
@last_status = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def type
|
|
18
|
+
:twitter
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def status
|
|
22
|
+
{
|
|
23
|
+
:started_at => started_at,
|
|
24
|
+
:packets_processed => packets_processed,
|
|
25
|
+
:errors => errors,
|
|
26
|
+
:last_error => last_error,
|
|
27
|
+
:last_ok => last_ok,
|
|
28
|
+
:last_status => last_status
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'rack'
|
|
3
|
+
|
|
4
|
+
class Messed
|
|
5
|
+
class Interface
|
|
6
|
+
class Adapter
|
|
7
|
+
class TwitterSearch < TwitterConsumer
|
|
8
|
+
|
|
9
|
+
attr_reader :packets_processed
|
|
10
|
+
|
|
11
|
+
def build_query
|
|
12
|
+
Rack::Utils.build_query(interface.configuration.options[:fetch][:query])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def start
|
|
16
|
+
@ids ||= default_ids
|
|
17
|
+
# do work.
|
|
18
|
+
begin
|
|
19
|
+
query = build_query
|
|
20
|
+
logger.debug "Twitter Search -> #{query.inspect}"
|
|
21
|
+
http = EventMachine::HttpRequest.new("http://#{interface.configuration.options[:fetch][:host]}/#{interface.configuration.options[:fetch][:path]}").
|
|
22
|
+
get(:query => query, :timeout => 30)
|
|
23
|
+
http.callback {
|
|
24
|
+
self.last_status = http.response_header.status
|
|
25
|
+
case http.response_header.status
|
|
26
|
+
when 200
|
|
27
|
+
self.last_ok = Time.new
|
|
28
|
+
data = JSON.parse(http.response)
|
|
29
|
+
interface.configuration.options[:fetch][:query][:since_id] = data['max_id'] if interface.configuration.options[:fetch][:query]
|
|
30
|
+
@test_set = Set.new(@ids)
|
|
31
|
+
data['results'].each do |result|
|
|
32
|
+
result_to_message(result)
|
|
33
|
+
end
|
|
34
|
+
trim_ids
|
|
35
|
+
end
|
|
36
|
+
EM.add_timer(interface.configuration.options[:interval]) do
|
|
37
|
+
start
|
|
38
|
+
end
|
|
39
|
+
}
|
|
40
|
+
http.errback {
|
|
41
|
+
self.errors += 1
|
|
42
|
+
self.last_error = Time.new
|
|
43
|
+
EM.add_timer(interface.configuration.options[:interval]) do
|
|
44
|
+
start
|
|
45
|
+
end
|
|
46
|
+
}
|
|
47
|
+
rescue RuntimeError
|
|
48
|
+
EM.add_timer(interface.configuration.options[:interval]) do
|
|
49
|
+
start
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def id_retention_count
|
|
55
|
+
interface.configuration.options[:id_retention_size] || 500
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def trim_ids
|
|
59
|
+
while @ids.size > id_retention_count
|
|
60
|
+
@ids.shift
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def store_ids?
|
|
65
|
+
id_retention_file
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def write_ids
|
|
69
|
+
File.open(id_retention_file, 'w') { |f| f << @ids.to_json }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def read_ids
|
|
73
|
+
@ids = JSON.parse(File.read(id_retention_file))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def default_ids
|
|
77
|
+
store_ids? ? read_ids : []
|
|
78
|
+
rescue
|
|
79
|
+
logger.error "Twitter Search: Unable to load ids from #{id_retention_file.inspect}"
|
|
80
|
+
[]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def id_retention_file
|
|
84
|
+
interface.configuration.options[:id_retention_tmp_file]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def result_to_message(result)
|
|
88
|
+
unless @test_set.include?(result['id'])
|
|
89
|
+
message = Message::Twitter.new do |m|
|
|
90
|
+
m.body = result['text']
|
|
91
|
+
m.from = result['from_user']
|
|
92
|
+
m.to = result['to_user_id']
|
|
93
|
+
m.created_at = Time.rfc2822(result['created_at'])
|
|
94
|
+
m.profile_image_url = result['profile_image_url']
|
|
95
|
+
m.id = result['id']
|
|
96
|
+
m.geo = result['geo']
|
|
97
|
+
m.from_user_id = result['from_user_id']
|
|
98
|
+
m.iso_language_code = result['iso_language_code']
|
|
99
|
+
m.source = result['source']
|
|
100
|
+
end
|
|
101
|
+
@ids << message.id
|
|
102
|
+
@packets_processed += 1
|
|
103
|
+
interface.application.incoming << message
|
|
104
|
+
logger.debug "Twitter Search: Adding message #{message.id}: #{message.body} to incoming queue"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'twitter'
|
|
2
|
+
|
|
3
|
+
class Messed
|
|
4
|
+
class Interface
|
|
5
|
+
class Adapter
|
|
6
|
+
class TwitterSender < Adapter
|
|
7
|
+
|
|
8
|
+
def message_class
|
|
9
|
+
Messed::Message::Twitter
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def start
|
|
13
|
+
jack = EM::Beanstalk.new
|
|
14
|
+
jack.watch(interface.application.outgoing.tube) do
|
|
15
|
+
jack.use(interface.application.outgoing.tube) do
|
|
16
|
+
jack.each_job do |job|
|
|
17
|
+
process_outgoing(job, message_class.from_json(job.body))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def process_outgoing(job, message)
|
|
24
|
+
if message.private?
|
|
25
|
+
req = EventMachine::HttpRequest.new("http://twitter.com/direct_messages/new.json")
|
|
26
|
+
data = {:body => {:user => message.to_user_id, :text => message.body}, :timeout => 30, :head => {'authorization' => [interface.configuration.options[:username], interface.configuration.options[:password]]}}
|
|
27
|
+
data[:body][:in_reply_to] = message.in_reply_to if message.in_reply_to
|
|
28
|
+
http = req.post(data)
|
|
29
|
+
http.callback {
|
|
30
|
+
job.delete
|
|
31
|
+
}
|
|
32
|
+
else
|
|
33
|
+
http = EventMachine::HttpRequest.new("http://twitter.com/statuses/update.json").
|
|
34
|
+
post(:body => {:status => message.body}, :timeout => 30)
|
|
35
|
+
http.callback {
|
|
36
|
+
job.delete
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'twitter/json_stream'
|
|
2
|
+
|
|
3
|
+
class Messed
|
|
4
|
+
class Interface
|
|
5
|
+
class Adapter
|
|
6
|
+
class TwitterStreaming < TwitterConsumer
|
|
7
|
+
|
|
8
|
+
include Logger::LoggingModule
|
|
9
|
+
|
|
10
|
+
def start
|
|
11
|
+
stream = Twitter::JSONStream.connect(
|
|
12
|
+
:path => "/1/statuses/#{interface.configuration.options['streaming']['type']}.json?track=%23classicmoviequotes",
|
|
13
|
+
:auth => "#{interface.configuration.options['username']}:#{interface.configuration.options['password']}"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
stream.each_item do |item|
|
|
17
|
+
self.last_ok = Time.new
|
|
18
|
+
result = JSON.parse(item)
|
|
19
|
+
message = Message::Twitter.new do |m|
|
|
20
|
+
m.body = result['text']
|
|
21
|
+
m.from = result['from_user']
|
|
22
|
+
m.to = result['to_user_id']
|
|
23
|
+
m.created_at = Time.rfc2822(result['created_at'])
|
|
24
|
+
m.profile_image_url = result['profile_image_url']
|
|
25
|
+
m.id = result['id']
|
|
26
|
+
m.geo = result['geo']
|
|
27
|
+
m.from_user_id = result['from_user_id']
|
|
28
|
+
m.iso_language_code = result['iso_language_code']
|
|
29
|
+
m.source = result['source']
|
|
30
|
+
end
|
|
31
|
+
self.packets_processed += 1
|
|
32
|
+
interface.application.incoming << message
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
stream.on_error do |message|
|
|
36
|
+
logger.error "message #{message.inspect}"
|
|
37
|
+
self.errors += 1
|
|
38
|
+
self.last_error = message
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
stream.on_max_reconnects do |timeout, retries|
|
|
42
|
+
logger.error "message -> #{timeout} #{retries}"
|
|
43
|
+
# Something is wrong on your side. Send yourself an email.
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
class Messed
|
|
4
|
+
class Interface
|
|
5
|
+
class Adapter
|
|
6
|
+
|
|
7
|
+
include Logger::LoggingModule
|
|
8
|
+
|
|
9
|
+
autoload :TwitterSearch, File.join('messed', 'interface', 'adapter', 'twitter_search')
|
|
10
|
+
autoload :TwitterSender, File.join('messed', 'interface', 'adapter', 'twitter_sender')
|
|
11
|
+
autoload :TwitterStreaming, File.join('messed', 'interface', 'adapter', 'twitter_streaming')
|
|
12
|
+
|
|
13
|
+
# abstract class for twitter consumption
|
|
14
|
+
autoload :TwitterConsumer, File.join(File.dirname(__FILE__), 'adapter', 'twitter_consumer')
|
|
15
|
+
|
|
16
|
+
Registry = {}
|
|
17
|
+
def self.register_for_name(name, class_name)
|
|
18
|
+
Registry[name] = class_name
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
register_for_name :twitter_search, 'Messed::Interface::Adapter::TwitterSearch'
|
|
22
|
+
register_for_name :twitter_sender, 'Messed::Interface::Adapter::TwitterSender'
|
|
23
|
+
register_for_name :twitter_streaming, 'Messed::Interface::Adapter::TwitterStreaming'
|
|
24
|
+
|
|
25
|
+
def self.for_name(name)
|
|
26
|
+
class_name = Registry[name]
|
|
27
|
+
class_name ?
|
|
28
|
+
class_name.constantize :
|
|
29
|
+
raise("No adapter for #{name}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :interface
|
|
33
|
+
|
|
34
|
+
def initialize(interface)
|
|
35
|
+
@interface = interface
|
|
36
|
+
init
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def save_state(state)
|
|
40
|
+
File.open(state_file, 'w') {|f| f << state.to_json }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load_state
|
|
44
|
+
JSON.parse(File.read(state_file))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def state_file
|
|
48
|
+
"/tmp/#{name}.state"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# any post initialization goes here
|
|
52
|
+
def init
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def type
|
|
56
|
+
raise "method type must be defined"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def start(detach)
|
|
60
|
+
raise "method start must be defined"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def send(message)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'thin'
|
|
2
|
+
|
|
3
|
+
class Messed
|
|
4
|
+
class Interface
|
|
5
|
+
class Runner
|
|
6
|
+
|
|
7
|
+
attr_reader :runnable, :options
|
|
8
|
+
|
|
9
|
+
def initialize(runnable, options)
|
|
10
|
+
@runnable = runnable
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start
|
|
15
|
+
runnable.start(options[:detach])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class Messed
|
|
2
|
+
class Interface
|
|
3
|
+
|
|
4
|
+
include Logger::LoggingModule
|
|
5
|
+
|
|
6
|
+
autoload :Runner, File.join('messed', 'interface', 'runner')
|
|
7
|
+
autoload :Adapter, File.join('messed', 'interface', 'adapter')
|
|
8
|
+
|
|
9
|
+
def self.interface_from_configuration(booter, name, configuration)
|
|
10
|
+
interface = Interface.new
|
|
11
|
+
interface.send(:name=, name)
|
|
12
|
+
interface.send(:configuration=, configuration)
|
|
13
|
+
interface.send(:adapter=, Adapter.for_name(configuration.adapter).new(interface))
|
|
14
|
+
interface.send(:booter=, booter)
|
|
15
|
+
interface
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_s
|
|
19
|
+
"#{Array(configuration['mode']) * ', '} ->> #{name}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def status
|
|
23
|
+
{
|
|
24
|
+
'adapter' => adapter.status,
|
|
25
|
+
'configuration' => configuration,
|
|
26
|
+
'name' => name
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
attr_reader :booter, :configuration, :adapter, :name, :started_at
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@started_at = Time.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def application
|
|
37
|
+
booter.application
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def status_host
|
|
41
|
+
configuration.status_address || '0.0.0.0'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def status_port
|
|
45
|
+
configuration.status_port || 11190
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def stop
|
|
49
|
+
Process.kill("INT", booter.read_pid_file(configuration.pid_file))
|
|
50
|
+
exit(0)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def start
|
|
54
|
+
begin
|
|
55
|
+
EM.start_server(status_host, status_port, EMRunner::StatusHandler) do |c|
|
|
56
|
+
c.interface = self
|
|
57
|
+
end
|
|
58
|
+
logger.info "Status handler for #{self.class} started on #{status_host}:#{status_port}"
|
|
59
|
+
rescue RuntimeError => e
|
|
60
|
+
if e.message =~ /no acceptor/
|
|
61
|
+
logger.error "Unable to start status handler"
|
|
62
|
+
else
|
|
63
|
+
raise e
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
booter.write_pid_file(configuration.pid_file)
|
|
68
|
+
adapter.start
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
protected
|
|
72
|
+
attr_writer :booter, :configuration, :adapter, :name
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
class Messed
|
|
4
|
+
class Logger
|
|
5
|
+
module LoggingModule
|
|
6
|
+
def self.included(cls)
|
|
7
|
+
cls.class_eval(<<-HERE_DOC, __FILE__, __LINE__)
|
|
8
|
+
|
|
9
|
+
def self.logger
|
|
10
|
+
Messed::Logger.instance.logger
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.logger=(logger)
|
|
14
|
+
Messed::Logger.instance.logger = logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
HERE_DOC
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def logger
|
|
21
|
+
Messed::Logger.instance.logger
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def logger=(logger)
|
|
25
|
+
Messed::Logger.instance.logger = logger
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
attr_accessor :logger
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
setup_logger
|
|
34
|
+
end
|
|
35
|
+
private :initialize
|
|
36
|
+
|
|
37
|
+
def self.instance
|
|
38
|
+
@@instance
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def setup_logger(logger = ::Logger.new(STDOUT), log_level = :debug)
|
|
42
|
+
@logger = logger
|
|
43
|
+
@logger.level = ::Logger.const_get(log_level.to_s.upcase.to_sym) if log_level
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@@instance = self.new
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class Messed
|
|
2
|
+
class Matcher
|
|
3
|
+
|
|
4
|
+
attr_accessor :destination
|
|
5
|
+
|
|
6
|
+
def stop_processing?
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Conditional < Matcher
|
|
11
|
+
|
|
12
|
+
attr_reader :matches
|
|
13
|
+
|
|
14
|
+
def initialize(body_matcher, other_matchers = nil)
|
|
15
|
+
@body_matcher = body_matcher
|
|
16
|
+
@other_matchers = other_matchers
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def match?(message)
|
|
20
|
+
matches = true
|
|
21
|
+
self.matches = nil
|
|
22
|
+
if @body_matcher
|
|
23
|
+
matches &&= @body_matcher === message.body
|
|
24
|
+
self.matches = Regexp.last_match if matches && @body_matcher.is_a?(Regexp)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if @other_matchers
|
|
28
|
+
keys = @other_matchers.keys
|
|
29
|
+
unless matches
|
|
30
|
+
key = keys.pop
|
|
31
|
+
matches &&= @other_matchers[key] === message.send(key)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
matches
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
attr_writer :matches
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Always < Matcher
|
|
42
|
+
|
|
43
|
+
def match?(message)
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Messed
|
|
2
|
+
class Message
|
|
3
|
+
class Twitter < Message
|
|
4
|
+
|
|
5
|
+
attr_accessor :created_at, :profile_image_url, :id, :geo, :from_user_id, :iso_language_code, :source, :private, :to_user_id
|
|
6
|
+
hash_accessor :profile_image_url, :id, :geo, :from_user_id, :iso_language_code, :source, :to_user_id, :private
|
|
7
|
+
hash_convert :created_at => Hashify::Convert::Time
|
|
8
|
+
|
|
9
|
+
def initialize(body = nil)
|
|
10
|
+
super(body)
|
|
11
|
+
self.private = false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def unique_id
|
|
15
|
+
from_user_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
alias_method :private?, :private
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class Messed
|
|
2
|
+
|
|
3
|
+
class Message
|
|
4
|
+
|
|
5
|
+
autoload :Twitter, File.join('messed', 'message', 'twitter')
|
|
6
|
+
|
|
7
|
+
include Hashify
|
|
8
|
+
include Hashify::Json
|
|
9
|
+
|
|
10
|
+
attr_accessor :body
|
|
11
|
+
attr_accessor :from
|
|
12
|
+
attr_accessor :to
|
|
13
|
+
attr_accessor :enqueued_at
|
|
14
|
+
attr_accessor :in_reply_to
|
|
15
|
+
|
|
16
|
+
hash_accessor :body, :from, :to
|
|
17
|
+
hash_convert :enqueued_at => Hashify::Convert::Time
|
|
18
|
+
hash_convert :in_reply_to => [proc{|x| x && x.to_hash}, proc{|x, parent| x && parent.class.from_hash(x)}]
|
|
19
|
+
|
|
20
|
+
def self.class_for_type(type)
|
|
21
|
+
case type
|
|
22
|
+
when :twitter
|
|
23
|
+
Twitter
|
|
24
|
+
else
|
|
25
|
+
raise type
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(body = nil)
|
|
30
|
+
self.body = body
|
|
31
|
+
yield self if block_given?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class Messed
|
|
2
|
+
class Queue
|
|
3
|
+
class Beanstalk < Queue
|
|
4
|
+
|
|
5
|
+
include Logger::LoggingModule
|
|
6
|
+
|
|
7
|
+
attr_accessor :application
|
|
8
|
+
attr_reader :tube
|
|
9
|
+
|
|
10
|
+
def initialize(tube, host = '127.0.0.1', port = 11300)
|
|
11
|
+
@tube, @host, @port = tube, host, port
|
|
12
|
+
@beanstalk = ::Beanstalk::Pool.new(Array("#{host}:#{port}"))
|
|
13
|
+
@beanstalk.use(tube)
|
|
14
|
+
@beanstalk.watch(tube)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def status
|
|
18
|
+
@beanstalk.stats_tube(tube)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def take(block = true)
|
|
22
|
+
job = beanstalk.reserve
|
|
23
|
+
begin
|
|
24
|
+
message = application.message_class.from_json(job.body)
|
|
25
|
+
rescue JSON::ParserError
|
|
26
|
+
logger.error "malformed message #{job.body}"
|
|
27
|
+
job.delete
|
|
28
|
+
else
|
|
29
|
+
yield message
|
|
30
|
+
job.delete
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def <<(message)
|
|
35
|
+
beanstalk.put message.to_json
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def jobs_available?
|
|
39
|
+
not jobs_available.zero?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def drain!
|
|
43
|
+
while jobs_available?
|
|
44
|
+
beanstalk.reserve.delete
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def jobs_available
|
|
49
|
+
beanstalk.stats_tube(tube)['current-jobs-ready']
|
|
50
|
+
end
|
|
51
|
+
protected
|
|
52
|
+
attr_reader :beanstalk
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/messed/queue.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'memcached'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
|
|
5
|
+
class Messed
|
|
6
|
+
class Session
|
|
7
|
+
class Memcache < Session
|
|
8
|
+
|
|
9
|
+
def initialize(config = "127.0.0.1:11211")
|
|
10
|
+
@connection = Memcached.new(config)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def reset!(id)
|
|
14
|
+
connection.delete(id)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with(id)
|
|
18
|
+
data = data(id)
|
|
19
|
+
begin
|
|
20
|
+
yield data
|
|
21
|
+
ensure
|
|
22
|
+
connection.set(key(id), data.to_json)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
attr_reader :connection
|
|
28
|
+
|
|
29
|
+
def data(id)
|
|
30
|
+
JSON.parse(connection.get(key(id))).strhash
|
|
31
|
+
rescue Memcached::NotFound
|
|
32
|
+
StrHash.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def key(id)
|
|
36
|
+
Digest::MD5.hexdigest(id.to_s)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|