rbitter 0.0.8 → 0.1.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -21
  3. data/.rspec +2 -2
  4. data/.travis.yml +15 -9
  5. data/Gemfile +12 -11
  6. data/LICENSE.txt +22 -22
  7. data/README.md +23 -16
  8. data/Rakefile +8 -8
  9. data/XMLRPC.md +19 -19
  10. data/bin/rbitter +20 -6
  11. data/lib/rbitter/arcserver.rb +126 -97
  12. data/lib/rbitter/console.rb +93 -61
  13. data/lib/rbitter/default/config_json.rb +38 -37
  14. data/lib/rbitter/dlthread.rb +66 -66
  15. data/lib/rbitter/env.rb +62 -59
  16. data/lib/rbitter/libtwitter_connection_override.rb +45 -45
  17. data/lib/rbitter/records.rb +121 -109
  18. data/lib/rbitter/records_migrate/20150327_add_index.rb +11 -7
  19. data/lib/rbitter/records_migrate/20150504_add_replyto_column.rb +12 -0
  20. data/lib/rbitter/streaming.rb +104 -75
  21. data/lib/rbitter/version.rb +20 -3
  22. data/lib/rbitter/xmlrpc.rb +3 -3
  23. data/lib/rbitter/xmlrpcd/base.rb +24 -24
  24. data/lib/rbitter/xmlrpcd/rpchandles.rb +11 -11
  25. data/lib/rbitter/xmlrpcd/xmlrpc_auth_server.rb +82 -83
  26. data/lib/rbitter/xmlrpcd/xmlrpcd.rb +69 -63
  27. data/lib/rbitter.rb +86 -92
  28. data/rbitter.gemspec +42 -42
  29. data/spec/config/default.json +32 -32
  30. data/spec/rbitter/arcserver_spec.rb +30 -9
  31. data/spec/rbitter/console_spec.rb +9 -9
  32. data/spec/rbitter/default/config_json_spec.rb +3 -3
  33. data/spec/rbitter/dlthread_spec.rb +13 -9
  34. data/spec/rbitter/env_spec.rb +62 -56
  35. data/spec/rbitter/libtwitter_connection_override_spec.rb +8 -8
  36. data/spec/rbitter/records_spec.rb +13 -13
  37. data/spec/rbitter/streaming_spec.rb +9 -9
  38. data/spec/rbitter/version_spec.rb +8 -8
  39. data/spec/rbitter/xmlrpc_spec.rb +8 -8
  40. data/spec/rbitter/xmlrpcd/base_spec.rb +29 -29
  41. data/spec/rbitter/xmlrpcd/rpchandles_spec.rb +10 -10
  42. data/spec/rbitter/xmlrpcd/xmlrpc_auth_server_spec.rb +8 -8
  43. data/spec/rbitter/xmlrpcd/xmlrpcd_spec.rb +9 -9
  44. data/spec/rbitter_spec.rb +42 -46
  45. data/spec/spec_helper.rb +39 -36
  46. metadata +68 -41
  47. data/config.json.example +0 -30
