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,4 @@
1
+ require 'atig/ofilter/escape_url'
2
+ require 'atig/ofilter/short_url'
3
+ require 'atig/ofilter/geo'
4
+ require 'atig/ofilter/footer'
@@ -0,0 +1,102 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+ require 'atig/http'
5
+ require 'atig/url_escape'
6
+
7
+ begin
8
+ require "punycode"
9
+ rescue LoadError
10
+ end
11
+
12
+ module Atig
13
+ module OFilter
14
+ class EscapeUrl
15
+ include Util
16
+ def initialize(context)
17
+ @log = context.log
18
+ @http = Atig::Http.new @log
19
+ end
20
+
21
+ def call(status)
22
+ status.merge(:status => escape_http_urls(status[:status]))
23
+ end
24
+
25
+ def exist_uri?(uri, limit = 1)
26
+ ret = nil
27
+ #raise "Not supported." unless uri.is_a?(URI::HTTP)
28
+ return ret if limit.zero? or uri.nil? or not uri.is_a?(URI::HTTP)
29
+ @log.debug uri.inspect
30
+
31
+ req = @http.req :head, uri
32
+ @http.http(uri, 3, 2).request(req) do |res|
33
+ ret = case res
34
+ when Net::HTTPSuccess
35
+ true
36
+ when Net::HTTPRedirection
37
+ uri = resolve_http_redirect(uri)
38
+ exist_uri?(uri, limit - 1)
39
+ when Net::HTTPClientError
40
+ false
41
+ else
42
+ nil
43
+ end
44
+ end
45
+ ret
46
+ rescue => e
47
+ @log.error e.inspect
48
+ ret
49
+ end
50
+
51
+ def escape_http_urls(text)
52
+ original_text = text.encoding!("UTF-8").dup
53
+
54
+ if defined? ::Punycode
55
+ # TODO: Nameprep
56
+ text.gsub!(%r{(https?://)([^\x00-\x2C\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+)}) do
57
+ domain = $2
58
+ # Dots:
59
+ # * U+002E (full stop) * U+3002 (ideographic full stop)
60
+ # * U+FF0E (fullwidth full stop) * U+FF61 (halfwidth ideographic full stop)
61
+ # => /[.\u3002\uFF0E\uFF61] # Ruby 1.9 /x
62
+ $1 + domain.split(/\.|\343\200\202|\357\274\216|\357\275\241/).map do |label|
63
+ break [domain] if /\A-|[\x00-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]|-\z/ === label
64
+ next label unless /[^-A-Za-z0-9]/ === label
65
+ punycode = Punycode.encode(label)
66
+ break [domain] if punycode.size > 59
67
+ "xn--#{punycode}"
68
+ end.join(".")
69
+ end
70
+ if text != original_text
71
+ log :info, "Punycode encoded: #{text}"
72
+ original_text = text.dup
73
+ end
74
+ end
75
+
76
+ urls = []
77
+ text.split(/[\s<>]+/).each do |str|
78
+ next if /%[0-9A-Fa-f]{2}/ === str
79
+ # URI::UNSAFE + "#"
80
+ escaped_str = URI.escape(str, %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]#]}) #'
81
+ URI.extract(escaped_str, %w[http https]).each do |url|
82
+ uri = URI(URI.rstrip(url))
83
+ if not urls.include?(uri.to_s) and self.exist_uri?(uri)
84
+ urls << uri.to_s
85
+ end
86
+ end if escaped_str != str
87
+ end
88
+ urls.each do |url|
89
+ unescaped_url = URI.unescape(url).encoding!("UTF-8")
90
+ text.gsub!(unescaped_url, url)
91
+ end
92
+ log :info, "Percent encoded: #{text}" if text != original_text
93
+
94
+ text.encoding!("UTF-8")
95
+ rescue => e
96
+ log :error, e
97
+ text
98
+ end
99
+ end
100
+ end
101
+ end
102
+
@@ -0,0 +1,20 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ module Atig
4
+ module OFilter
5
+ class Footer
6
+ def initialize(context)
7
+ @opts = context.opts
8
+ end
9
+
10
+ def call(q)
11
+ if @opts.footer && !@opts.footer.empty? then
12
+ q.merge :status => "#{q[:status]} #{@opts.footer}"
13
+ else
14
+ q
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ module Atig
4
+ module OFilter
5
+ class Geo
6
+ def initialize(context)
7
+ @opts = context.opts
8
+ end
9
+
10
+ def call(q)
11
+ return q unless @opts.ll
12
+ lat, long = @opts.ll.split(",", 2)
13
+ q.merge :lat => lat.to_f, :long => long.to_f
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/unu'
4
+ require 'atig/bitly'
5
+
6
+ module Atig
7
+ module OFilter
8
+ class ShortUrl
9
+ MIN_LEN = 20
10
+
11
+ def initialize(context)
12
+ @log = context.log
13
+ @opts = context.opts
14
+ @http = Atig::Http.new @log
15
+ end
16
+
17
+ def call(status)
18
+ mesg = status[:status]
19
+ status.merge(:status => short_urls(mesg))
20
+ end
21
+
22
+ def short_urls(mesg)
23
+ shorten = case
24
+ when @opts.bitlify.to_s.include?(":")
25
+ login, key, len = @opts.bitlify.to_s.split(":", 3)
26
+ @len = (len || MIN_LEN).to_i
27
+ Bitly.login @log, login, key
28
+ when @opts.bitlify
29
+ @len = (@opts.bitlify.to_s || MIN_LEN).to_i
30
+ Bitly.no_login @log
31
+ when @opts.unuify
32
+ @len = (@opts.unuify.to_s || MIN_LEN).to_i
33
+ Unu.new @log
34
+ else
35
+ return mesg
36
+ end
37
+ mesg.gsub(URI.regexp(%w[http https])) do|url|
38
+ if URI.rstrip(url).size < @len then
39
+ url
40
+ else
41
+ shorten.shorten url
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Atig
3
+ class Option
4
+ class << self
5
+ def default_value(name, value)
6
+ @default ||= {}
7
+ @default[name.to_sym] = value
8
+ end
9
+
10
+ def parse(str)
11
+ @default ||= {}
12
+ opts = str.split(" ")
13
+ opts = opts.inject({}) do |r, i|
14
+ key, value = i.split("=", 2)
15
+
16
+ r.update key.to_sym => parse_value(value)
17
+ end
18
+ self.new(@default.merge(opts))
19
+ end
20
+
21
+ def parse_value(value)
22
+ case value
23
+ when nil, /\Atrue\z/ then true
24
+ when /\Afalse\z/ then false
25
+ when /\A\d+\z/ then value.to_i
26
+ when /\A(?:\d+\.\d*|\.\d+)\z/ then value.to_f
27
+ else value
28
+ end
29
+ end
30
+ end
31
+
32
+ default_value :api_base, 'https://api.twitter.com/1/'
33
+ default_value :stream_api_base, 'http://chirpstream.twitter.com/2b/'
34
+ default_value :search_api_base, 'http://search.twitter.com/'
35
+
36
+ def initialize(table)
37
+ @table = {}
38
+ table.each do|key,value|
39
+ key = key.to_sym
40
+ @table[key] = value
41
+ new_ostruct_member key
42
+ end
43
+
44
+ # Ruby 1.8だとidというフィールドが作れないので作っておく
45
+ new_ostruct_member :id, true
46
+ end
47
+
48
+ def marshal_dump; @table end
49
+ def fields;
50
+ @table.keys
51
+ end
52
+
53
+ def [](name)
54
+ @table[name.to_sym]
55
+ end
56
+
57
+ def []=(name, value)
58
+ @table[name.to_sym] = value
59
+ end
60
+
61
+ def new_ostruct_member(name, force=false)
62
+ if force or (not self.respond_to?(name))
63
+ class << self; self; end.class_eval do
64
+ define_method(name) { @table[name] }
65
+ define_method(:"#{name}=") { |x| @table[name] = x }
66
+ end
67
+ end
68
+ end
69
+
70
+ def method_missing(mid, *args) # :nodoc:
71
+ mname = mid.id2name
72
+ len = args.length
73
+ if mname =~ /=$/
74
+ if len != 1
75
+ raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
76
+ end
77
+ if self.frozen?
78
+ raise TypeError, "can't modify frozen #{self.class}", caller(1)
79
+ end
80
+ name = mname.chop.to_sym
81
+ @table[name] = args[0]
82
+ self.new_ostruct_member(name)
83
+ elsif len == 0
84
+ @table[mid]
85
+ else
86
+ raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,79 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/util'
4
+
5
+ module Atig
6
+ class Scheduler
7
+ include Util
8
+
9
+ attr_reader :search
10
+
11
+ def initialize(context, api, search, stream)
12
+ @log = context.log
13
+ @api = api
14
+ @search = search
15
+ @stream = stream
16
+ @agents = []
17
+
18
+ @queue = SizedQueue.new 10
19
+ daemon do
20
+ f = @queue.pop
21
+ f.call
22
+ end
23
+ end
24
+
25
+ def repeat(interval,opts={}, &f)
26
+ @queue.push(lambda{ safe { f.call @api } })
27
+ t = daemon do
28
+ sleep interval
29
+ log :debug, "agent #{t.inspect} is invoked"
30
+ @queue.push(lambda{ safe { f.call @api } })
31
+ end
32
+
33
+ log :info, "repeat agent #{t.inspect} is registered"
34
+ @agents << t
35
+ t
36
+ end
37
+
38
+ def delay(interval, opts={}, &f)
39
+ sleep interval
40
+ @queue.push(lambda{ safe { f.call @api } })
41
+ end
42
+
43
+ def stream(&f)
44
+ return nil unless @stream
45
+ @stream_thread.kill if @stream_thread
46
+ @stream_thread = daemon {
47
+ f.call @stream
48
+ }
49
+ end
50
+
51
+ def re_try(count, &f)
52
+ begin
53
+ f.call
54
+ rescue => e
55
+ log :error, [count, e.inspect].inspect
56
+ if count > 0
57
+ count -= 1
58
+ sleep 1
59
+ log :debug, "retry"
60
+ retry
61
+ end
62
+ log :error, "Some Error Happened: #{e}"
63
+ raise e
64
+ end
65
+ end
66
+
67
+ def limit
68
+ @api.limit
69
+ end
70
+
71
+ def remain
72
+ @api.remain
73
+ end
74
+
75
+ def reset
76
+ @api.reset
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'atig/http'
4
+ require 'atig/url_escape'
5
+
6
+ module Atig
7
+ class Search
8
+ def search(query, options = {})
9
+ search = URI("http://search.twitter.com/search.json")
10
+ search.path = "/search.json"
11
+ params = options; options[:q] = query
12
+ search.query = options.to_query_str
13
+ http = Http.new nil
14
+ req = http.req(:get, search)
15
+ res = http.http(search, 5, 10).request(req)
16
+ res = JSON.parse(res.body)
17
+ rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
18
+ @log.error e
19
+ text
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/basic_twitter'
3
+ require 'atig/http'
4
+
5
+ module Atig
6
+ # from tig.rb
7
+ class SearchTwitter < BasicTwitter
8
+ def initialize(context)
9
+ super context, :search_api_base
10
+ @http = Atig::Http.new @log
11
+ end
12
+
13
+ protected
14
+ def request(uri, opts)
15
+ header = {}
16
+ req = @http.req :get, uri, header
17
+ @log.debug [req.method, uri.to_s]
18
+ @http.http(uri, 30, 30).request req
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'forwardable'
3
+
4
+ module Atig
5
+ class SizedHash
6
+ extend Forwardable
7
+
8
+ def_delegators :@xs, :size
9
+
10
+ def initialize(size)
11
+ @size = size
12
+ @xs = []
13
+ end
14
+
15
+ def [](key)
16
+ kv = @xs.assoc(key)
17
+ if kv then
18
+ @xs.delete kv
19
+ @xs.push kv
20
+ kv[1]
21
+ end
22
+ end
23
+
24
+ def []=(key,value)
25
+ @xs.push [key, value]
26
+ @xs.shift if @xs.size > @size
27
+ end
28
+
29
+ def key?(key)
30
+ @xs.any?{|k,_| key == k }
31
+ end
32
+ end
33
+ end