atig 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.
Files changed (171) hide show
  1. data/.gitignore +24 -0
  2. data/Gemfile +3 -0
  3. data/README.mkdn +52 -0
  4. data/Rakefile +15 -0
  5. data/atig.gemspec +25 -0
  6. data/bin/atig +74 -0
  7. data/docs/OMakefile +32 -0
  8. data/docs/OMakeroot +45 -0
  9. data/docs/_static/allow.png +0 -0
  10. data/docs/_static/emacs.png +0 -0
  11. data/docs/_static/irc_setting.png +0 -0
  12. data/docs/_static/irssi.png +0 -0
  13. data/docs/_static/limechat.png +0 -0
  14. data/docs/_static/limechat_s.png +0 -0
  15. data/docs/_static/oauth_channel.png +0 -0
  16. data/docs/_static/screenshot.png +0 -0
  17. data/docs/_static/structure.png +0 -0
  18. data/docs/_static/verify.png +0 -0
  19. data/docs/changelog.rst +96 -0
  20. data/docs/commandline_options.rst +21 -0
  21. data/docs/commands.rst +84 -0
  22. data/docs/conf.py +194 -0
  23. data/docs/config.rst +159 -0
  24. data/docs/feature.rst +41 -0
  25. data/docs/graphics.graffle +1995 -0
  26. data/docs/hacking_guide.rst +43 -0
  27. data/docs/index.rst +109 -0
  28. data/docs/irc.rst +31 -0
  29. data/docs/options.rst +75 -0
  30. data/docs/quickstart.rst +89 -0
  31. data/docs/resize.sh +7 -0
  32. data/docs/tiarra.rst +2 -0
  33. data/docs/tig.rst +21 -0
  34. data/lib/atig.rb +19 -0
  35. data/lib/atig/agent.rb +8 -0
  36. data/lib/atig/agent/agent.rb +38 -0
  37. data/lib/atig/agent/clenup.rb +23 -0
  38. data/lib/atig/agent/dm.rb +35 -0
  39. data/lib/atig/agent/following.rb +45 -0
  40. data/lib/atig/agent/full_list.rb +20 -0
  41. data/lib/atig/agent/list.rb +55 -0
  42. data/lib/atig/agent/list_status.rb +46 -0
  43. data/lib/atig/agent/mention.rb +13 -0
  44. data/lib/atig/agent/other_list.rb +18 -0
  45. data/lib/atig/agent/own_list.rb +18 -0
  46. data/lib/atig/agent/stream_follow.rb +38 -0
  47. data/lib/atig/agent/timeline.rb +13 -0
  48. data/lib/atig/agent/user_stream.rb +31 -0
  49. data/lib/atig/basic_twitter.rb +116 -0
  50. data/lib/atig/bitly.rb +52 -0
  51. data/lib/atig/channel.rb +5 -0
  52. data/lib/atig/channel/channel.rb +17 -0
  53. data/lib/atig/channel/dm.rb +14 -0
  54. data/lib/atig/channel/list.rb +76 -0
  55. data/lib/atig/channel/mention.rb +20 -0
  56. data/lib/atig/channel/retweet.rb +28 -0
  57. data/lib/atig/channel/timeline.rb +74 -0
  58. data/lib/atig/command.rb +21 -0
  59. data/lib/atig/command/autofix.rb +58 -0
  60. data/lib/atig/command/command.rb +24 -0
  61. data/lib/atig/command/command_helper.rb +95 -0
  62. data/lib/atig/command/destroy.rb +44 -0
  63. data/lib/atig/command/dm.rb +31 -0
  64. data/lib/atig/command/favorite.rb +27 -0
  65. data/lib/atig/command/info.rb +50 -0
  66. data/lib/atig/command/limit.rb +15 -0
  67. data/lib/atig/command/location.rb +23 -0
  68. data/lib/atig/command/name.rb +18 -0
  69. data/lib/atig/command/option.rb +37 -0
  70. data/lib/atig/command/refresh.rb +18 -0
  71. data/lib/atig/command/reply.rb +37 -0
  72. data/lib/atig/command/retweet.rb +63 -0
  73. data/lib/atig/command/search.rb +51 -0
  74. data/lib/atig/command/spam.rb +26 -0
  75. data/lib/atig/command/status.rb +41 -0
  76. data/lib/atig/command/thread.rb +44 -0
  77. data/lib/atig/command/time.rb +32 -0
  78. data/lib/atig/command/uptime.rb +32 -0
  79. data/lib/atig/command/user.rb +42 -0
  80. data/lib/atig/command/user_info.rb +27 -0
  81. data/lib/atig/command/version.rb +49 -0
  82. data/lib/atig/command/whois.rb +39 -0
  83. data/lib/atig/db/db.rb +60 -0
  84. data/lib/atig/db/followings.rb +131 -0
  85. data/lib/atig/db/listenable.rb +22 -0
  86. data/lib/atig/db/lists.rb +76 -0
  87. data/lib/atig/db/roman.rb +30 -0
  88. data/lib/atig/db/sized_uniq_array.rb +62 -0
  89. data/lib/atig/db/sql.rb +35 -0
  90. data/lib/atig/db/statuses.rb +147 -0
  91. data/lib/atig/db/transaction.rb +47 -0
  92. data/lib/atig/exception_util.rb +26 -0
  93. data/lib/atig/gateway.rb +62 -0
  94. data/lib/atig/gateway/channel.rb +99 -0
  95. data/lib/atig/gateway/session.rb +326 -0
  96. data/lib/atig/http.rb +95 -0
  97. data/lib/atig/ifilter.rb +7 -0
  98. data/lib/atig/ifilter/expand_url.rb +74 -0
  99. data/lib/atig/ifilter/retweet.rb +14 -0
  100. data/lib/atig/ifilter/retweet_time.rb +16 -0
  101. data/lib/atig/ifilter/sanitize.rb +18 -0
  102. data/lib/atig/ifilter/strip.rb +15 -0
  103. data/lib/atig/ifilter/utf7.rb +26 -0
  104. data/lib/atig/ifilter/xid.rb +36 -0
  105. data/lib/atig/levenshtein.rb +49 -0
  106. data/lib/atig/monkey.rb +4 -0
  107. data/lib/atig/oauth-patch.rb +40 -0
  108. data/lib/atig/oauth.rb +55 -0
  109. data/lib/atig/ofilter.rb +4 -0
  110. data/lib/atig/ofilter/escape_url.rb +102 -0
  111. data/lib/atig/ofilter/footer.rb +20 -0
  112. data/lib/atig/ofilter/geo.rb +17 -0
  113. data/lib/atig/ofilter/short_url.rb +47 -0
  114. data/lib/atig/option.rb +90 -0
  115. data/lib/atig/scheduler.rb +79 -0
  116. data/lib/atig/search.rb +22 -0
  117. data/lib/atig/search_twitter.rb +21 -0
  118. data/lib/atig/sized_hash.rb +33 -0
  119. data/lib/atig/stream.rb +66 -0
  120. data/lib/atig/twitter.rb +79 -0
  121. data/lib/atig/twitter_struct.rb +63 -0
  122. data/lib/atig/unu.rb +27 -0
  123. data/lib/atig/update_checker.rb +53 -0
  124. data/lib/atig/url_escape.rb +62 -0
  125. data/lib/atig/util.rb +16 -0
  126. data/lib/atig/version.rb +3 -0
  127. data/lib/memory_profiler.rb +77 -0
  128. data/spec/command/autofix_spec.rb +35 -0
  129. data/spec/command/destroy_spec.rb +98 -0
  130. data/spec/command/dm_spec.rb +28 -0
  131. data/spec/command/favorite_spec.rb +55 -0
  132. data/spec/command/limit_spec.rb +27 -0
  133. data/spec/command/location_spec.rb +25 -0
  134. data/spec/command/name_spec.rb +19 -0
  135. data/spec/command/option_spec.rb +133 -0
  136. data/spec/command/refresh_spec.rb +22 -0
  137. data/spec/command/reply_spec.rb +79 -0
  138. data/spec/command/retweet_spec.rb +66 -0
  139. data/spec/command/spam_spec.rb +27 -0
  140. data/spec/command/status_spec.rb +44 -0
  141. data/spec/command/thread_spec.rb +91 -0
  142. data/spec/command/time_spec.rb +52 -0
  143. data/spec/command/uptime_spec.rb +55 -0
  144. data/spec/command/user_info_spec.rb +42 -0
  145. data/spec/command/user_spec.rb +50 -0
  146. data/spec/command/version_spec.rb +67 -0
  147. data/spec/command/whois_spec.rb +78 -0
  148. data/spec/db/followings_spec.rb +100 -0
  149. data/spec/db/listenable_spec.rb +32 -0
  150. data/spec/db/lists_spec.rb +104 -0
  151. data/spec/db/roman_spec.rb +17 -0
  152. data/spec/db/sized_uniq_array_spec.rb +63 -0
  153. data/spec/db/statuses_spec.rb +180 -0
  154. data/spec/ifilter/expand_url_spec.rb +44 -0
  155. data/spec/ifilter/retweet_spec.rb +28 -0
  156. data/spec/ifilter/retweet_time_spec.rb +25 -0
  157. data/spec/ifilter/sanitize_spec.rb +25 -0
  158. data/spec/ifilter/sid_spec.rb +29 -0
  159. data/spec/ifilter/strip_spec.rb +23 -0
  160. data/spec/ifilter/tid_spec.rb +29 -0
  161. data/spec/ifilter/utf7_spec.rb +30 -0
  162. data/spec/levenshtein_spec.rb +24 -0
  163. data/spec/ofilter/escape_url_spec.rb +50 -0
  164. data/spec/ofilter/footer_spec.rb +32 -0
  165. data/spec/ofilter/geo_spec.rb +33 -0
  166. data/spec/ofilter/short_url_spec.rb +127 -0
  167. data/spec/option_spec.rb +91 -0
  168. data/spec/sized_hash_spec.rb +45 -0
  169. data/spec/spec_helper.rb +35 -0
  170. data/spec/update_checker_spec.rb +55 -0
  171. metadata +326 -0
