atig 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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