@@ -1,109 +1,121 @@
1
- # encoding: utf-8
2
-
3
- require "active_record"
4
- require "date"
5
-
6
- module Rbitter
7
- class Record < ActiveRecord::Base
8
- end
9
- end
10
-
11
- module ARSupport
12
- SCHEME_VERSION = 20150327
13
- SCHEME = {
14
- :marker => :integer, # 0 normal, 2 cut, 1 resume
15
- :marker_msg => :string, # == 0 success, == 2 w/ message
16
- :userid => :integer,
17
- :username => :string,
18
- :tweetid => :integer,
19
- :tweet => :text, # with url unpacked
20
- :date => :datetime,
21
- :rt_count => :integer,
22
- :fav_count => :integer
23
- }
24
-
25
- module_function
26
- def prepared?
27
- ActiveRecord::Base.connection.table_exists?(:records)
28
- end
29
-
30
- def connect_database
31
- if Rbitter.env['activerecord'] == 'sqlite3'
32
- warn "Warning: If you enable XMLRPC access, using sqlite is not recommended."
33
- warn "Warning: Random crash can happen because of concurrency."
34
-
35
- if RUBY_PLATFORM == 'java'
36
- require "jdbc/sqlite3"
37
- Jdbc::SQLite3.load_driver
38
- ActiveRecord::Base.establish_connection(
39
- adapter: 'jdbcsqlite3',
40
- database: Rbitter.env['sqlite3']['dbfile'],
41
- timeout: 10000) # Long timeout for slow computer
42
- else
43
- ActiveRecord::Base.establish_connection(
44
- adapter: 'sqlite3',
45
- database: Rbitter.env['sqlite3']['dbfile'],
46
- timeout: 10000) # Long timeout for slow computer
47
- end
48
- elsif Rbitter.env['activerecord'] == 'mysql2'
49
- Jdbc::MySQL.load_driver if RUBY_PLATFORM == 'java'
50
-
51
- ActiveRecord::Base.establish_connection(
52
- adapter: (RUBY_PLATFORM == 'java' ? 'jdbcmysql' : 'mysql2'),
53
- host: Rbitter.env['mysql2']['host'],
54
- port: Rbitter.env['mysql2']['port'],
55
- database: Rbitter.env['mysql2']['dbname'],
56
- username: Rbitter.env['mysql2']['username'],
57
- password: Rbitter.env['mysql2']['password'],
58
- encoding: "utf8mb4",
59
- collation: "utf8mb4_unicode_ci")
60
- else
61
- raise RuntimeException.new("Unknown configuration value. 'activerecord' value should be sqlite3 or mysql2.")
62
- end
63
- end
64
-
65
- def update_database_scheme
66
- current_version = ActiveRecord::Migrator.current_version
67
- if current_version < SCHEME_VERSION
68
- warn "[records] Your ActiveRecord scheme is outdated."
69
- warn "[records] Migrate... #{current_version} => #{SCHEME_VERSION}"
70
- ActiveRecord::Migrator.migrate(File.expand_path("../records_migrate", __FILE__), SCHEME_VERSION)
71
- end
72
- end
73
-
74
- def prepare option_string=""
75
- ActiveRecord::Schema.define(version: SCHEME_VERSION) {
76
- # MySQL specific option_string:
77
- # utf8mb4 -> supporting UTF-8 4-byte characters (i.e. Emoji)
78
- create_table(:records, { :options => option_string }) do |t|
79
- SCHEME.each_key { |column|
80
- case SCHEME[column]
81
- when :string
82
- t.string column
83
- when :integer
84
- t.integer column, :limit => 8
85
- when :datetime
86
- t.datetime column
87
- when :text
88
- t.text column
89
- else
90
- puts "Unexpected column type '#{SCHEME[column]}' of #{column}"
91
- end
92
- }
93
- end
94
-
95
- add_index :records, :tweetid
96
- }
97
- end
98
-
99
- def any_to_datestring(obj)
100
- if obj.is_a?(String)
101
- # try to parse it
102
- DateTime.parse(obj).strftime("%Y-%m-%d %H:%M:%S")
103
- elsif obj.is_a?(DateTime) or obj.is_a?(Time)
104
- obj.strftime("%Y-%m-%d %H:%M:%S")
105
- else
106
- raise ArgumentError.new("Can\'t automatically extract DateTime info")
107
- end
108
- end
109
- end
1
+ # encoding: utf-8
2
+
3
+ require "active_record"
4
+ require "date"
5
+
6
+ module Rbitter
7
+ class Record < ActiveRecord::Base
8
+ end
9
+ end
10
+
11
+ module ARSupport
12
+ SCHEME_VERSION = 20150504
13
+ SCHEME = {
14
+ :marker => :integer, # 0 normal, 1 begin 2 halt
15
+ :marker_msg => :string,
16
+ :userid => :integer,
17
+ :username => :string,
18
+ :tweetid => :integer,
19
+ :replyto => :integer,
20
+ :tweet => :text, # with url unpacked
21
+ :date => :datetime,
22
+ :rt_count => :integer,
23
+ :fav_count => :integer
24
+ }
25
+
26
+ module_function
27
+ def prepared?
28
+ ActiveRecord::Base.connection.table_exists?(:records)
29
+ end
30
+
31
+ def connect_database
32
+ if Rbitter['activerecord'] == 'sqlite3'
33
+ warn "Warning: If you enable XMLRPC access, using sqlite is not recommended."
34
+ warn "Warning: Random crash can happen because of concurrency."
35
+
36
+ if RUBY_PLATFORM == 'java'
37
+ require "jdbc/sqlite3"
38
+ Jdbc::SQLite3.load_driver
39
+ ActiveRecord::Base.establish_connection(
40
+ adapter: 'jdbcsqlite3',
41
+ database: Rbitter['sqlite3']['dbfile'],
42
+ timeout: 10000) # Long timeout for slow computer
43
+ else
44
+ ActiveRecord::Base.establish_connection(
45
+ adapter: 'sqlite3',
46
+ database: Rbitter['sqlite3']['dbfile'],
47
+ timeout: 10000) # Long timeout for slow computer
48
+ end
49
+ elsif Rbitter['activerecord'] == 'mysql2'
50
+ Jdbc::MySQL.load_driver if RUBY_PLATFORM == 'java'
51
+
52
+ ActiveRecord::Base.establish_connection(
53
+ adapter: (RUBY_PLATFORM == 'java' ? 'jdbcmysql' : 'mysql2'),
54
+ host: Rbitter['mysql2']['host'],
55
+ port: Rbitter['mysql2']['port'],
56
+ database: Rbitter['mysql2']['dbname'],
57
+ username: Rbitter['mysql2']['username'],
58
+ password: Rbitter['mysql2']['password'],
59
+ encoding: "utf8mb4",
60
+ collation: "utf8mb4_unicode_ci")
61
+ else
62
+ raise RuntimeException.new("Unknown configuration value. 'activerecord' value should be sqlite3 or mysql2.")
63
+ end
64
+ end
65
+
66
+ def update_database_scheme
67
+ current_version = ActiveRecord::Migrator.current_version
68
+ if current_version < SCHEME_VERSION
69
+ warn "[records] Your ActiveRecord scheme is outdated."
70
+ warn "[records] Migrate... #{current_version} => #{SCHEME_VERSION}"
71
+ ActiveRecord::Migrator.migrate(File.expand_path("../records_migrate", __FILE__), SCHEME_VERSION)
72
+ end
73
+ end
74
+
75
+ def prepare option_string=""
76
+ ActiveRecord::Schema.define(version: SCHEME_VERSION) {
77
+ # MySQL specific option_string:
78
+ # utf8mb4 -> supporting UTF-8 4-byte characters (i.e. Emoji)
79
+ create_table(:records, { :options => option_string }) do |t|
80
+ SCHEME.each_key { |column|
81
+ case SCHEME[column]
82
+ when :string
83
+ t.string column
84
+ when :integer
85
+ t.integer column, :limit => 8
86
+ when :datetime
87
+ t.datetime column
88
+ when :text
89
+ t.text column
90
+ else
91
+ puts "Unexpected column type '#{SCHEME[column]}' of #{column}"
92
+ end
93
+ }
94
+ end
95
+
96
+ add_index :records, :tweetid
97
+ }
98
+ end
99
+
100
+ def any_to_datestring(obj)
101
+ if obj.is_a?(String)
102
+ # try to parse it
103
+ DateTime.parse(obj).strftime("%Y-%m-%d %H:%M:%S")
104
+ elsif obj.is_a?(DateTime) or obj.is_a?(Time)
105
+ obj.strftime("%Y-%m-%d %H:%M:%S")
106
+ else
107
+ raise ArgumentError.new("Can\'t automatically extract DateTime info")
108
+ end
109
+ end
110
+
111
+ def export_to_csv(csvfile)
112
+ open(csvfile, 'w') { |f|
113
+ f.write("marker,marker_msg,userid,username,tweetid,replyto,tweet,date,rt_count,fav_count")
114
+ f.write("\n")
115
+ Rbitter::Record.find_each { |t|
116
+ f.write("#{t.marker},#{t.marker_msg},#{t.userid},#{t.username},#{t.tweetid},")
117
+ f.write("#{t.replyto},#{t.tweet},#{t.date},#{t.rt_count},#{t.fav_count}\n")
118
+ }
119
+ }
120
+ end
121
+ end
@@ -1,8 +1,12 @@
1
- # encoding: utf-8
2
-
3
-
4
- class AddIndex < ActiveRecord::Migration
5
- def self.change
6
- add_index :records, :tweetid
7
- end
1
+ # encoding: utf-8
2
+
3
+
4
+ class AddIndex < ActiveRecord::Migration
5
+ def up
6
+ add_index :records, :tweetid
7
+ end
8
+
9
+ def change
10
+ up
11
+ end
8
12
  end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class AddReplytoColumn < ActiveRecord::Migration
