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.
- data/.gitignore +24 -0
- data/Gemfile +3 -0
- data/README.mkdn +52 -0
- data/Rakefile +15 -0
- data/atig.gemspec +25 -0
- data/bin/atig +74 -0
- data/docs/OMakefile +32 -0
- data/docs/OMakeroot +45 -0
- data/docs/_static/allow.png +0 -0
- data/docs/_static/emacs.png +0 -0
- data/docs/_static/irc_setting.png +0 -0
- data/docs/_static/irssi.png +0 -0
- data/docs/_static/limechat.png +0 -0
- data/docs/_static/limechat_s.png +0 -0
- data/docs/_static/oauth_channel.png +0 -0
- data/docs/_static/screenshot.png +0 -0
- data/docs/_static/structure.png +0 -0
- data/docs/_static/verify.png +0 -0
- data/docs/changelog.rst +96 -0
- data/docs/commandline_options.rst +21 -0
- data/docs/commands.rst +84 -0
- data/docs/conf.py +194 -0
- data/docs/config.rst +159 -0
- data/docs/feature.rst +41 -0
- data/docs/graphics.graffle +1995 -0
- data/docs/hacking_guide.rst +43 -0
- data/docs/index.rst +109 -0
- data/docs/irc.rst +31 -0
- data/docs/options.rst +75 -0
- data/docs/quickstart.rst +89 -0
- data/docs/resize.sh +7 -0
- data/docs/tiarra.rst +2 -0
- data/docs/tig.rst +21 -0
- data/lib/atig.rb +19 -0
- data/lib/atig/agent.rb +8 -0
- data/lib/atig/agent/agent.rb +38 -0
- data/lib/atig/agent/clenup.rb +23 -0
- data/lib/atig/agent/dm.rb +35 -0
- data/lib/atig/agent/following.rb +45 -0
- data/lib/atig/agent/full_list.rb +20 -0
- data/lib/atig/agent/list.rb +55 -0
- data/lib/atig/agent/list_status.rb +46 -0
- data/lib/atig/agent/mention.rb +13 -0
- data/lib/atig/agent/other_list.rb +18 -0
- data/lib/atig/agent/own_list.rb +18 -0
- data/lib/atig/agent/stream_follow.rb +38 -0
- data/lib/atig/agent/timeline.rb +13 -0
- data/lib/atig/agent/user_stream.rb +31 -0
- data/lib/atig/basic_twitter.rb +116 -0
- data/lib/atig/bitly.rb +52 -0
- data/lib/atig/channel.rb +5 -0
- data/lib/atig/channel/channel.rb +17 -0
- data/lib/atig/channel/dm.rb +14 -0
- data/lib/atig/channel/list.rb +76 -0
- data/lib/atig/channel/mention.rb +20 -0
- data/lib/atig/channel/retweet.rb +28 -0
- data/lib/atig/channel/timeline.rb +74 -0
- data/lib/atig/command.rb +21 -0
- data/lib/atig/command/autofix.rb +58 -0
- data/lib/atig/command/command.rb +24 -0
- data/lib/atig/command/command_helper.rb +95 -0
- data/lib/atig/command/destroy.rb +44 -0
- data/lib/atig/command/dm.rb +31 -0
- data/lib/atig/command/favorite.rb +27 -0
- data/lib/atig/command/info.rb +50 -0
- data/lib/atig/command/limit.rb +15 -0
- data/lib/atig/command/location.rb +23 -0
- data/lib/atig/command/name.rb +18 -0
- data/lib/atig/command/option.rb +37 -0
- data/lib/atig/command/refresh.rb +18 -0
- data/lib/atig/command/reply.rb +37 -0
- data/lib/atig/command/retweet.rb +63 -0
- data/lib/atig/command/search.rb +51 -0
- data/lib/atig/command/spam.rb +26 -0
- data/lib/atig/command/status.rb +41 -0
- data/lib/atig/command/thread.rb +44 -0
- data/lib/atig/command/time.rb +32 -0
- data/lib/atig/command/uptime.rb +32 -0
- data/lib/atig/command/user.rb +42 -0
- data/lib/atig/command/user_info.rb +27 -0
- data/lib/atig/command/version.rb +49 -0
- data/lib/atig/command/whois.rb +39 -0
- data/lib/atig/db/db.rb +60 -0
- data/lib/atig/db/followings.rb +131 -0
- data/lib/atig/db/listenable.rb +22 -0
- data/lib/atig/db/lists.rb +76 -0
- data/lib/atig/db/roman.rb +30 -0
- data/lib/atig/db/sized_uniq_array.rb +62 -0
- data/lib/atig/db/sql.rb +35 -0
- data/lib/atig/db/statuses.rb +147 -0
- data/lib/atig/db/transaction.rb +47 -0
- data/lib/atig/exception_util.rb +26 -0
- data/lib/atig/gateway.rb +62 -0
- data/lib/atig/gateway/channel.rb +99 -0
- data/lib/atig/gateway/session.rb +326 -0
- data/lib/atig/http.rb +95 -0
- data/lib/atig/ifilter.rb +7 -0
- data/lib/atig/ifilter/expand_url.rb +74 -0
- data/lib/atig/ifilter/retweet.rb +14 -0
- data/lib/atig/ifilter/retweet_time.rb +16 -0
- data/lib/atig/ifilter/sanitize.rb +18 -0
- data/lib/atig/ifilter/strip.rb +15 -0
- data/lib/atig/ifilter/utf7.rb +26 -0
- data/lib/atig/ifilter/xid.rb +36 -0
- data/lib/atig/levenshtein.rb +49 -0
- data/lib/atig/monkey.rb +4 -0
- data/lib/atig/oauth-patch.rb +40 -0
- data/lib/atig/oauth.rb +55 -0
- data/lib/atig/ofilter.rb +4 -0
- data/lib/atig/ofilter/escape_url.rb +102 -0
- data/lib/atig/ofilter/footer.rb +20 -0
- data/lib/atig/ofilter/geo.rb +17 -0
- data/lib/atig/ofilter/short_url.rb +47 -0
- data/lib/atig/option.rb +90 -0
- data/lib/atig/scheduler.rb +79 -0
- data/lib/atig/search.rb +22 -0
- data/lib/atig/search_twitter.rb +21 -0
- data/lib/atig/sized_hash.rb +33 -0
- data/lib/atig/stream.rb +66 -0
- data/lib/atig/twitter.rb +79 -0
- data/lib/atig/twitter_struct.rb +63 -0
- data/lib/atig/unu.rb +27 -0
- data/lib/atig/update_checker.rb +53 -0
- data/lib/atig/url_escape.rb +62 -0
- data/lib/atig/util.rb +16 -0
- data/lib/atig/version.rb +3 -0
- data/lib/memory_profiler.rb +77 -0
- data/spec/command/autofix_spec.rb +35 -0
- data/spec/command/destroy_spec.rb +98 -0
- data/spec/command/dm_spec.rb +28 -0
- data/spec/command/favorite_spec.rb +55 -0
- data/spec/command/limit_spec.rb +27 -0
- data/spec/command/location_spec.rb +25 -0
- data/spec/command/name_spec.rb +19 -0
- data/spec/command/option_spec.rb +133 -0
- data/spec/command/refresh_spec.rb +22 -0
- data/spec/command/reply_spec.rb +79 -0
- data/spec/command/retweet_spec.rb +66 -0
- data/spec/command/spam_spec.rb +27 -0
- data/spec/command/status_spec.rb +44 -0
- data/spec/command/thread_spec.rb +91 -0
- data/spec/command/time_spec.rb +52 -0
- data/spec/command/uptime_spec.rb +55 -0
- data/spec/command/user_info_spec.rb +42 -0
- data/spec/command/user_spec.rb +50 -0
- data/spec/command/version_spec.rb +67 -0
- data/spec/command/whois_spec.rb +78 -0
- data/spec/db/followings_spec.rb +100 -0
- data/spec/db/listenable_spec.rb +32 -0
- data/spec/db/lists_spec.rb +104 -0
- data/spec/db/roman_spec.rb +17 -0
- data/spec/db/sized_uniq_array_spec.rb +63 -0
- data/spec/db/statuses_spec.rb +180 -0
- data/spec/ifilter/expand_url_spec.rb +44 -0
- data/spec/ifilter/retweet_spec.rb +28 -0
- data/spec/ifilter/retweet_time_spec.rb +25 -0
- data/spec/ifilter/sanitize_spec.rb +25 -0
- data/spec/ifilter/sid_spec.rb +29 -0
- data/spec/ifilter/strip_spec.rb +23 -0
- data/spec/ifilter/tid_spec.rb +29 -0
- data/spec/ifilter/utf7_spec.rb +30 -0
- data/spec/levenshtein_spec.rb +24 -0
- data/spec/ofilter/escape_url_spec.rb +50 -0
- data/spec/ofilter/footer_spec.rb +32 -0
- data/spec/ofilter/geo_spec.rb +33 -0
- data/spec/ofilter/short_url_spec.rb +127 -0
- data/spec/option_spec.rb +91 -0
- data/spec/sized_hash_spec.rb +45 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/update_checker_spec.rb +55 -0
- metadata +326 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/command/command'
|
|
3
|
+
begin
|
|
4
|
+
require 'jcode'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Atig
|
|
9
|
+
module Command
|
|
10
|
+
class Status < Atig::Command::Command
|
|
11
|
+
def initialize(*args); super end
|
|
12
|
+
def command_name; %w(status) end
|
|
13
|
+
|
|
14
|
+
def action(target, mesg, command, args)
|
|
15
|
+
if args.empty?
|
|
16
|
+
yield "/me #{command} blah blah"
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
text = mesg.split(" ", 2)[1]
|
|
20
|
+
previous,*_ = db.statuses.find_by_user( db.me, :limit => 1)
|
|
21
|
+
if previous and
|
|
22
|
+
((::Time.now - ::Time.parse(previous.status.created_at)).to_i < 60*60*24 rescue true) and
|
|
23
|
+
text.strip == previous.status.text.strip
|
|
24
|
+
yield "You can't submit the same status twice in a row."
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
q = gateway.output_message(:status => text)
|
|
28
|
+
|
|
29
|
+
if q[:status].each_char.to_a.size > 140 then
|
|
30
|
+
yield "You can't submit the status over 140 chars"
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
api.delay(0, :retry=>3) do|t|
|
|
35
|
+
ret = t.post("statuses/update", q)
|
|
36
|
+
gateway.update_status ret,target
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/command/command'
|
|
3
|
+
require 'atig/command/info'
|
|
4
|
+
|
|
5
|
+
module Atig
|
|
6
|
+
module Command
|
|
7
|
+
class Thread < Atig::Command::Command
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def command_name; %w(thread) end
|
|
13
|
+
|
|
14
|
+
def action(target, mesg, command, args)
|
|
15
|
+
if args.empty?
|
|
16
|
+
yield "/me #{command} <ID> [<NUM>]"
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
tid, num = args
|
|
21
|
+
count = 10 unless (1..20).include?(count = num.to_i)
|
|
22
|
+
|
|
23
|
+
if entry = Info.find_status(db, tid) then
|
|
24
|
+
chain(entry,count){|x|
|
|
25
|
+
gateway[target].message x, Net::IRC::Constants::NOTICE
|
|
26
|
+
}
|
|
27
|
+
else
|
|
28
|
+
yield "No such ID : #{tid}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def chain(entry,count, &f)
|
|
33
|
+
if count <= 0 then
|
|
34
|
+
return
|
|
35
|
+
elsif id = entry.status.in_reply_to_status_id then
|
|
36
|
+
Info.status(db, api, id){|next_|
|
|
37
|
+
chain(next_, count - 1, &f)
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
f.call entry
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'atig/command/command'
|
|
5
|
+
|
|
6
|
+
module Atig
|
|
7
|
+
module Command
|
|
8
|
+
class Time < Atig::Command::Command
|
|
9
|
+
def command_name; %w(time) end
|
|
10
|
+
|
|
11
|
+
def action(target, mesg, command, args)
|
|
12
|
+
if args.empty?
|
|
13
|
+
yield "/me #{command} <NICK>"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
nick, *_ = args
|
|
17
|
+
Info.user(db, api, nick){|user|
|
|
18
|
+
offset = user.utc_offset
|
|
19
|
+
time = "TIME :%s%s (%s)" % [
|
|
20
|
+
(::Time.now + offset).utc.iso8601[0, 19],
|
|
21
|
+
"%+.2d:%.2d" % (offset/60).divmod(60),
|
|
22
|
+
user.time_zone
|
|
23
|
+
]
|
|
24
|
+
entry = TwitterStruct.make('user' => user,
|
|
25
|
+
'status' => { 'text' =>
|
|
26
|
+
Net::IRC.ctcp_encode(time) })
|
|
27
|
+
gateway[target].message entry, Net::IRC::Constants::NOTICE
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/command/command'
|
|
4
|
+
|
|
5
|
+
module Atig
|
|
6
|
+
module Command
|
|
7
|
+
class Uptime < Atig::Command::Command
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
super
|
|
10
|
+
@time = ::Time.now
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def command_name; "uptime" end
|
|
14
|
+
|
|
15
|
+
def action(target, mesg, command, args)
|
|
16
|
+
yield format(::Time.now - @time)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def format(x)
|
|
20
|
+
day , y = x.divmod(60*60*24)
|
|
21
|
+
hour, z = y.divmod(60*60)
|
|
22
|
+
min , sec = z.divmod(60)
|
|
23
|
+
|
|
24
|
+
s = ""
|
|
25
|
+
s += "#{day} days " if day > 0
|
|
26
|
+
s += "%02d:" % hour if hour > 0
|
|
27
|
+
s += "%02d:%02d" % [min,sec]
|
|
28
|
+
s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'atig/command/command'
|
|
5
|
+
|
|
6
|
+
module Atig
|
|
7
|
+
module Command
|
|
8
|
+
class User < Atig::Command::Command
|
|
9
|
+
def initialize(*args); super end
|
|
10
|
+
def command_name; %w(user u) end
|
|
11
|
+
|
|
12
|
+
def action(target, mesg, command, args)
|
|
13
|
+
if args.empty?
|
|
14
|
+
yield "/me #{command} <NICK> [<NUM>]"
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
nick, num,*_ = args
|
|
18
|
+
|
|
19
|
+
count = 20 unless (1..200).include?(count = num.to_i)
|
|
20
|
+
api.delay(0) do|t|
|
|
21
|
+
begin
|
|
22
|
+
statuses = t.get("statuses/user_timeline",
|
|
23
|
+
{ :count => count, :screen_name => nick})
|
|
24
|
+
statuses.reverse_each do|status|
|
|
25
|
+
db.statuses.transaction do|d|
|
|
26
|
+
d.add :status => status, :user => status.user, :source => :user
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
db.statuses.
|
|
31
|
+
find_by_screen_name(nick, :limit=>count).
|
|
32
|
+
reverse_each do|entry|
|
|
33
|
+
gateway[target].message entry, Net::IRC::Constants::NOTICE
|
|
34
|
+
end
|
|
35
|
+
rescue Twitter::APIFailed => e
|
|
36
|
+
yield e.to_s
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/command/command'
|
|
4
|
+
|
|
5
|
+
module Atig
|
|
6
|
+
module Command
|
|
7
|
+
class UserInfo < Atig::Command::Command
|
|
8
|
+
def initialize(*args); super end
|
|
9
|
+
def command_name; %w(bio userinfo) end
|
|
10
|
+
|
|
11
|
+
def action(target, mesg, command,args)
|
|
12
|
+
if args.empty?
|
|
13
|
+
yield "/me #{command} <ID>"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
nick,*_ = args
|
|
17
|
+
|
|
18
|
+
Info.user(db, api, nick)do|user|
|
|
19
|
+
entry = TwitterStruct.make('user' => user,
|
|
20
|
+
'status' => { 'text' =>
|
|
21
|
+
Net::IRC.ctcp_encode(user.description) })
|
|
22
|
+
gateway[target].message entry, Net::IRC::Constants::NOTICE
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/twitter'
|
|
4
|
+
require 'atig/command/command'
|
|
5
|
+
|
|
6
|
+
module Atig
|
|
7
|
+
module Command
|
|
8
|
+
class Version < Atig::Command::Command
|
|
9
|
+
def command_name; %w(version) end
|
|
10
|
+
|
|
11
|
+
def action(target, mesg, command,args)
|
|
12
|
+
if args.empty?
|
|
13
|
+
yield "/me #{command} <ID>"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
nick,*_ = args
|
|
17
|
+
|
|
18
|
+
entries = db.statuses.find_by_screen_name(nick, :limit => 1)
|
|
19
|
+
if entries && !entries.empty? then
|
|
20
|
+
entry = TwitterStruct.make('user' => entries.first.user,
|
|
21
|
+
'status' => { 'text' =>
|
|
22
|
+
format(entries.first.status.source) })
|
|
23
|
+
gateway[target].message entry, Net::IRC::Constants::NOTICE
|
|
24
|
+
else
|
|
25
|
+
api.delay(0) do|t|
|
|
26
|
+
begin
|
|
27
|
+
user = t.get("users/show", { :screen_name => nick})
|
|
28
|
+
db.statuses.transaction do|d|
|
|
29
|
+
d.add :user => user, :status => user.status, :source => :version
|
|
30
|
+
entry = TwitterStruct.make('user' => user,
|
|
31
|
+
'status' => { 'text' =>
|
|
32
|
+
format(user.status.source) })
|
|
33
|
+
gateway[target].message entry, Net::IRC::Constants::NOTICE
|
|
34
|
+
end
|
|
35
|
+
rescue Twitter::APIFailed => e
|
|
36
|
+
yield e.to_s
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def format(source)
|
|
43
|
+
version = source.gsub(/<[^>]*>/, "").strip
|
|
44
|
+
version << " <#{$1}>" if source =~ / href="([^"]+)/
|
|
45
|
+
Net::IRC.ctcp_encode version
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/command/info'
|
|
3
|
+
require 'time'
|
|
4
|
+
module Atig
|
|
5
|
+
module Command
|
|
6
|
+
class Whois < Atig::Command::Command
|
|
7
|
+
def command_name; %w(whois) end
|
|
8
|
+
|
|
9
|
+
def action(target, mesg, command,args)
|
|
10
|
+
if args.empty?
|
|
11
|
+
yield "/me #{command} <ID>"
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
nick,*_ = args
|
|
15
|
+
|
|
16
|
+
Atig::Command::Info::user(db, api, nick) do|user|
|
|
17
|
+
id = "id=#{user.id}"
|
|
18
|
+
host = "twitter.com"
|
|
19
|
+
host += "/protected" if user.protected
|
|
20
|
+
desc = user.name
|
|
21
|
+
desc = "#{desc} / #{user.description}".gsub(/\s+/, " ") if user.description and not user.description.empty?
|
|
22
|
+
signon_at = ::Time.parse(user.created_at).to_i rescue 0
|
|
23
|
+
idle_sec = (::Time.now - (user.status ? ::Time.parse(user.status.created_at) : signon_at)).to_i rescue 0
|
|
24
|
+
location = user.location
|
|
25
|
+
location = "SoMa neighborhood of San Francisco, CA" if location.nil? or location.empty?
|
|
26
|
+
|
|
27
|
+
send Net::IRC::Constants::RPL_WHOISUSER, nick, id, host, "*", desc
|
|
28
|
+
send Net::IRC::Constants::RPL_WHOISSERVER, nick, host, location
|
|
29
|
+
send Net::IRC::Constants::RPL_WHOISIDLE, nick, "#{idle_sec}", "#{signon_at}", "seconds idle, signon time"
|
|
30
|
+
send Net::IRC::Constants::RPL_ENDOFWHOIS, nick, "End of WHOIS list"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def send(command,nick,*params)
|
|
35
|
+
gateway.post gateway.server_name, command, db.me.screen_name, nick, *params
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/atig/db/db.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/db/followings'
|
|
4
|
+
require 'atig/db/statuses'
|
|
5
|
+
require 'atig/db/lists'
|
|
6
|
+
require 'atig/util'
|
|
7
|
+
require 'thread'
|
|
8
|
+
require 'set'
|
|
9
|
+
require 'fileutils'
|
|
10
|
+
require 'tmpdir'
|
|
11
|
+
|
|
12
|
+
module Atig
|
|
13
|
+
module Db
|
|
14
|
+
class Db
|
|
15
|
+
include Util
|
|
16
|
+
attr_reader :followings, :statuses, :dms, :lists
|
|
17
|
+
attr_accessor :me
|
|
18
|
+
Path = ::Dir.tmpdir
|
|
19
|
+
VERSION = 4
|
|
20
|
+
|
|
21
|
+
def initialize(context, opt={})
|
|
22
|
+
@log = context.log
|
|
23
|
+
@me = opt[:me]
|
|
24
|
+
|
|
25
|
+
@followings = Followings.new dir('following')
|
|
26
|
+
@statuses = Statuses.new dir('status')
|
|
27
|
+
@dms = Statuses.new dir('dm')
|
|
28
|
+
@lists = Lists.new dir('lists.%s')
|
|
29
|
+
|
|
30
|
+
log :info, "initialize"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dir(id)
|
|
34
|
+
dir = File.expand_path "atig/#{@me.screen_name}/", Path
|
|
35
|
+
log :debug, "db(#{id}) = #{dir}"
|
|
36
|
+
FileUtils.mkdir_p dir
|
|
37
|
+
File.expand_path "#{id}.#{VERSION}.db", dir
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def transaction(&f)
|
|
41
|
+
@followings.transaction do|_|
|
|
42
|
+
@statuses.transaction do|_|
|
|
43
|
+
@dms.transaction do|_|
|
|
44
|
+
@lists.transaction do|_|
|
|
45
|
+
f.call self
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cleanup
|
|
53
|
+
transaction do
|
|
54
|
+
@statuses.cleanup
|
|
55
|
+
@dms.cleanup
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/db/listenable'
|
|
4
|
+
require 'atig/db/transaction'
|
|
5
|
+
require 'atig/db/sql'
|
|
6
|
+
|
|
7
|
+
module Atig
|
|
8
|
+
module Db
|
|
9
|
+
class Followings
|
|
10
|
+
include Listenable
|
|
11
|
+
include Transaction
|
|
12
|
+
|
|
13
|
+
def initialize(name)
|
|
14
|
+
@db = Sql.new name
|
|
15
|
+
|
|
16
|
+
unless File.exist? name then
|
|
17
|
+
@db.execute do|db|
|
|
18
|
+
db.execute %{create table users (
|
|
19
|
+
id integer primary key,
|
|
20
|
+
screen_name text,
|
|
21
|
+
user_id text,
|
|
22
|
+
protected bool,
|
|
23
|
+
only bool,
|
|
24
|
+
data blob);}
|
|
25
|
+
db.execute %{
|
|
26
|
+
create index users_screen on users (screen_name);
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@users = []
|
|
32
|
+
@on_invalidated = lambda{}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def size
|
|
36
|
+
@db.execute do|db|
|
|
37
|
+
db.get_first_value('SELECT COUNT(*) FROM users').to_i
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def empty?
|
|
42
|
+
@db.execute do|db|
|
|
43
|
+
db.get_first_value('SELECT * FROM users LIMIT 1') == nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def invalidate
|
|
48
|
+
@on_invalidated.call
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def on_invalidated(&f)
|
|
52
|
+
@on_invalidated = f
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def users
|
|
56
|
+
@db.execute{|db|
|
|
57
|
+
db.execute("SELECT data FROM users").map{|data|
|
|
58
|
+
@db.load data[0]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def exists?(db, templ, *args)
|
|
64
|
+
db.get_first_value("SELECT * FROM users WHERE #{templ} LIMIT 1",*args) != nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def may_notify(mode, xs)
|
|
68
|
+
unless xs.empty? then
|
|
69
|
+
notify mode, xs
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def update(users)
|
|
74
|
+
@db.execute do|db|
|
|
75
|
+
may_notify :join, users.select{|u|
|
|
76
|
+
not exists?(db,
|
|
77
|
+
"screen_name = ?",
|
|
78
|
+
u.screen_name)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
names = users.map{|u| u.screen_name.inspect }.join(",")
|
|
82
|
+
parts =
|
|
83
|
+
may_notify :part, db.execute(%{SELECT screen_name,data FROM users
|
|
84
|
+
WHERE screen_name NOT IN (#{names})}).map{|_,data|
|
|
85
|
+
@db.load(data)
|
|
86
|
+
}
|
|
87
|
+
db.execute(%{DELETE FROM users
|
|
88
|
+
WHERE screen_name NOT IN (#{names})})
|
|
89
|
+
|
|
90
|
+
may_notify :mode, users.select{|u|
|
|
91
|
+
exists?(db,
|
|
92
|
+
"screen_name = ? AND (protected != ? OR only != ?)",
|
|
93
|
+
u.screen_name, u.protected, u.only)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
users.each do|user|
|
|
97
|
+
id = db.get_first_value('SELECT id FROM users WHERE user_id = ? LIMIT 1', user.id)
|
|
98
|
+
if id then
|
|
99
|
+
db.execute("UPDATE users SET screen_name = ?, protected = ?, only = ?, data = ? WHERE id = ?",
|
|
100
|
+
user.screen_name,
|
|
101
|
+
user.protected,
|
|
102
|
+
user.only,
|
|
103
|
+
@db.dump(user),
|
|
104
|
+
id)
|
|
105
|
+
else
|
|
106
|
+
db.execute("INSERT INTO users
|
|
107
|
+
VALUES(NULL, :screen_name, :user_id, :protected, :only, :data)",
|
|
108
|
+
:screen_name => user.screen_name,
|
|
109
|
+
:user_id => user.id,
|
|
110
|
+
:protected => user.protected,
|
|
111
|
+
:only => user.only,
|
|
112
|
+
:data => @db.dump(user))
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def find_by_screen_name(name)
|
|
119
|
+
@db.execute do|db|
|
|
120
|
+
@db.load db.get_first_value('SELECT data FROM users WHERE screen_name = ? LIMIT 1', name)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def include?(user)
|
|
125
|
+
@db.execute do|db|
|
|
126
|
+
exists? db,'user_id = ?', user.id
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|