akane 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: da68e17edf6170994c230591963faaa2f127ce0a
4
+ data.tar.gz: 112e4fd65c01fbd1bc45c5bf35fab09824482e42
5
+ SHA512:
6
+ metadata.gz: 4029d0d629134c6b7e27bafdcfc72747ef7ae104b97a4c303b2175278384c298b6b993169442f2fabe76aa57c322f5438e0f88b1c2e657127da78fb273a86833
7
+ data.tar.gz: 60e2d7f0b8387798df35547b334e1e8b9dc40300f3c6a05455cbaa0d4f86dff0d201654477e741b1fb346dcf137d6fa18e2fab08daa2b662e9551e4315b75766
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ akane.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in akane.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Shota Fukumori (sora_h)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,27 @@
1
+ # Akane
2
+
3
+ Log your timeline
4
+
5
+ ## Requirements
6
+
7
+ - Ruby 2.0.0 or later
8
+
9
+ ## Installation
10
+
11
+ $ gem install akane
12
+
13
+ ## Usage
14
+
15
+ TBD
16
+
17
+ ## Todo
18
+
19
+ * REST API fallbacking
20
+
21
+ ## Contributing
22
+
23
+ 1. Fork it
24
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
25
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
26
+ 4. Push to the branch (`git push origin my-new-feature`)
27
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'akane/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "akane"
8
+ spec.version = Akane::VERSION
9
+ spec.authors = ["Shota Fukumori (sora_h)"]
10
+ spec.email = ["her@sorah.jp"]
11
+ spec.description = %q{Log the timeline}
12
+ spec.summary = %q{Log your timeline to something}
13
+ spec.homepage = "https://github.com/sorah/akane"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "tweetstream", "~> 2.6.0"
22
+ spec.add_dependency "elasticsearch", "~> 0.4.1"
23
+
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", "~> 2.14.1"
27
+ spec.add_development_dependency "simplecov"
28
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'akane'
4
+ require 'akane/cli'
5
+
6
+ exit Akane::CLI.run(ARGV)
@@ -0,0 +1,5 @@
1
+ require "akane/version"
2
+
3
+ module Akane
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,83 @@
1
+ require 'akane/manager'
2
+ require 'akane/config'
3
+
4
+ module Akane
5
+ class CLI
6
+ class << self
7
+ def run(*args)
8
+ self.new(*args).run
9
+ end
10
+ end
11
+
12
+ def initialize(args)
13
+ @args = args
14
+ @options = nil
15
+ end
16
+
17
+ def start
18
+ config.logger.info 'Starting...'
19
+ manager = Akane::Manager.new(config)
20
+ manager.run
21
+ end
22
+
23
+ def help
24
+ puts <<-EOH
25
+ Usage:
26
+ akane COMMAND [ARGS]
27
+
28
+ Common commands:
29
+
30
+ akane start - start akari
31
+
32
+ Common options:
33
+
34
+ -c, --config=FILE - Specify config file name to use
35
+
36
+ EOH
37
+ 0
38
+ end
39
+
40
+ def run
41
+ if @args.include?('--help')
42
+ @command = :help
43
+ else
44
+ @command = (@args.shift || :help).to_sym
45
+ end
46
+
47
+ result = if self.respond_to?(@command)
48
+ self.__send__(@command)
49
+ else
50
+ self.help
51
+ end
52
+
53
+ result.kind_of?(Numeric) ? result : 0
54
+ end
55
+
56
+ private
57
+
58
+ def config
59
+ @config ||= Akane::Config.new(options[:config])
60
+ end
61
+
62
+ def option_parser
63
+ @option_parser ||= OptionParser.new.tap do |opt|
64
+ opt.on_tail('--help', 'Show this message') { help; exit 0 }
65
+ opt.on_tail('-c CONF', '--config=CONF', 'Specify configuration file (default: ./akane.yml)') do |conf|
66
+ @options[:config] = conf
67
+ end
68
+ end
69
+ end
70
+
71
+ def parse_options
72
+ return @options if @options
73
+ @options = {config: './akane.yml'}
74
+ yield option_parser if block_given?
75
+ option_parser.parse!(@args)
76
+ end
77
+
78
+ def options
79
+ parse_options unless @options
80
+ @options
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+ require 'logger'
3
+
4
+ module Akane
5
+ class Config
6
+ def initialize(file_or_hash)
7
+ @hash = case file_or_hash
8
+ when String
9
+ YAML.load_file(file_or_hash)
10
+ when Hash
11
+ file_or_hash
12
+ else
13
+ raise ArgumentError, 'file_or_hash is not Hash or String'
14
+ end
15
+ end
16
+
17
+ def [](k)
18
+ @hash[k.to_s]
19
+ end
20
+
21
+ def logger
22
+ Logger.new(@hash["log"] || $stdout)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,97 @@
1
+ require 'eventmachine'
2
+ require 'akane/config'
3
+ require 'akane/recorder'
4
+ require 'akane/receivers/stream'
5
+
6
+ module Akane
7
+ class Manager
8
+ def initialize(config)
9
+ @config = config
10
+ @logger = config.logger
11
+ end
12
+
13
+ def prepare
14
+ @logger.info 'Preparing'
15
+ @receivers = @config["accounts"].map do |name, credential|
16
+ Akane::Receivers::Stream.new(
17
+ consumer: {token: @config["consumer"]["token"], secret: @config["consumer"]["secret"]},
18
+ account: {token: credential["token"], secret: credential["secret"]},
19
+ logger: @config.logger
20
+ ).tap do |receiver|
21
+ @logger.info "Preparing... receiver - #{receiver.class}"
22
+ receiver.on_tweet( &(method(:on_tweet).to_proc.curry[name]))
23
+ receiver.on_message(&(method(:on_message).to_proc.curry[name]))
24
+ receiver.on_event( &(method(:on_event).to_proc.curry[name]))
25
+ receiver.on_delete( &(method(:on_delete).to_proc.curry[name]))
26
+ end
27
+ end
28
+
29
+ @storages = @config["storages"].flat_map do |definition|
30
+ case definition
31
+ when Hash
32
+ definition.map do |kind, config|
33
+ [kind, config]
34
+ end
35
+ when String
36
+ [[definition, {}]]
37
+ end
38
+ end.map do |kind, config|
39
+ @logger.info "Preparing... storgae - #{kind}"
40
+ require "akane/storages/#{kind}"
41
+ Akane::Storages.const_get(kind.gsub(/(?:\A|_)(.)/) { $1.upcase }).new(
42
+ config: config,
43
+ logger: @config.logger
44
+ )
45
+ end
46
+
47
+ @recorder = Akane::Recorder.new(@storages, logger: @config.logger)
48
+
49
+ @logger.info "Prepared with #{@storages.size} storage(s) and #{@receivers.size} receiver(s)"
50
+ end
51
+
52
+ def start
53
+ @logger.info "Starting receivers..."
54
+ @receivers.each(&:start)
55
+ @logger.info "Starting recorder..."
56
+ if EM.reactor_running?
57
+ EM.defer { @recorder.run }
58
+ else
59
+ @recorder.run
60
+ end
61
+ end
62
+
63
+ def run
64
+ @logger.info "Running..."
65
+ self.prepare()
66
+
67
+ if EM.reactor_running?
68
+ start()
69
+ else
70
+ @logger.info "Diving into Eventmachine"
71
+ EM.epoll
72
+ EM.kqueue
73
+ EM.run do
74
+ start()
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def on_tweet(account, tweet)
82
+ @recorder.record_tweet(account, tweet)
83
+ end
84
+
85
+ def on_message(account, message)
86
+ @recorder.record_message(account, message)
87
+ end
88
+
89
+ def on_event(account, event)
90
+ @recorder.record_event(account, event)
91
+ end
92
+
93
+ def on_delete(account, user_id, tweet_id)
94
+ @recorder.mark_as_deleted(account, user_id, tweet_id)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,47 @@
1
+ module Akane
2
+ module Receivers
3
+ class AbstractReceiver
4
+ def initialize(consumer: raise(ArgumentError, 'missing consumer'),
5
+ account: raise(ArgumentError, 'missing account'),
6
+ logger: Logger.new($stdout),
7
+ config: {})
8
+ @consumer = consumer
9
+ @account = account
10
+ @logger = logger
11
+ @config = config
12
+
13
+ @hooks = {}
14
+ end
15
+
16
+ def start
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def stop
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def running?
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def on(kind, &block)
29
+ (@hooks[kind] ||= []) << block
30
+ self
31
+ end
32
+
33
+ def on_tweet(&block) on(:tweet, &block) end
34
+ def on_delete(&block) on(:delete, &block) end
35
+ def on_message(&block) on(:message, &block) end
36
+ def on_event(&block) on(:event, &block) end
37
+
38
+ private
39
+
40
+ def invoke(kind, *args)
41
+ return unless @hooks[kind]
42
+ @hooks[kind].each { |hook| hook.call(*args) }
43
+ self
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,63 @@
1
+ require 'akane/receivers/abstract_receiver'
2
+ require 'tweetstream'
3
+
4
+ module Akane
5
+ module Receivers
6
+ class Stream < AbstractReceiver
7
+ def initialize(*)
8
+ super
9
+ @running = false
10
+ end
11
+
12
+ def running?() @running end
13
+
14
+ def stream
15
+ @stream ||= TweetStream::Client.new(
16
+ auth_method: :oauth,
17
+ consumer_key: @consumer[:token],
18
+ consumer_secret: @consumer[:secret],
19
+ oauth_token: @account[:token],
20
+ oauth_token_secret: @account[:secret]
21
+ ).tap { |stream|
22
+ stream.on_anything do |hash|
23
+ invoke(:event, hash) if hash["event"]
24
+ end
25
+
26
+ stream.on_timeline_status do |tweet|
27
+ invoke(:tweet, tweet)
28
+ end
29
+
30
+ stream.on_delete do |tweet_id, user_id|
31
+ invoke(:delete, user_id, tweet_id)
32
+ end
33
+
34
+ stream.on_direct_message do |message|
35
+ invoke(:message, message)
36
+ end
37
+
38
+ stream.on_inited do
39
+ @logger.info "Stream: inited"
40
+ end
41
+
42
+ stream.on_reconnect do
43
+ @logger.info "Stream: reconnected"
44
+ end
45
+ }
46
+ end
47
+
48
+ def start
49
+ @logger.info "Stream : Starting"
50
+ stream.userstream
51
+ @running = true
52
+ self
53
+ end
54
+
55
+ def stop
56
+ stream.stop_stream
57
+ @stream = nil
58
+ @running = false
59
+ self
60
+ end
61
+ end
62
+ end
63
+ end