5
+ def up
6
+ add_column :records, :replyto, :integer, :limit => 8
7
+ end
8
+
9
+ def change
10
+ up
11
+ end
12
+ end
@@ -1,76 +1,105 @@
1
- # encoding: utf-8
2
-
3
- require 'twitter'
4
-
5
- module Rbitter
6
- class StreamClient
7
- def initialize(tokens)
8
- @t = Twitter::Streaming::Client.new do |object|
9
- object.consumer_key = tokens['consumer_key']
10
- object.consumer_secret = tokens['consumer_secret']
11
- object.access_token = tokens['access_token']
12
- object.access_token_secret = tokens['access_token_secret']
13
- end
14
- end
15
-
16
- def run(&operation_block)
17
- begin
18
- internal(&operation_block)
19
- rescue EOFError => e
20
- puts "Network unreachable. Retry in 3 seconds..."
21
- sleep 3
22
- retry
23
- end
24
- end
25
-
26
- private
27
- def internal(&operation_block)
28
- @t.user do |tweet|
29
- if tweet.is_a?(Twitter::Tweet)
30
- if tweet.retweet?
31
- tweet = tweet.retweeted_tweet
32
- end
33
-
34
- text = tweet.full_text.gsub(/(\r\n|\n)/, '')
35
-
36
- # unpack uris and media links
37
- media_urls = Array.new
38
- web_urls = Array.new
39
-
40
- if tweet.entities?
41
- if tweet.media?
42
- tweet.media.each { |media|
43
- media_urls.push("#{media.media_uri_https}")
44
- text.gsub!("#{media.url}", "#{media.display_url}")
45
- }
46
- end
47
-
48
- text += " "
49
- text += media_urls.join(" ")
50
-
51
- if tweet.uris?
52
- tweet.uris.each { |uri|
53
- web_urls.push("#{uri.expanded_url}")
54
- text.gsub!("#{uri.url}", "#{uri.expanded_url}")
55
- }
56
- end
57
- end
58
-
59
- res = {
60
- "tweetid" => tweet.id,
61
- "userid" => tweet.user.id,
62
- "tweet" => text,
63
- "rt_count" => tweet.retweet_count,
64
- "fav_count" => tweet.favorite_count,
65
- "screen_name" => tweet.user.screen_name,
66
- "date" => tweet.created_at,
67
- "media_urls" => media_urls,
68
- "web_urls" => web_urls
69
- }
70
-
71
- yield res
72
- end
73
- end
74
- end
75
- end
1
+ # encoding: utf-8
2
+
3
+ require 'twitter'
4
+
5
+ module Rbitter
6
+ class DummyStreamClient
7
+ def initialize(tokens); end
8
+
9
+ def run(&operation_block)
10
+ internal(&operation_block)
11
+ end
12
+
13
+ private
14
+ def internal(&operation_block)
15
+ tweets = [{
16
+ "tweetid" => 1,
17
+ "userid" => 1,
18
+ "replyto" => nil,
19
+ "tweet" => "test",
20
+ "rt_count" => 0,
21
+ "fav_count" => 0,
22
+ "screen_name" => "twitter",
23
+ "date" => "2015-01-01 12:11:10",
24
+ "media_urls" => ["https://pbs.twimg.com/media/CEPWFtgUgAEmbcV.png"],
25
+ "web_urls" => ["https://www.google.com/"]
26
+ }]
27
+
28
+ tweets.each { |tweet|
29
+ yield tweet
30
+ }
31
+ end
32
+ end
33
+
34
+ class StreamClient
35
+ def initialize(tokens)
36
+ @t = Twitter::Streaming::Client.new do |object|
37
+ object.consumer_key = tokens['consumer_key']
38
+ object.consumer_secret = tokens['consumer_secret']
39
+ object.access_token = tokens['access_token']
40
+ object.access_token_secret = tokens['access_token_secret']
41
+ end
42
+ end
43
+
44
+ def run(&operation_block)
45
+ begin
46
+ internal(&operation_block)
47
+ rescue EOFError => e
48
+ puts "Network unreachable. Retry in 3 seconds..."
49
+ sleep 3
50
+ retry
51
+ end
52
+ end
53
+
54
+ private
55
+ def internal(&operation_block)
56
+ @t.user do |tweet|
57
+ if tweet.is_a?(Twitter::Tweet)
58
+ if tweet.retweet?
59
+ tweet = tweet.retweeted_tweet
60
+ end
61
+
62
+ text = tweet.full_text.gsub(/(\r\n|\n)/, '')
63
+
64
+ # unpack uris and media links
65
+ media_urls = Array.new
66
+ web_urls = Array.new
67
+
68
+ if tweet.entities?
69
+ if tweet.media?
70
+ tweet.media.each { |media|
71
+ media_urls.push("#{media.media_uri_https}")
72
+ text.gsub!("#{media.url}", "#{media.display_url}")
73
+ }
74
+ end
75
+
76
+ text += " "
77
+ text += media_urls.join(" ")
78
+
79
+ if tweet.uris?
80
+ tweet.uris.each { |uri|
81
+ web_urls.push("#{uri.expanded_url}")
82
+ text.gsub!("#{uri.url}", "#{uri.expanded_url}")
83
+ }
84
+ end
85
+ end
86
+
87
+ res = {
88
+ "tweetid" => tweet.id,
89
+ "userid" => tweet.user.id,
90
+ "replyto" => tweet.in_reply_to_status_id? ? tweet.in_reply_to_status_id : nil,
91
+ "tweet" => text,
92
+ "rt_count" => tweet.retweet_count,
93
+ "fav_count" => tweet.favorite_count,
94
+ "screen_name" => tweet.user.screen_name,
95
+ "date" => tweet.created_at,
96
+ "media_urls" => media_urls,
97
+ "web_urls" => web_urls
98
+ }
99
+
100
+ yield res
101
+ end
102
+ end
103
+ end
104
+ end
76
105
  end
