jugyo-termtter 0.7.0 → 0.7.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +19 -9
- data/README.rdoc +20 -0
- data/Rakefile +3 -2
- data/bin/termtter +11 -5
- data/lib/filter/expand-tinyurl.rb +8 -1
- data/lib/filter/fib.rb +13 -0
- data/lib/filter/yhara.rb +1 -1
- data/lib/plugin/bomb.rb +26 -0
- data/lib/plugin/confirm.rb +24 -0
- data/lib/plugin/favorite.rb +25 -4
- data/lib/plugin/fib.rb +2 -1
- data/lib/plugin/filter.rb +1 -3
- data/lib/plugin/follow.rb +40 -0
- data/lib/plugin/group.rb +40 -0
- data/lib/plugin/history.rb +53 -0
- data/lib/plugin/keyword.rb +1 -1
- data/lib/plugin/log.rb +8 -2
- data/lib/plugin/plugin.rb +8 -8
- data/lib/plugin/quicklook.rb +36 -0
- data/lib/plugin/reload.rb +3 -0
- data/lib/plugin/shell.rb +2 -3
- data/lib/plugin/sl.rb +3 -0
- data/lib/plugin/spam.rb +7 -0
- data/lib/plugin/standard_plugins.rb +10 -1
- data/lib/plugin/stdout.rb +25 -10
- data/lib/plugin/uri-open.rb +10 -4
- data/lib/plugin/yhara.rb +142 -0
- data/lib/termtter.rb +173 -37
- data/run_termtter.rb +0 -2
- data/test/test_termtter.rb +0 -22
- metadata +38 -11
- data/lib/filter.rb +0 -28
data/lib/plugin/keyword.rb
CHANGED
@@ -12,5 +12,5 @@ end
|
|
12
12
|
# keyword.rb
|
13
13
|
# provides a keyword watching method
|
14
14
|
# example config
|
15
|
-
# configatron.timeline_format = '<%= color(time, 90) %> <%= color(status, s.has_keyword ? 4 : status_color) %> <%= color(id, 90) %>'
|
15
|
+
# configatron.plugins.stdout.timeline_format = '<%= color(time, 90) %> <%= color(status, s.has_keyword ? 4 : status_color) %> <%= color(id, 90) %>'
|
16
16
|
# configatron.plugins.keyword.keywords = [ /motemen/ ]
|
data/lib/plugin/log.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
module Termtter::Client
|
2
2
|
public_storage[:log] = []
|
3
|
+
configatron.plugins.log.set_default('max_size', 1/0.0)
|
3
4
|
|
4
5
|
add_help '/WORD', 'Search log for WORD'
|
5
6
|
|
6
|
-
add_hook do |statuses,event|
|
7
|
+
add_hook do |statuses, event|
|
7
8
|
case event
|
8
9
|
when :update_friends_timeline
|
9
10
|
public_storage[:log] += statuses
|
11
|
+
max_size = configatron.plugins.log.max_size
|
12
|
+
if public_storage[:log].size > max_size
|
13
|
+
public_storage[:log] = public_storage[:log][-max_size..-1]
|
14
|
+
end
|
15
|
+
public_storage[:log] = public_storage[:log].uniq.sort_by{|a| a.created_at} if statuses.first
|
10
16
|
end
|
11
17
|
end
|
12
18
|
|
13
|
-
add_command %r'^/(.+)' do |m,t|
|
19
|
+
add_command %r'^/(.+)' do |m, t|
|
14
20
|
pat = Regexp.new(m[1])
|
15
21
|
statuses = public_storage[:log].select { |s| s.text =~ pat }
|
16
22
|
call_hooks(statuses, :list_friends_timeline, t)
|
data/lib/plugin/plugin.rb
CHANGED
@@ -16,21 +16,21 @@ module Termtter::Client
|
|
16
16
|
|
17
17
|
add_help 'plugins', 'Show list of plugins'
|
18
18
|
add_command /^plugins$/ do |m, t|
|
19
|
-
puts public_storage[:plugins].join("\n")
|
19
|
+
puts public_storage[:plugins].sort.join("\n")
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.find_plugin_candidates(a, b)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
public_storage[:plugins].grep(/^#{Regexp.quote a}/i)
|
27
|
-
end.
|
28
|
-
map {|u| b % u }
|
23
|
+
public_storage[:plugins].
|
24
|
+
grep(/^#{Regexp.quote a}/i).
|
25
|
+
map {|u| b % u }
|
29
26
|
end
|
30
27
|
|
31
28
|
add_completion do |input|
|
32
|
-
|
29
|
+
case input
|
30
|
+
when /^(plugin)\s+(.+)/
|
33
31
|
find_plugin_candidates $2, "#{$1} %s"
|
32
|
+
when /^(plugin)\s+$/
|
33
|
+
public_storage[:plugins].sort
|
34
34
|
else
|
35
35
|
%w[ plugin plugins ].grep(/^#{Regexp.quote input}/)
|
36
36
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'pathname'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
configatron.plugins.quicklook.set_default(:quicklook_tmpdir, "#{Dir.tmpdir}/termtter-quicklook-tmpdir")
|
7
|
+
tmpdir = Pathname.new(configatron.plugins.quicklook.quicklook_tmpdir)
|
8
|
+
tmpdir.mkdir unless tmpdir.exist?
|
9
|
+
|
10
|
+
def quicklook(url)
|
11
|
+
tmpdir = Pathname.new(configatron.plugins.quicklook.quicklook_tmpdir)
|
12
|
+
path = tmpdir + Pathname.new(url).basename
|
13
|
+
|
14
|
+
Thread.new do
|
15
|
+
open(path, 'w') do |f|
|
16
|
+
f.write(open(url).read)
|
17
|
+
end
|
18
|
+
system("qlmanage -p #{path} > /dev/null 2>&1")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Termtter::Client
|
23
|
+
add_command %r'^(?:quicklook|ql)\s+(\w+)$' do |m,t|
|
24
|
+
id = m[1]
|
25
|
+
status = t.show(id).first
|
26
|
+
|
27
|
+
if (status)
|
28
|
+
uris = URI.regexp.match(status.text).to_a
|
29
|
+
quicklook(uris.first) unless uris.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# quicklook.rb
|
35
|
+
# TODO:
|
36
|
+
# Close quicklook window automatically.
|
data/lib/plugin/shell.rb
CHANGED
data/lib/plugin/sl.rb
ADDED
data/lib/plugin/spam.rb
ADDED
@@ -32,6 +32,14 @@ module Termtter::Client
|
|
32
32
|
call_hooks(t.show(m[1]), :show, t)
|
33
33
|
end
|
34
34
|
|
35
|
+
# TODO: Change colors when remaining_hits is low.
|
36
|
+
# TODO: Simmulate remaining_hits.
|
37
|
+
add_command /^(limit|lm)\s*$/ do |m, t|
|
38
|
+
limit = t.get_rate_limit_status
|
39
|
+
puts "=> #{limit.remaining_hits}/#{limit.hourly_limit}"
|
40
|
+
end
|
41
|
+
|
42
|
+
|
35
43
|
add_command /^pause\s*$/ do |m, t|
|
36
44
|
pause
|
37
45
|
end
|
@@ -50,6 +58,7 @@ exit,e Exit
|
|
50
58
|
help Print this help message
|
51
59
|
list,l List the posts in your friends timeline
|
52
60
|
list,l USERNAME List the posts in the the given user's timeline
|
61
|
+
limit,lm Show the API limit status
|
53
62
|
pause Pause updating
|
54
63
|
update,u TEXT Post a new message
|
55
64
|
resume Resume updating
|
@@ -124,7 +133,7 @@ show ID Show a single status
|
|
124
133
|
end
|
125
134
|
|
126
135
|
add_completion do |input|
|
127
|
-
standard_commands = %w[exit help list pause update resume replies search show]
|
136
|
+
standard_commands = %w[exit help list pause update resume replies search show limit]
|
128
137
|
case input
|
129
138
|
when /^(list|l)?\s+(.*)/
|
130
139
|
find_user_candidates $2, "#{$1} %s"
|
data/lib/plugin/stdout.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
|
+
require 'highline'
|
1
2
|
require 'erb'
|
2
3
|
|
3
|
-
configatron.set_default(
|
4
|
+
configatron.plugins.stdout.set_default(
|
5
|
+
:colors,
|
6
|
+
[:white, :red, :green, :yellow, :blue, :magenta, :cyan])
|
7
|
+
configatron.plugins.stdout.set_default(
|
4
8
|
:timeline_format,
|
5
9
|
'<%= color(time, 90) %> <%= color(status, status_color) %> <%= color(id, 90) %>')
|
6
10
|
|
7
|
-
|
11
|
+
$highline = HighLine.new
|
12
|
+
|
13
|
+
if win?
|
8
14
|
require 'kconv'
|
9
15
|
def color(str, num)
|
10
16
|
str.to_s.tosjis
|
@@ -13,21 +19,24 @@ if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
|
|
13
19
|
STDOUT.puts(str.tosjis)
|
14
20
|
end
|
15
21
|
else
|
16
|
-
def color(str,
|
17
|
-
|
22
|
+
def color(str, value)
|
23
|
+
case value
|
24
|
+
when String, Symbol
|
25
|
+
$highline.color(str, value)
|
26
|
+
else
|
27
|
+
"\e[#{value}m#{str}\e[0m"
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
20
31
|
|
21
32
|
Termtter::Client.add_hook do |statuses, event|
|
22
|
-
colors = %w(0 31 32 33 34 35 36 91 92 93 94 95 96)
|
23
|
-
|
24
33
|
case event
|
25
34
|
when :update_friends_timeline, :list_friends_timeline, :list_user_timeline, :show, :replies
|
26
35
|
unless statuses.empty?
|
27
36
|
statuses.reverse! if event == :update_friends_timeline
|
28
37
|
statuses.each do |s|
|
29
38
|
text = s.text
|
30
|
-
status_color = colors[s.user_screen_name.hash % colors.size]
|
39
|
+
status_color = configatron.plugins.stdout.colors[s.user_screen_name.hash % configatron.plugins.stdout.colors.size]
|
31
40
|
status = "#{s.user_screen_name}: #{text}"
|
32
41
|
if s.in_reply_to_status_id
|
33
42
|
status += " (reply to #{s.in_reply_to_status_id})"
|
@@ -43,18 +52,24 @@ Termtter::Client.add_hook do |statuses, event|
|
|
43
52
|
|
44
53
|
id = s.id
|
45
54
|
|
46
|
-
puts ERB.new(configatron.timeline_format).result(binding)
|
55
|
+
puts ERB.new(configatron.plugins.stdout.timeline_format).result(binding)
|
47
56
|
end
|
48
57
|
end
|
49
58
|
when :search
|
50
59
|
statuses.each do |s|
|
51
60
|
text = s.text
|
52
|
-
status_color = colors[s.user_screen_name.hash % colors.size]
|
61
|
+
status_color = configatron.plugins.stdout.colors[s.user_screen_name.hash % configatron.plugins.stdout.colors.size]
|
53
62
|
|
54
63
|
status = "#{s.user_screen_name}: #{text}"
|
55
64
|
time = "(#{s.created_at.strftime('%m-%d %H:%M')})"
|
56
65
|
id = s.id
|
57
|
-
puts ERB.new(configatron.timeline_format).result(binding)
|
66
|
+
puts ERB.new(configatron.plugins.stdout.timeline_format).result(binding)
|
58
67
|
end
|
59
68
|
end
|
60
69
|
end
|
70
|
+
|
71
|
+
# stdout.rb
|
72
|
+
# output statuses to stdout
|
73
|
+
# example config
|
74
|
+
# configatron.plugins.stdout.colors = [:white, :red, :green, :yellow, :blue, :magenta, :cyan]
|
75
|
+
# configatron.plugins.stdout.timeline_format = '<%= color(time, 90) %> <%= color(status, status_color) %> <%= color(id, 90) %>'
|
data/lib/plugin/uri-open.rb
CHANGED
@@ -10,11 +10,17 @@ module Termtter::Client
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.open_uri(uri)
|
13
|
-
|
14
|
-
|
15
|
-
system 'firefox', uri
|
13
|
+
unless configatron.plugins.uri_open.browser.nil?
|
14
|
+
system configatron.plugins.uri_open.browser, uri
|
16
15
|
else
|
17
|
-
|
16
|
+
case RUBY_PLATFORM
|
17
|
+
when /linux/
|
18
|
+
system 'firefox', uri
|
19
|
+
when /mswin(?!ce)|mingw|bccwin/
|
20
|
+
system 'explorer', uri
|
21
|
+
else
|
22
|
+
system 'open', uri
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
20
26
|
|
data/lib/plugin/yhara.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
if RUBY_VERSION < "1.8.7"
|
2
|
+
class Array
|
3
|
+
def choice
|
4
|
+
at(rand(size))
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# based on new-harizon.rb
|
10
|
+
module Yharian
|
11
|
+
|
12
|
+
VOICES =
|
13
|
+
%w(Agnes Albert Bad\ News Bahh Bells Boing Bruce Bubbles Cellos Deranged Fred Hysterical Junior Kathy Pipe\ Organ Princess Ralph Trinoids Vicki Victoria Whisper Zarvox)
|
14
|
+
|
15
|
+
|
16
|
+
class Speaker
|
17
|
+
attr_reader :name
|
18
|
+
def initialize(name)
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
|
22
|
+
def talk(context)
|
23
|
+
n = 7
|
24
|
+
words = (0..rand(n)).map { %w[y hara].choice }.
|
25
|
+
inject {|r, e| r + (rand < 0.97 ? ' ' : ', ') + e }
|
26
|
+
eos = %w(? ? . . . . . . . . !).choice
|
27
|
+
[Remark.new(self,words, eos)]
|
28
|
+
end
|
29
|
+
|
30
|
+
def voice(context = nil)
|
31
|
+
@name
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class Alex < Speaker
|
38
|
+
def initialize
|
39
|
+
super 'Alex'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Vicki < Speaker
|
44
|
+
def initialize
|
45
|
+
super 'Vicki'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Yhara < Speaker
|
50
|
+
def initialize
|
51
|
+
super 'yhara'
|
52
|
+
end
|
53
|
+
|
54
|
+
def voice(context = nil)
|
55
|
+
VOICES.choise
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Jenifer < Speaker
|
60
|
+
ARABIAN = %w[ايران نيست]
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
super 'jenifer'
|
64
|
+
end
|
65
|
+
|
66
|
+
def talk(context)
|
67
|
+
words = (0..rand(3)). map { ARABIAN.choice }.join(' ')
|
68
|
+
[Remark.new(self,words, '')]
|
69
|
+
end
|
70
|
+
|
71
|
+
def voice(context = nil)
|
72
|
+
'Princess'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Remark
|
77
|
+
attr_reader :speaker, :words, :eos, :pronounciation
|
78
|
+
|
79
|
+
def initialize(speaker, words, eos, options = {})
|
80
|
+
@speaker = speaker
|
81
|
+
@words = words
|
82
|
+
@eos = eos # end of text : "?" or "." or "!"
|
83
|
+
@pronounciation = options[:pronounciation] || text
|
84
|
+
end
|
85
|
+
|
86
|
+
def text
|
87
|
+
@words + @eos
|
88
|
+
end
|
89
|
+
|
90
|
+
def interrogative?
|
91
|
+
@eos == '?'
|
92
|
+
end
|
93
|
+
|
94
|
+
def display
|
95
|
+
puts "#{@speaker.name}: #{text}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def say(context = nil)
|
99
|
+
Kernel.say pronounciation, :voice => @speaker.voice(context)
|
100
|
+
end
|
101
|
+
|
102
|
+
def correct?(s)
|
103
|
+
s.gsub(/[^yhar]/,'') == @words.gsub(/[^yhar]/,'')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
@@context = []
|
108
|
+
@@speakers = [Alex.new, Vicki.new]
|
109
|
+
|
110
|
+
def self.text
|
111
|
+
if ( @@context.last && Yhara === @@context.last.speaker && rand < 0.25 ) || rand < 0.01
|
112
|
+
speaker = Jenifer.new
|
113
|
+
elsif @@context.last && @@context.last.words =~ /y hara/ and @@context.last.interrogative? and rand < 0.25
|
114
|
+
speaker = Yhara.new
|
115
|
+
else
|
116
|
+
speaker = @@speakers[rand(2)]
|
117
|
+
end
|
118
|
+
|
119
|
+
remark = speaker.talk(@@context).first
|
120
|
+
@@context.push remark
|
121
|
+
remark.text
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
module Termtter::Client
|
126
|
+
|
127
|
+
add_help 'yhara', 'Post a new Yharian sentence'
|
128
|
+
|
129
|
+
add_command /^(yhara|y)\s*/ do |m, t|
|
130
|
+
text = Yharian::text
|
131
|
+
unless text.empty?
|
132
|
+
t.update_status(text)
|
133
|
+
puts "=> #{text}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# yhara.rb
|
139
|
+
# post a new yharian sentence
|
140
|
+
# example:
|
141
|
+
# > yhara
|
142
|
+
# => hara y y hara.
|
data/lib/termtter.rb
CHANGED
@@ -3,21 +3,25 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'json'
|
6
|
+
require 'net/https'
|
6
7
|
require 'open-uri'
|
7
8
|
require 'cgi'
|
8
9
|
require 'readline'
|
9
10
|
require 'enumerator'
|
10
11
|
require 'parsedate'
|
11
12
|
require 'configatron'
|
12
|
-
require 'filter'
|
13
13
|
|
14
14
|
if RUBY_VERSION < '1.8.7'
|
15
15
|
class Array
|
16
|
-
def take(n)
|
16
|
+
def take(n) self[0...n] end
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
def win?
|
21
|
+
RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/
|
22
|
+
end
|
23
|
+
|
24
|
+
if win?
|
21
25
|
require 'kconv'
|
22
26
|
module Readline
|
23
27
|
alias :old_readline :readline
|
@@ -30,6 +34,17 @@ end
|
|
30
34
|
|
31
35
|
configatron.set_default(:update_interval, 300)
|
32
36
|
configatron.set_default(:prompt, '> ')
|
37
|
+
configatron.set_default(:enable_ssl, false)
|
38
|
+
configatron.proxy.set_default(:port, '8080')
|
39
|
+
|
40
|
+
# FIXME: we need public_storage all around the script
|
41
|
+
module Termtter
|
42
|
+
module Client
|
43
|
+
def self.public_storage
|
44
|
+
@@public_storage ||= {}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
33
48
|
|
34
49
|
def plugin(s)
|
35
50
|
require "plugin/#{s}"
|
@@ -37,32 +52,76 @@ end
|
|
37
52
|
|
38
53
|
def filter(s)
|
39
54
|
load "filter/#{s}.rb"
|
55
|
+
rescue LoadError
|
56
|
+
raise
|
57
|
+
else
|
58
|
+
Termtter::Client.public_storage[:filters] = []
|
59
|
+
Termtter::Client.public_storage[:filters] << s
|
60
|
+
true
|
40
61
|
end
|
41
62
|
|
42
63
|
# FIXME: delete this method after the major version up
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
64
|
+
unless defined? original_require
|
65
|
+
alias original_require require
|
66
|
+
def require(s)
|
67
|
+
if %r|^termtter/(.*)| =~ s
|
68
|
+
puts "[WARNING] use plugin '#{$1}' instead of require"
|
69
|
+
puts " Such a legacy .termtter file will not be supported until version 1.0.0"
|
70
|
+
s = "plugin/#{$1}"
|
71
|
+
end
|
72
|
+
original_require s
|
49
73
|
end
|
50
|
-
original_require s
|
51
74
|
end
|
52
75
|
|
53
76
|
module Termtter
|
54
|
-
VERSION = '0.7.
|
77
|
+
VERSION = '0.7.5'
|
55
78
|
APP_NAME = 'termtter'
|
56
79
|
|
80
|
+
class Connection
|
81
|
+
attr_reader :protocol, :port, :proxy_uri
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
@proxy_host = configatron.proxy.host
|
85
|
+
@proxy_port = configatron.proxy.port
|
86
|
+
@proxy_user = configatron.proxy.user_name
|
87
|
+
@proxy_password = configatron.proxy.password
|
88
|
+
@proxy_uri = nil
|
89
|
+
@enable_ssl = configatron.enable_ssl
|
90
|
+
@protocol = "http"
|
91
|
+
@port = 80
|
92
|
+
|
93
|
+
unless @proxy_host.empty?
|
94
|
+
@http_class = Net::HTTP::Proxy(@proxy_host, @proxy_port,
|
95
|
+
@proxy_user, @proxy_password)
|
96
|
+
@proxy_uri = "http://" + @proxy_host + ":" + @proxy_port + "/"
|
97
|
+
else
|
98
|
+
@http_class = Net::HTTP
|
99
|
+
end
|
100
|
+
|
101
|
+
if @enable_ssl
|
102
|
+
@protocol = "https"
|
103
|
+
@port = 443
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def start(host, port, &block)
|
108
|
+
http = @http_class.new(host, port)
|
109
|
+
http.use_ssl = @enable_ssl
|
110
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
|
111
|
+
http.start(&block)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
57
115
|
class Twitter
|
58
116
|
|
59
117
|
def initialize(user_name, password)
|
60
118
|
@user_name = user_name
|
61
119
|
@password = password
|
120
|
+
@connection = Connection.new
|
62
121
|
end
|
63
122
|
|
64
123
|
def update_status(status)
|
65
|
-
|
124
|
+
@connection.start("twitter.com", @connection.port) do |http|
|
66
125
|
uri = '/statuses/update.xml'
|
67
126
|
http.request(post_request(uri), "status=#{CGI.escape(status)}&source=#{APP_NAME}")
|
68
127
|
end
|
@@ -70,13 +129,13 @@ module Termtter
|
|
70
129
|
end
|
71
130
|
|
72
131
|
def get_friends_timeline(since_id = nil)
|
73
|
-
uri =
|
132
|
+
uri = "#{@connection.protocol}://twitter.com/statuses/friends_timeline.json"
|
74
133
|
uri << "?since_id=#{since_id}" if since_id
|
75
134
|
return get_timeline(uri)
|
76
135
|
end
|
77
136
|
|
78
137
|
def get_user_timeline(screen_name)
|
79
|
-
return get_timeline("
|
138
|
+
return get_timeline("#{@connection.protocol}://twitter.com/statuses/user_timeline/#{screen_name}.json")
|
80
139
|
rescue OpenURI::HTTPError => e
|
81
140
|
puts "No such user: #{screen_name}"
|
82
141
|
nears = near_users(screen_name)
|
@@ -85,7 +144,7 @@ module Termtter
|
|
85
144
|
end
|
86
145
|
|
87
146
|
def search(query)
|
88
|
-
results = JSON.parse(open(
|
147
|
+
results = JSON.parse(open("#{@connection.protocol}://search.twitter.com/search.json?q=" + CGI.escape(query)).read, :proxy => @connection.proxy_uri)['results']
|
89
148
|
return results.map do |s|
|
90
149
|
status = Status.new
|
91
150
|
status.id = s['id']
|
@@ -97,15 +156,15 @@ module Termtter
|
|
97
156
|
end
|
98
157
|
|
99
158
|
def show(id)
|
100
|
-
return get_timeline("
|
159
|
+
return get_timeline("#{@connection.protocol}://twitter.com/statuses/show/#{id}.json")
|
101
160
|
end
|
102
161
|
|
103
162
|
def replies
|
104
|
-
return get_timeline("
|
163
|
+
return get_timeline("#{@connection.protocol}://twitter.com/statuses/replies.json")
|
105
164
|
end
|
106
165
|
|
107
166
|
def get_timeline(uri)
|
108
|
-
data = JSON.parse(open(uri, :http_basic_authentication => [
|
167
|
+
data = JSON.parse(open(uri, :http_basic_authentication => [user_name, password], :proxy => @connection.proxy_uri).read)
|
109
168
|
data = [data] unless data.instance_of? Array
|
110
169
|
return data.map do |s|
|
111
170
|
status = Status.new
|
@@ -121,16 +180,48 @@ module Termtter
|
|
121
180
|
end
|
122
181
|
end
|
123
182
|
|
183
|
+
# note: APILimit.reset_time_in_seconds == APILimit.reset_time.to_i
|
184
|
+
APILIMIT = Struct.new("APILimit", :reset_time, :reset_time_in_seconds, :remaining_hits, :hourly_limit)
|
185
|
+
def get_rate_limit_status
|
186
|
+
uri = 'http://twitter.com/account/rate_limit_status.json'
|
187
|
+
data = JSON.parse(open(uri, :http_basic_authentication => [user_name, password], :proxy => @connection.proxy_uri).read)
|
188
|
+
|
189
|
+
reset_time = Time.parse(data['reset_time'])
|
190
|
+
reset_time_in_seconds = data['reset_time_in_seconds'].to_i
|
191
|
+
|
192
|
+
APILIMIT.new(reset_time, reset_time_in_seconds, data['remaining_hits'], data['hourly_limit'])
|
193
|
+
end
|
194
|
+
|
195
|
+
alias :api_limit :get_rate_limit_status
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def user_name
|
200
|
+
unless @user_name.instance_of? String
|
201
|
+
@user_name = Readline.readline('user name: ', false)
|
202
|
+
end
|
203
|
+
@user_name
|
204
|
+
end
|
205
|
+
|
206
|
+
def password
|
207
|
+
unless @password.instance_of? String
|
208
|
+
system 'stty -echo'
|
209
|
+
@password = Readline.readline('password: ', false)
|
210
|
+
system 'stty echo'
|
211
|
+
puts
|
212
|
+
end
|
213
|
+
@password
|
214
|
+
end
|
215
|
+
|
124
216
|
def near_users(screen_name)
|
125
217
|
Client::public_storage[:users].select {|user|
|
126
218
|
/#{user}/i =~ screen_name || /#{screen_name}/i =~ user
|
127
219
|
}.join(', ')
|
128
220
|
end
|
129
|
-
private :near_users
|
130
221
|
|
131
222
|
def post_request(uri)
|
132
223
|
req = Net::HTTP::Post.new(uri)
|
133
|
-
req.basic_auth(
|
224
|
+
req.basic_auth(user_name, password)
|
134
225
|
req.add_field('User-Agent', 'Termtter http://github.com/jugyo/termtter')
|
135
226
|
req.add_field('X-Twitter-Client', 'Termtter')
|
136
227
|
req.add_field('X-Twitter-Client-URL', 'http://github.com/jugyo/termtter')
|
@@ -144,6 +235,7 @@ module Termtter
|
|
144
235
|
@@hooks = []
|
145
236
|
@@commands = {}
|
146
237
|
@@completions = []
|
238
|
+
@@filters = []
|
147
239
|
@@helps = []
|
148
240
|
|
149
241
|
class << self
|
@@ -159,6 +251,12 @@ module Termtter
|
|
159
251
|
@@commands[regex] = block
|
160
252
|
end
|
161
253
|
|
254
|
+
def add_macro(r, s)
|
255
|
+
add_command(r) do |m, t|
|
256
|
+
call_commands(s % m, t)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
162
260
|
def clear_commands
|
163
261
|
@@commands.clear
|
164
262
|
end
|
@@ -179,6 +277,27 @@ module Termtter
|
|
179
277
|
@@helps.clear
|
180
278
|
end
|
181
279
|
|
280
|
+
def add_filter(&filter)
|
281
|
+
@@filters << filter
|
282
|
+
end
|
283
|
+
|
284
|
+
def clear_filters
|
285
|
+
@@filters.clear
|
286
|
+
end
|
287
|
+
|
288
|
+
# memo: each filter must return Array of Status
|
289
|
+
def apply_filters(statuses)
|
290
|
+
filtered = statuses
|
291
|
+
@@filters.each do |f|
|
292
|
+
filtered = f.call(filtered)
|
293
|
+
end
|
294
|
+
filtered
|
295
|
+
rescue => e
|
296
|
+
puts "Error: #{e}"
|
297
|
+
puts e.backtrace.join("\n")
|
298
|
+
statuses
|
299
|
+
end
|
300
|
+
|
182
301
|
Readline.basic_word_break_characters= "\t\n\"\\'`><=;|&{("
|
183
302
|
Readline.completion_proc = proc {|input|
|
184
303
|
@@completions.map {|completion|
|
@@ -186,10 +305,6 @@ module Termtter
|
|
186
305
|
}.flatten.compact
|
187
306
|
}
|
188
307
|
|
189
|
-
def public_storage
|
190
|
-
@@public_storage ||= {}
|
191
|
-
end
|
192
|
-
|
193
308
|
def call_hooks(statuses, event, tw)
|
194
309
|
statuses = apply_filters(statuses)
|
195
310
|
@@hooks.each do |h|
|
@@ -231,6 +346,8 @@ module Termtter
|
|
231
346
|
end
|
232
347
|
|
233
348
|
def exit
|
349
|
+
call_hooks([], :exit, nil)
|
350
|
+
@@main_thread.kill
|
234
351
|
@@update_thread.kill
|
235
352
|
@@input_thread.kill
|
236
353
|
end
|
@@ -240,7 +357,9 @@ module Termtter
|
|
240
357
|
initialized = false
|
241
358
|
@@pause = false
|
242
359
|
tw = Termtter::Twitter.new(configatron.user_name, configatron.password)
|
360
|
+
call_hooks([], :initialize, tw)
|
243
361
|
|
362
|
+
@@input_thread = nil
|
244
363
|
@@update_thread = Thread.new do
|
245
364
|
since_id = nil
|
246
365
|
loop do
|
@@ -251,9 +370,16 @@ module Termtter
|
|
251
370
|
unless statuses.empty?
|
252
371
|
since_id = statuses[0].id
|
253
372
|
end
|
373
|
+
print "\e[1K\e[0G" if !statuses.empty? && !win?
|
254
374
|
call_hooks(statuses, :update_friends_timeline, tw)
|
255
375
|
initialized = true
|
256
|
-
|
376
|
+
@@input_thread.kill if @@input_thread && !statuses.empty?
|
377
|
+
rescue OpenURI::HTTPError => e
|
378
|
+
if e.message == '401 Unauthorized'
|
379
|
+
puts 'Could not login'
|
380
|
+
puts 'plese check your account settings'
|
381
|
+
exit!
|
382
|
+
end
|
257
383
|
rescue => e
|
258
384
|
puts "Error: #{e}"
|
259
385
|
puts e.backtrace.join("\n")
|
@@ -270,8 +396,25 @@ module Termtter
|
|
270
396
|
Readline.__send__("#{vi_or_emacs}_editing_mode")
|
271
397
|
end
|
272
398
|
|
273
|
-
|
274
|
-
|
399
|
+
begin
|
400
|
+
stty_save = `stty -g`.chomp
|
401
|
+
trap("INT") { system "stty", stty_save; exit }
|
402
|
+
rescue Errno::ENOENT
|
403
|
+
end
|
404
|
+
|
405
|
+
@@main_thread = Thread.new do
|
406
|
+
loop do
|
407
|
+
@@input_thread = create_input_thread(tw)
|
408
|
+
@@input_thread.join
|
409
|
+
end
|
410
|
+
end
|
411
|
+
@@main_thread.join
|
412
|
+
end
|
413
|
+
|
414
|
+
def create_input_thread(tw)
|
415
|
+
Thread.new do
|
416
|
+
erb = ERB.new(configatron.prompt)
|
417
|
+
while buf = Readline.readline(erb.result(tw.__send__(:binding)), true)
|
275
418
|
begin
|
276
419
|
call_commands(buf, tw)
|
277
420
|
rescue CommandNotFound => e
|
@@ -283,18 +426,8 @@ module Termtter
|
|
283
426
|
end
|
284
427
|
end
|
285
428
|
end
|
286
|
-
|
287
|
-
begin
|
288
|
-
stty_save = `stty -g`.chomp
|
289
|
-
trap("INT") { system "stty", stty_save; exit }
|
290
|
-
rescue Errno::ENOENT
|
291
|
-
end
|
292
|
-
|
293
|
-
@@input_thread.join
|
294
429
|
end
|
295
|
-
|
296
430
|
end
|
297
|
-
|
298
431
|
end
|
299
432
|
|
300
433
|
class CommandNotFound < StandardError; end
|
@@ -307,6 +440,9 @@ module Termtter
|
|
307
440
|
attr_accessor attr.to_sym
|
308
441
|
end
|
309
442
|
|
443
|
+
def eql?(other); self.id == other.id end
|
444
|
+
def hash; self.id end
|
445
|
+
|
310
446
|
def english?
|
311
447
|
self.class.english?(self.text)
|
312
448
|
end
|