akane 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.
@@ -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