@@ -1,3 +1,20 @@
1
- module Rbitter
2
- VERSION = "0.0.8"
3
- end
1
+ module Rbitter
2
+ PRODUCT_NAME = "Rbitter"
3
+ VERSION = "0.1.0"
4
+
5
+ def major
6
+ VERSION.match(/^([0-9]+)\./)[1]
7
+ end
8
+
9
+ def minor
10
+ VERSION.match(/\.([0-9]+)\./)[1]
11
+ end
12
+
13
+ def patchlv
14
+ VERSION.match(/\.([0-9]+)$/)[1]
15
+ end
16
+
17
+ def version_string
18
+ "#{PRODUCT_NAME} #{VERSION}"
19
+ end
20
+ end
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
2
- #
3
-
1
+ # encoding: utf-8
2
+ #
3
+
4
4
  require "rbitter/xmlrpcd/xmlrpcd"
@@ -1,25 +1,25 @@
1
- # encoding: utf-8
2
-
3
- module RPCHandles
4
- RH_INFO = Struct.new("RPCHANDLE_INFO", :name, :version, :author, :description) {
5
- def digest
6
- "<rpchandle: #{name}-#{version} (written by #{author}, #{description})>"
7
- end
8
- }
9
-
10
- module BaseHandle
11
- # If a handler doesn't require an authorization, please inherit below class
12
- class NoAuth < Object
13
- def self.auth?
14
- false
15
- end
16
- end
17
-
18
- # If a handler does require an authorization, please inherit below class
19
- class Auth < Object
20
- def self.auth?
21
- true
22
- end
23
- end
24
- end
1
+ # encoding: utf-8
2
+
3
+ module RPCHandles
4
+ RH_INFO = Struct.new("RPCHANDLE_INFO", :name, :version, :author, :description) {
5
+ def digest
6
+ "<rpchandle: #{name}-#{version} (written by #{author}, #{description})>"
7
+ end
8
+ }
9
+
10
+ module BaseHandle
11
+ # If a handler doesn't require an authorization, please inherit below class
12
+ class NoAuth < Object
13
+ def self.auth?
14
+ false
15
+ end
16
+ end
17
+
18
+ # If a handler does require an authorization, please inherit below class
19
+ class Auth < Object
20
+ def self.auth?
21
+ true
22
+ end
23
+ end
24
+ end
25
25
  end
@@ -1,12 +1,12 @@
1
- # encoding: utf-8
2
-
3
- module RPCHandles
4
- # Override this function will activate authentication feature.
5
- # You can write and add RPCHandle. See 'rpc' folder.
6
-
7
- @@auth_pool = nil
8
- module_function
9
- def auth
10
- @@auth_pool
11
- end
1
+ # encoding: utf-8
2
+
3
+ module RPCHandles
4
+ # Override this function will activate authentication feature.
5
+ # You can write and add RPCHandle. See 'rpc' folder.
6
+
7
+ @@auth_pool = nil
8
+ module_function
9
+ def auth
10
+ @@auth_pool
11
+ end
12
12
  end