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