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,23 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/util'
|
|
4
|
+
|
|
5
|
+
module Atig
|
|
6
|
+
module Agent
|
|
7
|
+
class Cleanup
|
|
8
|
+
include Util
|
|
9
|
+
|
|
10
|
+
def initialize(context, api, db)
|
|
11
|
+
@log = context.log
|
|
12
|
+
daemon do
|
|
13
|
+
db.transaction do|t|
|
|
14
|
+
log :info, "cleanup"
|
|
15
|
+
t.cleanup
|
|
16
|
+
end
|
|
17
|
+
# once a day
|
|
18
|
+
sleep 60*60*24
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/util'
|
|
3
|
+
|
|
4
|
+
module Atig
|
|
5
|
+
module Agent
|
|
6
|
+
class Dm
|
|
7
|
+
include Util
|
|
8
|
+
|
|
9
|
+
def initialize(context, api, db)
|
|
10
|
+
@log = context.log
|
|
11
|
+
@api = api
|
|
12
|
+
@prev = nil
|
|
13
|
+
|
|
14
|
+
log :info, "initialize"
|
|
15
|
+
|
|
16
|
+
@api.repeat(600) do|t|
|
|
17
|
+
q = { :count => 200 }
|
|
18
|
+
if @prev
|
|
19
|
+
q.update :since_id => @prev
|
|
20
|
+
else
|
|
21
|
+
q.update :count => 1
|
|
22
|
+
end
|
|
23
|
+
dms = t.get("direct_messages", q)
|
|
24
|
+
log :debug, "You have #{dms.size} dm."
|
|
25
|
+
|
|
26
|
+
dms.reverse_each do|dm|
|
|
27
|
+
db.dms.transaction do|d|
|
|
28
|
+
d.add :status => dm, :user => dm.sender
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/util'
|
|
4
|
+
|
|
5
|
+
module Atig
|
|
6
|
+
module Agent
|
|
7
|
+
class Following
|
|
8
|
+
include Util
|
|
9
|
+
|
|
10
|
+
def initialize(context, api, db)
|
|
11
|
+
@opts = context.opts
|
|
12
|
+
@log = context.log
|
|
13
|
+
@db = db
|
|
14
|
+
log :info, "initialize"
|
|
15
|
+
|
|
16
|
+
api.repeat(3600){|t| update t }
|
|
17
|
+
@db.followings.on_invalidated{
|
|
18
|
+
log :info, "invalidated followings"
|
|
19
|
+
api.delay(0){|t| update t }
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def update(api)
|
|
24
|
+
if @db.followings.empty?
|
|
25
|
+
friends = api.page("statuses/friends/#{@db.me.id}", :users)
|
|
26
|
+
else
|
|
27
|
+
@db.me = api.post("account/update_profile")
|
|
28
|
+
return if @db.me.friends_count == @db.followings.size
|
|
29
|
+
friends = api.page("statuses/friends/#{@db.me.id}", :users)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if @opts.only
|
|
33
|
+
followers = api.page("followers/ids/#{@db.me.id}", :ids)
|
|
34
|
+
friends.each do|friend|
|
|
35
|
+
friend[:only] = !followers.include?(friend.id)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@db.followings.transaction do|d|
|
|
40
|
+
d.update friends
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/util'
|
|
4
|
+
require 'atig/agent/list'
|
|
5
|
+
|
|
6
|
+
module Atig
|
|
7
|
+
module Agent
|
|
8
|
+
class FullList < List
|
|
9
|
+
def entry_points
|
|
10
|
+
[ "#{@db.me.screen_name}/lists",
|
|
11
|
+
"#{@db.me.screen_name}/lists/subscriptions"
|
|
12
|
+
]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def interval
|
|
16
|
+
3600
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/util'
|
|
4
|
+
|
|
5
|
+
module Atig
|
|
6
|
+
module Agent
|
|
7
|
+
class List
|
|
8
|
+
include Util
|
|
9
|
+
|
|
10
|
+
def initialize(context, api, db)
|
|
11
|
+
@log = context.log
|
|
12
|
+
@db = db
|
|
13
|
+
log :info, "initialize"
|
|
14
|
+
|
|
15
|
+
@db.lists.on_invalidated{|name|
|
|
16
|
+
log :info, "invalidated #{name}"
|
|
17
|
+
api.delay(0){|t|
|
|
18
|
+
if name == :all then
|
|
19
|
+
full_update t
|
|
20
|
+
else
|
|
21
|
+
@db.lists[name].update t.page("#{@db.me.screen_name}/#{name}/members", :users, true)
|
|
22
|
+
end
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
api.repeat( interval ) do|t|
|
|
26
|
+
self.full_update t
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def full_update(t)
|
|
31
|
+
lists = entry_points.map{|entry|
|
|
32
|
+
t.page(entry, :lists, true)
|
|
33
|
+
}.flatten
|
|
34
|
+
|
|
35
|
+
users = {}
|
|
36
|
+
lists.map do|list|
|
|
37
|
+
name = if list.user.screen_name == @db.me.screen_name then
|
|
38
|
+
"#{list.slug}"
|
|
39
|
+
else
|
|
40
|
+
"#{list.user.screen_name}^#{list.slug}"
|
|
41
|
+
end
|
|
42
|
+
begin
|
|
43
|
+
users[name] =
|
|
44
|
+
t.page("#{list.user.screen_name}/#{list.slug}/members", :users, true)
|
|
45
|
+
rescue APIFailed => e
|
|
46
|
+
log :error, e.inspect
|
|
47
|
+
users[name] =
|
|
48
|
+
@db.lists.find_by_list_name(list.slug)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
@db.lists.update users
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/util'
|
|
4
|
+
|
|
5
|
+
module Atig; end
|
|
6
|
+
module Atig::Agent; end
|
|
7
|
+
|
|
8
|
+
class Atig::Agent::ListStatus
|
|
9
|
+
include Atig::Util
|
|
10
|
+
|
|
11
|
+
def initialize(context, api, db)
|
|
12
|
+
@log = context.log
|
|
13
|
+
@db = db
|
|
14
|
+
log :info, "initialize"
|
|
15
|
+
|
|
16
|
+
@prev = {}
|
|
17
|
+
api.repeat(60*5) do|t|
|
|
18
|
+
db.lists.each do |name, _|
|
|
19
|
+
log :debug, "retrieve #{name} statuses"
|
|
20
|
+
q = {}
|
|
21
|
+
q.update(:since_id => @prev[name]) if @prev.key?(name)
|
|
22
|
+
|
|
23
|
+
screen_name,slug = parse name
|
|
24
|
+
statuses = t.get("#{screen_name}/lists/#{slug}/statuses",q)
|
|
25
|
+
statuses.reverse_each do|status|
|
|
26
|
+
db.statuses.transaction do|d|
|
|
27
|
+
d.add(:status => status,
|
|
28
|
+
:user => status.user,
|
|
29
|
+
:source => :list,
|
|
30
|
+
:list => name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
@prev[name] = statuses[0].id if statuses && statuses.size > 0
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse(name)
|
|
38
|
+
if name.include? '^' then
|
|
39
|
+
name.split("^",2)
|
|
40
|
+
else
|
|
41
|
+
[@db.me.screen_name, name]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/agent/agent'
|
|
3
|
+
|
|
4
|
+
module Atig
|
|
5
|
+
module Agent
|
|
6
|
+
class Mention < Atig::Agent::Agent
|
|
7
|
+
def initialize(context, api, db); super end
|
|
8
|
+
def interval; 180 end
|
|
9
|
+
def path; '/statuses/mentions' end
|
|
10
|
+
def source; :mention end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/util'
|
|
4
|
+
require 'atig/agent/list'
|
|
5
|
+
|
|
6
|
+
module Atig
|
|
7
|
+
module Agent
|
|
8
|
+
class OtherList < List
|
|
9
|
+
def entry_points
|
|
10
|
+
[ "#{@db.me.screen_name}/lists/subscriptions" ]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def interval
|
|
14
|
+
3600
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'atig/util'
|
|
4
|
+
require 'atig/agent/list'
|
|
5
|
+
|
|
6
|
+
module Atig
|
|
7
|
+
module Agent
|
|
8
|
+
class OwnList < List
|
|
9
|
+
def entry_points
|
|
10
|
+
[ "#{@db.me.screen_name}/lists" ]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def interval
|
|
14
|
+
3600
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/util'
|
|
3
|
+
|
|
4
|
+
module Atig
|
|
5
|
+
module Agent
|
|
6
|
+
class StreamFollow
|
|
7
|
+
include Util
|
|
8
|
+
|
|
9
|
+
def initialize(context, api, db)
|
|
10
|
+
@log = context.log
|
|
11
|
+
@api = api
|
|
12
|
+
@prev = nil
|
|
13
|
+
|
|
14
|
+
return unless context.opts.stream
|
|
15
|
+
|
|
16
|
+
log :info, "initialize"
|
|
17
|
+
|
|
18
|
+
@api.delay(0)do|t|
|
|
19
|
+
@follows = context.opts.follow.split(',').map{|user|
|
|
20
|
+
t.get("users/show",:screen_name=>user).id
|
|
21
|
+
}.join(',')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@api.stream do|t|
|
|
26
|
+
Thread.pass until @follows
|
|
27
|
+
t.watch('statuses/filter', :follow => @follows) do |status|
|
|
28
|
+
if status and status.user
|
|
29
|
+
db.transaction do|d|
|
|
30
|
+
d.statuses.add :status => status, :user => status.user, :source => :stream_follow
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/agent/agent'
|
|
3
|
+
|
|
4
|
+
module Atig
|
|
5
|
+
module Agent
|
|
6
|
+
class Timeline < Atig::Agent::Agent
|
|
7
|
+
def initialize(context, api, db); super end
|
|
8
|
+
def interval; 30 end
|
|
9
|
+
def path; '/statuses/home_timeline' end
|
|
10
|
+
def source; :timeline end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- mode:ruby; coding:utf-8 -*-
|
|
2
|
+
require 'atig/util'
|
|
3
|
+
|
|
4
|
+
module Atig
|
|
5
|
+
module Agent
|
|
6
|
+
class UserStream
|
|
7
|
+
include Util
|
|
8
|
+
|
|
9
|
+
def initialize(context, api, db)
|
|
10
|
+
@log = context.log
|
|
11
|
+
@api = api
|
|
12
|
+
@prev = nil
|
|
13
|
+
|
|
14
|
+
return unless context.opts.stream
|
|
15
|
+
|
|
16
|
+
log :info, "initialize"
|
|
17
|
+
|
|
18
|
+
@api.stream do|t|
|
|
19
|
+
t.watch('user') do |status|
|
|
20
|
+
# @log.debug status.inspect
|
|
21
|
+
if status and status.user
|
|
22
|
+
db.statuses.transaction do|d|
|
|
23
|
+
d.add :status => status, :user => status.user, :source => :user_stream
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require 'atig/twitter_struct'
|
|
6
|
+
require 'atig/http'
|
|
7
|
+
|
|
8
|
+
module Atig
|
|
9
|
+
# from tig.rb
|
|
10
|
+
class BasicTwitter
|
|
11
|
+
attr_reader :limit, :remain, :reset
|
|
12
|
+
|
|
13
|
+
class APIFailed < StandardError; end
|
|
14
|
+
|
|
15
|
+
def initialize(context, api_base)
|
|
16
|
+
@log = context.log
|
|
17
|
+
@opts = context.opts
|
|
18
|
+
@api_base = api_base
|
|
19
|
+
@http = Atig::Http.new @log
|
|
20
|
+
|
|
21
|
+
@limit = @remain = 150
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def api(path, query = {}, opts = {})
|
|
25
|
+
path.sub!(%r{\A/+}, "")
|
|
26
|
+
|
|
27
|
+
uri = api_base
|
|
28
|
+
uri.path += path
|
|
29
|
+
uri.path += ".json" if path != "users/username_available"
|
|
30
|
+
uri.query = query.to_query_str unless query.empty?
|
|
31
|
+
|
|
32
|
+
header = {}
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
ret = request(uri, opts)
|
|
36
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
37
|
+
@log.error e.inspect
|
|
38
|
+
raise e.inspect
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if ret["X-RateLimit-Limit"] then
|
|
42
|
+
hourly_limit = ret["X-RateLimit-Limit"].to_i
|
|
43
|
+
unless hourly_limit.zero?
|
|
44
|
+
if @limit != hourly_limit
|
|
45
|
+
msg = "The rate limit per hour was changed: #{@limit} to #{hourly_limit}"
|
|
46
|
+
@log.info msg
|
|
47
|
+
@limit = hourly_limit
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if ret["X-RateLimit-Remaining"] then
|
|
53
|
+
@remain = ret["X-RateLimit-Remaining"].to_i
|
|
54
|
+
@log.debug "IP based limit: #{@remain}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if ret["X-RateLimit-Reset"] then
|
|
58
|
+
@reset = ret["X-RateLimit-Reset"].to_i
|
|
59
|
+
@log.debug "RateLimit Reset: #{@reset}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
case ret
|
|
63
|
+
when Net::HTTPOK # 200
|
|
64
|
+
# Avoid Twitter's invalid JSON
|
|
65
|
+
json = ret.body.strip.sub(/\A(?:false|true)\z/, "[\\&]")
|
|
66
|
+
|
|
67
|
+
res = JSON.parse(json)
|
|
68
|
+
if res.is_a?(Hash) && res["error"] # and not res["response"]
|
|
69
|
+
if @error != res["error"]
|
|
70
|
+
@error = res["error"]
|
|
71
|
+
log @error
|
|
72
|
+
end
|
|
73
|
+
raise APIFailed, res["error"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
TwitterStruct.make(res)
|
|
77
|
+
when Net::HTTPNoContent, # 204
|
|
78
|
+
Net::HTTPNotModified # 304
|
|
79
|
+
[]
|
|
80
|
+
when Net::HTTPBadRequest # 400: exceeded the rate limitation
|
|
81
|
+
if ret.key?("X-RateLimit-Reset")
|
|
82
|
+
@log.info "waiting for rate limit reset"
|
|
83
|
+
s = ret["X-RateLimit-Reset"].to_i - Time.now.to_i
|
|
84
|
+
if s > 0
|
|
85
|
+
sleep (s > 60 * 10) ? 60 * 10 : s # 10 分に一回はとってくるように
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
raise APIFailed, "#{ret.code}: #{ret.message}"
|
|
89
|
+
when Net::HTTPUnauthorized # 401
|
|
90
|
+
raise APIFailed, "#{ret.code}: #{ret.message}"
|
|
91
|
+
else
|
|
92
|
+
raise APIFailed, "Server Returned #{ret.code} #{ret.message}"
|
|
93
|
+
end
|
|
94
|
+
rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
|
|
95
|
+
raise APIFailed, e.inspect
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.http_methods(*methods)
|
|
99
|
+
methods.each do |m|
|
|
100
|
+
self.module_eval <<END
|
|
101
|
+
def #{m}(path, query = {}, opts = {})
|
|
102
|
+
opts.update( :method => :#{m})
|
|
103
|
+
api path, query, opts
|
|
104
|
+
end
|
|
105
|
+
END
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
http_methods :get, :post, :put, :delete
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
def api_base
|
|
113
|
+
URI(@opts.send @api_base)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|