@@ -0,0 +1,23 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+
5
+ module Atig
6
+ module Agent
7
+ class Cleanup
8
+ include Util
9
+
10
+ def initialize(context, api, db)
11
+ @log = context.log
12
+ daemon do
13
+ db.transaction do|t|
14
+ log :info, "cleanup"
15
+ t.cleanup
16
+ end
17
+ # once a day
18
+ sleep 60*60*24
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/util'
3
+
4
+ module Atig
5
+ module Agent
6
+ class Dm
7
+ include Util
8
+
9
+ def initialize(context, api, db)
10
+ @log = context.log
11
+ @api = api
12
+ @prev = nil
13
+
14
+ log :info, "initialize"
15
+
16
+ @api.repeat(600) do|t|
17
+ q = { :count => 200 }
18
+ if @prev
19
+ q.update :since_id => @prev
20
+ else
21
+ q.update :count => 1
22
+ end
23
+ dms = t.get("direct_messages", q)
24
+ log :debug, "You have #{dms.size} dm."
25
+
26
+ dms.reverse_each do|dm|
27
+ db.dms.transaction do|d|
28
+ d.add :status => dm, :user => dm.sender
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+
5
+ module Atig
6
+ module Agent
7
+ class Following
8
+ include Util
9
+
10
+ def initialize(context, api, db)
11
+ @opts = context.opts
12
+ @log = context.log
13
+ @db = db
14
+ log :info, "initialize"
15
+
16
+ api.repeat(3600){|t| update t }
17
+ @db.followings.on_invalidated{
18
+ log :info, "invalidated followings"
19
+ api.delay(0){|t| update t }
20
+ }
21
+ end
22
+
23
+ def update(api)
24
+ if @db.followings.empty?
25
+ friends = api.page("statuses/friends/#{@db.me.id}", :users)
26
+ else
27
+ @db.me = api.post("account/update_profile")
28
+ return if @db.me.friends_count == @db.followings.size
29
+ friends = api.page("statuses/friends/#{@db.me.id}", :users)
30
+ end
31
+
32
+ if @opts.only
33
+ followers = api.page("followers/ids/#{@db.me.id}", :ids)
34
+ friends.each do|friend|
35
+ friend[:only] = !followers.include?(friend.id)
36
+ end
37
+ end
38
+
39
+ @db.followings.transaction do|d|
40
+ d.update friends
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+ require 'atig/agent/list'
5
+
6
+ module Atig
7
+ module Agent
8
+ class FullList < List
9
+ def entry_points
10
+ [ "#{@db.me.screen_name}/lists",
11
+ "#{@db.me.screen_name}/lists/subscriptions"
12
+ ]
13
+ end
14
+
15
+ def interval
16
+ 3600
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,55 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+
5
+ module Atig
6
+ module Agent
7
+ class List
8
+ include Util
9
+
10
+ def initialize(context, api, db)
11
+ @log = context.log
12
+ @db = db
13
+ log :info, "initialize"
14
+
15
+ @db.lists.on_invalidated{|name|
16
+ log :info, "invalidated #{name}"
17
+ api.delay(0){|t|
18
+ if name == :all then
19
+ full_update t
20
+ else
21
+ @db.lists[name].update t.page("#{@db.me.screen_name}/#{name}/members", :users, true)
22
+ end
23
+ }
24
+ }
25
+ api.repeat( interval ) do|t|
26
+ self.full_update t
27
+ end
28
+ end
29
+
30
+ def full_update(t)
31
+ lists = entry_points.map{|entry|
32
+ t.page(entry, :lists, true)
33
+ }.flatten
34
+
35
+ users = {}
36
+ lists.map do|list|
37
+ name = if list.user.screen_name == @db.me.screen_name then
38
+ "#{list.slug}"
39
+ else
40
+ "#{list.user.screen_name}^#{list.slug}"
41
+ end
42
+ begin
43
+ users[name] =
44
+ t.page("#{list.user.screen_name}/#{list.slug}/members", :users, true)
45
+ rescue APIFailed => e
46
+ log :error, e.inspect
47
+ users[name] =
48
+ @db.lists.find_by_list_name(list.slug)
49
+ end
50
+ end
51
+ @db.lists.update users
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+
5
+ module Atig; end
6
+ module Atig::Agent; end
7
+
8
+ class Atig::Agent::ListStatus
9
+ include Atig::Util
10
+
11
+ def initialize(context, api, db)
12
+ @log = context.log
13
+ @db = db
14
+ log :info, "initialize"
15
+
16
+ @prev = {}
17
+ api.repeat(60*5) do|t|
18
+ db.lists.each do |name, _|
19
+ log :debug, "retrieve #{name} statuses"
20
+ q = {}
21
+ q.update(:since_id => @prev[name]) if @prev.key?(name)
22
+
23
+ screen_name,slug = parse name
24
+ statuses = t.get("#{screen_name}/lists/#{slug}/statuses",q)
25
+ statuses.reverse_each do|status|
26
+ db.statuses.transaction do|d|
27
+ d.add(:status => status,
28
+ :user => status.user,
29
+ :source => :list,
30
+ :list => name)
31
+ end
32
+ end
33
+ @prev[name] = statuses[0].id if statuses && statuses.size > 0
34
+ end
35
+ end
36
+
37
+ def parse(name)
38
+ if name.include? '^' then
39
+ name.split("^",2)
40
+ else
41
+ [@db.me.screen_name, name]
42
+ end
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,13 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/agent/agent'
3
+
4
+ module Atig
5
+ module Agent
6
+ class Mention < Atig::Agent::Agent
7
+ def initialize(context, api, db); super end
8
+ def interval; 180 end
9
+ def path; '/statuses/mentions' end
10
+ def source; :mention end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+ require 'atig/agent/list'
5
+
6
+ module Atig
7
+ module Agent
8
+ class OtherList < List
9
+ def entry_points
10
+ [ "#{@db.me.screen_name}/lists/subscriptions" ]
11
+ end
12
+
13
+ def interval
14
+ 3600
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+ require 'atig/agent/list'
5
+
6
+ module Atig
7
+ module Agent
8
+ class OwnList < List
9
+ def entry_points
10
+ [ "#{@db.me.screen_name}/lists" ]
11
+ end
12
+
13
+ def interval
14
+ 3600
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/util'
3
+
4
+ module Atig
5
+ module Agent
6
+ class StreamFollow
7
+ include Util
8
+
9
+ def initialize(context, api, db)
10
+ @log = context.log
11
+ @api = api
12
+ @prev = nil
13
+
14
+ return unless context.opts.stream
15
+
16
+ log :info, "initialize"
17
+
18
+ @api.delay(0)do|t|
19
+ @follows = context.opts.follow.split(',').map{|user|
20
+ t.get("users/show",:screen_name=>user).id
21
+ }.join(',')
22
+ end
23
+
24
+
25
+ @api.stream do|t|
26
+ Thread.pass until @follows
27
+ t.watch('statuses/filter', :follow => @follows) do |status|
28
+ if status and status.user
29
+ db.transaction do|d|
30
+ d.statuses.add :status => status, :user => status.user, :source => :stream_follow
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/agent/agent'
3
+
4
+ module Atig
5
+ module Agent
6
+ class Timeline < Atig::Agent::Agent
7
+ def initialize(context, api, db); super end
8
+ def interval; 30 end
9
+ def path; '/statuses/home_timeline' end
10
+ def source; :timeline end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/util'
3
+
4
+ module Atig
5
+ module Agent
6
+ class UserStream
7
+ include Util
8
+
9
+ def initialize(context, api, db)
10
+ @log = context.log
11
+ @api = api
12
+ @prev = nil
13
+
14
+ return unless context.opts.stream
15
+
16
+ log :info, "initialize"
17
+
18
+ @api.stream do|t|
19
+ t.watch('user') do |status|
20
+ # @log.debug status.inspect
21
+ if status and status.user
22
+ db.statuses.transaction do|d|
23
+ d.add :status => status, :user => status.user, :source => :user_stream
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require "json"
5
+ require 'atig/twitter_struct'
6
+ require 'atig/http'
7
+
8
+ module Atig
9
+ # from tig.rb
10
+ class BasicTwitter
11
+ attr_reader :limit, :remain, :reset
12
+
13
+ class APIFailed < StandardError; end
14
+
15
+ def initialize(context, api_base)
16
+ @log = context.log
17
+ @opts = context.opts
18
+ @api_base = api_base
19
+ @http = Atig::Http.new @log
20
+
21
+ @limit = @remain = 150
22
+ end
23
+
24
+ def api(path, query = {}, opts = {})
25
+ path.sub!(%r{\A/+}, "")
26
+
27
+ uri = api_base
28
+ uri.path += path
29
+ uri.path += ".json" if path != "users/username_available"
30
+ uri.query = query.to_query_str unless query.empty?
31
+
32
+ header = {}
33
+
34
+ begin
35
+ ret = request(uri, opts)
36
+ rescue OpenSSL::SSL::SSLError => e
37
+ @log.error e.inspect
38
+ raise e.inspect
39
+ end
40
+
41
+ if ret["X-RateLimit-Limit"] then
42
+ hourly_limit = ret["X-RateLimit-Limit"].to_i
43
+ unless hourly_limit.zero?
44
+ if @limit != hourly_limit
45
+ msg = "The rate limit per hour was changed: #{@limit} to #{hourly_limit}"
46
+ @log.info msg
47
+ @limit = hourly_limit
48
+ end
49
+ end
50
+ end
51
+
52
+ if ret["X-RateLimit-Remaining"] then
53
+ @remain = ret["X-RateLimit-Remaining"].to_i
54
+ @log.debug "IP based limit: #{@remain}"
55
+ end
56
+
57
+ if ret["X-RateLimit-Reset"] then
58
+ @reset = ret["X-RateLimit-Reset"].to_i
59
+ @log.debug "RateLimit Reset: #{@reset}"
60
+ end
61
+
62
+ case ret
63
+ when Net::HTTPOK # 200
64
+ # Avoid Twitter's invalid JSON
65
+ json = ret.body.strip.sub(/\A(?:false|true)\z/, "[\\&]")
66
+
67
+ res = JSON.parse(json)
68
+ if res.is_a?(Hash) && res["error"] # and not res["response"]
69
+ if @error != res["error"]
70
+ @error = res["error"]
71
+ log @error
72
+ end
73
+ raise APIFailed, res["error"]
74
+ end
75
+
76
+ TwitterStruct.make(res)
77
+ when Net::HTTPNoContent, # 204
78
+ Net::HTTPNotModified # 304
79
+ []
80
+ when Net::HTTPBadRequest # 400: exceeded the rate limitation
81
+ if ret.key?("X-RateLimit-Reset")
82
+ @log.info "waiting for rate limit reset"
83
+ s = ret["X-RateLimit-Reset"].to_i - Time.now.to_i
84
+ if s > 0
85
+ sleep (s > 60 * 10) ? 60 * 10 : s # 10 分に一回はとってくるように
86
+ end
87
+ end
88
+ raise APIFailed, "#{ret.code}: #{ret.message}"
89
+ when Net::HTTPUnauthorized # 401
90
+ raise APIFailed, "#{ret.code}: #{ret.message}"
91
+ else
92
+ raise APIFailed, "Server Returned #{ret.code} #{ret.message}"
93
+ end
94
+ rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
95
+ raise APIFailed, e.inspect
96
+ end
97
+
98
+ def self.http_methods(*methods)
99
+ methods.each do |m|
100
+ self.module_eval <<END
101
+ def #{m}(path, query = {}, opts = {})
102
+ opts.update( :method => :#{m})
103
+ api path, query, opts
104
+ end
105
+ END
106
+ end
107
+ end
108
+
109
+ http_methods :get, :post, :put, :delete
110
+
111
+ private
112
+ def api_base
113
+ URI(@opts.send @api_base)
114
+ end
115
+ end
116
+ end