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,22 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'forwardable'
3
+
4
+ module Atig
5
+ module Db
6
+ module Listenable
7
+ SingleThread = false
8
+ def listen(&f)
9
+ @listeners ||= []
10
+ @listeners.push f
11
+ end
12
+
13
+ private
14
+ def notify(*args)
15
+ @listeners ||= []
16
+ @listeners.each{|f|
17
+ f.call(*args)
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,76 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'atig/db/listenable'
4
+ require 'atig/db/transaction'
5
+ module Atig
6
+ module Db
7
+ class Lists
8
+ include Listenable
9
+ include Transaction
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ @lists = {}
14
+ @on_invalidated = lambda{|*_| }
15
+ @members = nil
16
+ end
17
+
18
+ def update(lists)
19
+ @members = Hash.new{|hash,key|
20
+ hash[key] = []
21
+ }
22
+
23
+ (lists.keys - @lists.keys).each do|name|
24
+ list = Followings.new(@name % name)
25
+ list.listen{|kind,users|
26
+ notify kind,name,users
27
+ }
28
+ @lists[name] = list
29
+ notify :new, name
30
+ end
31
+
32
+ (@lists.keys - lists.keys).each do|x|
33
+ @lists.delete x
34
+ notify :del,x
35
+ end
36
+
37
+ lists.each do|name,users|
38
+ @lists[name].update users
39
+ end
40
+
41
+ lists.each do|list, users|
42
+ users.each do|user|
43
+ @members[user.screen_name] << list
44
+ end
45
+ end
46
+ end
47
+
48
+ def [](name)
49
+ @lists[name]
50
+ end
51
+
52
+ def invalidate(name)
53
+ @on_invalidated.call name
54
+ end
55
+
56
+ def on_invalidated(&f)
57
+ @on_invalidated = f
58
+ end
59
+
60
+ def find_by_screen_name(name)
61
+ return [] unless @members
62
+ @members[name]
63
+ end
64
+
65
+ def find_by_list_name(name)
66
+ @lists[name].users
67
+ end
68
+
69
+ def each(&f)
70
+ @lists.each do|name,users|
71
+ f.call name,users.users
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,30 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ module Atig
4
+ module Db
5
+ class Roman
6
+ Seq = %w[
7
+ a i u e o ka ki ku ke ko sa shi su se so
8
+ ta chi tsu te to na ni nu ne no ha hi fu he ho
9
+ ma mi mu me mo ya yu yo ra ri ru re ro
10
+ wa wo n
11
+ ga gi gu ge go za ji zu ze zo da de do
12
+ ba bi bu be bo pa pi pu pe po
13
+ kya kyu kyo sha shu sho cha chu cho
14
+ nya nyu nyo hya hyu hyo mya myu myo
15
+ rya ryu ryo
16
+ gya gyu gyo ja ju jo bya byu byo
17
+ pya pyu pyo
18
+ ].freeze
19
+
20
+ def make(n)
21
+ ret = []
22
+ begin
23
+ n, r = n.divmod(Seq.size)
24
+ ret << Seq[r]
25
+ end while n > 0
26
+ ret.reverse.join
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'forwardable'
4
+
5
+ module Atig
6
+ module Db
7
+ class SizedUniqArray
8
+ extend Forwardable
9
+ include Enumerable
10
+ def_delegators :@xs,:[]
11
+
12
+ attr_reader :size
13
+
14
+ def initialize(capacity)
15
+ @size = 0
16
+ @index = 0
17
+ @capacity = capacity
18
+ @xs = Array.new(capacity, nil)
19
+ end
20
+
21
+ def each(&f)
22
+ if @size < @capacity then
23
+ 0.upto(@size - 1) {|i|
24
+ f.call @xs[i]
25
+ }
26
+ else
27
+ 0.upto(@size - 1){|i|
28
+ f.call @xs[ (i + @index) % @capacity ]
29
+ }
30
+ end
31
+ end
32
+
33
+ def reverse_each(&f)
34
+ if @size < @capacity then
35
+ (@size - 1).downto(0) {|i|
36
+ f.call @xs[i]
37
+ }
38
+ else
39
+ (@size - 1).downto(0){|i|
40
+ f.call @xs[ (i + @index) % @capacity ]
41
+ }
42
+ end
43
+ end
44
+
45
+ def include?(item)
46
+ self.any?{|x|
47
+ x.id == item.id
48
+ }
49
+ end
50
+
51
+ def push(item)
52
+ return nil if include? item
53
+ i = @index
54
+ @xs[i] = item
55
+ @size = [ @size + 1, @capacity ].min
56
+ @index = ( @index + 1 ) % @capacity
57
+ i
58
+ end
59
+ alias_method :<<, :push
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,35 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ require 'sqlite3'
4
+
5
+ module Atig
6
+ module Db
7
+ class Sql
8
+ def initialize(name)
9
+ @name = name
10
+ end
11
+
12
+ def dump(obj)
13
+ [Marshal.dump(obj)].pack('m')
14
+ end
15
+
16
+ def load(text)
17
+ if text == nil then
18
+ nil
19
+ else
20
+ Marshal.load(text.unpack('m').first)
21
+ end
22
+ end
23
+
24
+ def execute(&f)
25
+ db = SQLite3::Database.new @name
26
+ begin
27
+ res = f.call db
28
+ ensure
29
+ db.close
30
+ end
31
+ res
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,147 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'atig/db/listenable'
3
+ require 'atig/db/transaction'
4
+ require 'sqlite3'
5
+ require 'atig/db/roman'
6
+ require 'atig/db/sql'
7
+ require 'base64'
8
+
9
+ class OpenStruct
10
+ def id; method_missing(:id) end
11
+ end
12
+
13
+ module Atig
14
+ module Db
15
+ class Statuses
16
+ include Listenable
17
+ include Transaction
18
+
19
+ Size = 400
20
+
21
+ def initialize(name)
22
+ @db = Sql.new name
23
+ @roman = Roman.new
24
+
25
+ unless File.exist? name then
26
+ @db.execute do|db|
27
+ db.execute %{create table status (
28
+ id integer primary key,
29
+ status_id text,
30
+ tid text,
31
+ sid text,
32
+ screen_name text,
33
+ user_id text,
34
+ created_at integer,
35
+ data blob);}
36
+
37
+ db.execute %{create table id (
38
+ id integer primary key,
39
+ screen_name text,
40
+ count integer);}
41
+
42
+ # thx to @L_star
43
+ # http://d.hatena.ne.jp/mzp/20100407#c
44
+ db.execute_batch %{
45
+ create index status_createdat on status(created_at);
46
+ create index status_sid on status(sid);
47
+ create index status_statusid on status(status_id);
48
+ create index status_tid on status(tid);
49
+ create index status_userid on status(user_id);
50
+
51
+ create index status_id on status(id);
52
+ create index id_id on id(id);
53
+ }
54
+ end
55
+ end
56
+ end
57
+
58
+ def add(opt)
59
+ @db.execute do|db|
60
+ id = opt[:status].id
61
+ return unless db.execute(%{SELECT id FROM status WHERE status_id = ?}, id).empty?
62
+
63
+ screen_name = opt[:user].screen_name
64
+ sum = db.get_first_value("SELECT sum(count) FROM id").to_i
65
+ count = db.get_first_value("SELECT count FROM id WHERE screen_name = ?", screen_name).to_i
66
+ entry = OpenStruct.new opt.merge(:tid => @roman.make(sum),
67
+ :sid => "#{screen_name}:#{@roman.make(count)}")
68
+ db.execute(%{INSERT INTO status
69
+ VALUES(NULL, :id, :tid, :sid, :screen_name, :user_id, :created_at, :data)},
70
+ :id => id,
71
+ :tid => entry.tid,
72
+ :sid => entry.sid,
73
+ :screen_name => screen_name,
74
+ :user_id => opt[:user].id,
75
+ :created_at => Time.parse(opt[:status].created_at).to_i,
76
+ :data => @db.dump(entry))
77
+ if count == 0 then
78
+ db.execute("INSERT INTO id VALUES(NULL,?,?)", screen_name, 1)
79
+ else
80
+ db.execute("UPDATE id SET count = ? WHERE screen_name = ?", count + 1, screen_name)
81
+ end
82
+ notify entry
83
+ end
84
+ end
85
+
86
+ def find_all(opt={})
87
+ find '1', 1, opt
88
+ end
89
+
90
+ def find_by_screen_name(name, opt={})
91
+ find 'screen_name',name, opt
92
+ end
93
+
94
+ def find_by_user(user, opt={})
95
+ find 'user_id', user.id, opt
96
+ end
97
+
98
+ def find_by_tid(tid)
99
+ find('tid', tid).first
100
+ end
101
+
102
+ def find_by_sid(tid)
103
+ find('sid', tid).first
104
+ end
105
+
106
+ def find_by_status_id(id)
107
+ find('status_id', id).first
108
+ end
109
+
110
+ def find_by_id(id)
111
+ find('id', id).first
112
+ end
113
+
114
+ def remove_by_id(id)
115
+ @db.execute do|db|
116
+ db.execute "DELETE FROM status WHERE id = ?",id
117
+ end
118
+ end
119
+
120
+ def cleanup
121
+ @db.execute do|db|
122
+ created_at = db.execute("SELECT created_at FROM status ORDER BY created_at DESC LIMIT 1 OFFSET ?", Size-1)
123
+ unless created_at.empty? then
124
+ db.execute "DELETE FROM status WHERE created_at < ?", created_at.first
125
+ end
126
+ db.execute "VACUUM status"
127
+ end
128
+ end
129
+
130
+ private
131
+ def find(lhs,rhs, opt={},&f)
132
+ query = "SELECT id,data FROM status WHERE #{lhs} = :rhs ORDER BY created_at DESC LIMIT :limit"
133
+ params = { :rhs => rhs, :limit => opt.fetch(:limit,20) }
134
+ res = []
135
+
136
+ @db.execute do|db|
137
+ db.execute(query,params) do|id,data,*_|
138
+ e = @db.load(data)
139
+ e.id = id.to_i
140
+ res << e
141
+ end
142
+ end
143
+ res
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,47 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+ require 'timeout'
3
+ require 'atig/util'
4
+
5
+ module Atig
6
+ module Db
7
+ module Transaction
8
+ include Util
9
+
10
+ def init
11
+ return if @queue
12
+
13
+ debug "transaction thread start"
14
+ @queue = SizedQueue.new 10
15
+ daemon do
16
+ f,src = @queue.pop
17
+ debug "transaction is poped at #{src}"
18
+
19
+ if respond_to?(:timeout_interval) && timeout_interval > 0 then
20
+ begin
21
+ timeout(timeout_interval){ f.call self }
22
+ rescue TimeoutError
23
+ debug "transaction is timeout at #{src}"
24
+ end
25
+ else
26
+ f.call self
27
+ end
28
+
29
+ debug "transaction is finished at #{src}"
30
+ end
31
+ end
32
+
33
+ def transaction(&f)
34
+ init
35
+
36
+ debug "transaction is registered"
37
+ @queue.push [ f, caller.first ]
38
+ end
39
+
40
+ def debug(s)
41
+ if respond_to? :log
42
+ log :debug, s
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ # -*- mode:ruby; coding:utf-8 -*-
2
+
3
+ module Atig
4
+ module ExceptionUtil
5
+ def safe(&f)
6
+ begin
7
+ f.call
8
+ rescue Exception => e
9
+ s = e.inspect + "\n"
10
+ e.backtrace.each do |l|
11
+ s += "\t#{l}\n"
12
+ end
13
+
14
+ log :error,s
15
+ end
16
+ end
17
+
18
+ def daemon(&f)
19
+ Thread.new do
20
+ loop{ safe { f.call }}
21
+ end
22
+ end
23
+
24
+ module_function :safe,:daemon
25
+ end
26
+ end