tit 1.1.2 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.markdown +23 -0
- data/README.markdown +3 -2
- data/Rakefile +3 -2
- data/VERSION.yml +2 -2
- data/bin/tit +48 -9
- data/lib/tit.rb +59 -10
- data/tit.gemspec +7 -4
- metadata +39 -13
data/ChangeLog.markdown
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# tit 1.2.2 2011-08-18
|
2
|
+
|
3
|
+
* freaking <3 all over the fucking twittersphere - decode that shit
|
4
|
+
|
5
|
+
# tit 1.2.1 2011-08-18
|
6
|
+
|
7
|
+
* no longer using "-u" or "--update" for sending a tweet when other options exist - using "-t" or "--tweet"
|
8
|
+
|
9
|
+
# tit 1.2.0 2011-08-18
|
10
|
+
|
11
|
+
* ability to get user's timeline
|
12
|
+
* tweet without using -u: just `tit 'tweet tweet'`
|
13
|
+
* user can now set number of tweets printed when requesting timelines
|
14
|
+
|
15
|
+
# tit 1.1.4 2011-08-17
|
16
|
+
|
17
|
+
* moved debug from -d to -D
|
18
|
+
* added -v --version switch
|
19
|
+
|
20
|
+
# tit 1.1.3 2011-08-15
|
21
|
+
|
22
|
+
* fixed a bug - ftools sucks
|
23
|
+
|
1
24
|
# tit 1.1.2 2010-01-26
|
2
25
|
|
3
26
|
* fixed a bug with update times and bad network connections
|
data/README.markdown
CHANGED
@@ -43,15 +43,16 @@ there's some more fucking options for you if you look at `tit -h`
|
|
43
43
|
|
44
44
|
### showing other people your tits ###
|
45
45
|
|
46
|
-
$ tit
|
46
|
+
$ tit "look at my stupid fucking tweet" -G 88.918:-34.879
|
47
47
|
|
48
48
|
dependencies
|
49
49
|
------------
|
50
50
|
|
51
|
-
[oauth][] and [nokogiri][]
|
51
|
+
[oauth][] and [nokogiri][] and [htmlentities][]
|
52
52
|
|
53
53
|
[oauth]: http://oauth.rubyforge.org/
|
54
54
|
[nokogiri]: http://nokogiri.org/
|
55
|
+
[htmlentities]: http://htmlentities.rubyforge.org/
|
55
56
|
|
56
57
|
caveats
|
57
58
|
-------
|
data/Rakefile
CHANGED
@@ -4,11 +4,12 @@ begin
|
|
4
4
|
gemspec.name = "tit"
|
5
5
|
gemspec.summary = "stupid fucking twitter client"
|
6
6
|
gemspec.description = "a stupid fucking twitter client"
|
7
|
-
gemspec.email = "leif.walsh@gmail.com"
|
7
|
+
gemspec.email = ["leif.walsh@gmail.com", "parkrmoore@gmail.com"]
|
8
8
|
gemspec.homepage = "http://github.com/adlaiff6/tit"
|
9
|
-
gemspec.authors = ["Leif Walsh"]
|
9
|
+
gemspec.authors = ["Leif Walsh", "Parker Moore"]
|
10
10
|
gemspec.add_dependency('nokogiri', '>= 0')
|
11
11
|
gemspec.add_dependency('oauth', '>= 0')
|
12
|
+
gemspec.add_dependency('htmlentities', '>= 0')
|
12
13
|
end
|
13
14
|
Jeweler::GemcutterTasks.new
|
14
15
|
rescue LoadError
|
data/VERSION.yml
CHANGED
data/bin/tit
CHANGED
@@ -12,33 +12,45 @@ def main
|
|
12
12
|
:payload => nil,
|
13
13
|
:notify => nil
|
14
14
|
}
|
15
|
+
unchanged = true
|
15
16
|
|
16
17
|
tit = Tit.new
|
17
18
|
|
18
19
|
tit.opts = OptionParser.new do |opts|
|
19
20
|
opts.banner = "Usage: #{File.basename($0)} " +
|
20
|
-
"[options] [action [action options]]"
|
21
|
+
"[status] [options] [action [action options]]"
|
21
22
|
|
22
23
|
opts.separator ""
|
23
24
|
opts.separator "Actions:"
|
24
|
-
|
25
|
+
|
25
26
|
opts.on("-p", "--public", "Show public timeline") do
|
26
27
|
options[:action] = :public
|
28
|
+
unchanged = false
|
27
29
|
end
|
28
30
|
opts.on("-H", "--home", "Show home timeline (default)") do
|
29
31
|
options[:action] = :home
|
32
|
+
unchanged = false
|
30
33
|
end
|
31
34
|
opts.on("-m", "--mentions", "Show mentions timeline") do
|
32
35
|
options[:action] = :mentions
|
36
|
+
unchanged = false
|
37
|
+
end
|
38
|
+
opts.on("-u", "--user [USER]",
|
39
|
+
"Show a particular user's timeline") do |user|
|
40
|
+
unchanged = false
|
41
|
+
options[:action] = :user_timeline
|
42
|
+
options[:payload] ||= {}
|
43
|
+
options[:payload]["user"] = user
|
33
44
|
end
|
34
|
-
opts.on("-
|
35
|
-
|
45
|
+
opts.on("-t", "--tweet [STATUS]", "Update status (required when using -G)") do |status|
|
46
|
+
unchanged = false
|
36
47
|
options[:action] = :update
|
37
48
|
options[:payload] ||= {}
|
38
|
-
options[:payload]["status"] = status
|
49
|
+
options[:payload]["status"] = status
|
39
50
|
end
|
40
51
|
opts.on("--pin PIN", ("Set auth pin if this is your first time playing " +
|
41
52
|
"with this tit")) do |pin|
|
53
|
+
unchanged = false
|
42
54
|
options[:pin] = pin
|
43
55
|
end
|
44
56
|
|
@@ -47,6 +59,7 @@ def main
|
|
47
59
|
|
48
60
|
opts.on("-P", "--poll [N]",
|
49
61
|
"Poll for more updates every N secs (default 180)") do |secs|
|
62
|
+
unchanged = false
|
50
63
|
options[:wait] = secs || '180'
|
51
64
|
options[:wait] = options[:wait].to_i
|
52
65
|
options[:wait] = 30 if options[:wait] < 30
|
@@ -57,6 +70,7 @@ def main
|
|
57
70
|
|
58
71
|
opts.on("-n", "--notify [PROG]",
|
59
72
|
"Send notifications using PROG (default: notify-send)") do |prog|
|
73
|
+
unchanged = false
|
60
74
|
options[:notify] = prog || "notify-send"
|
61
75
|
end
|
62
76
|
|
@@ -65,6 +79,7 @@ def main
|
|
65
79
|
|
66
80
|
opts.on("-G", "--geo LAT:LONG",
|
67
81
|
"Set latitude and longitude for update") do |s|
|
82
|
+
unchanged = false
|
68
83
|
sp = s.split(/:/)
|
69
84
|
|
70
85
|
tit.abort("invalid geotag format: #{s}", opts) unless sp.length == 2
|
@@ -77,13 +92,28 @@ def main
|
|
77
92
|
opts.separator ""
|
78
93
|
opts.separator "Common options:"
|
79
94
|
|
80
|
-
opts.on("-
|
95
|
+
opts.on("-D", "--debug", "Show debugging information") do
|
96
|
+
unchanged = false
|
81
97
|
options[:debug] = true
|
82
98
|
end
|
99
|
+
|
100
|
+
opts.on_tail("--count [NUM_STATUSES]", "Set number of statuses you see") do |count|
|
101
|
+
unchanged = false
|
102
|
+
tit.update_count(count)
|
103
|
+
end
|
104
|
+
|
83
105
|
opts.on_tail("-h", "--help", "Show this message") do
|
106
|
+
unchanged = false
|
84
107
|
puts opts
|
85
108
|
exit
|
86
109
|
end
|
110
|
+
|
111
|
+
opts.on_tail("-v", "--version", "Show version") do
|
112
|
+
unchanged = false
|
113
|
+
puts ["v", Tit::VERSION.join('.')].join("")
|
114
|
+
exit
|
115
|
+
end
|
116
|
+
|
87
117
|
end
|
88
118
|
|
89
119
|
begin
|
@@ -91,6 +121,13 @@ def main
|
|
91
121
|
rescue OptionParser::InvalidOption => e
|
92
122
|
tit.abort(e.message)
|
93
123
|
end
|
124
|
+
|
125
|
+
# terribly hacky, but i can't figure out how OptionsParser handles no switches and just an argument. If it does at all.
|
126
|
+
if(unchanged == true and ARGV[0])
|
127
|
+
options[:action] = :update
|
128
|
+
options[:payload] ||= {}
|
129
|
+
options[:payload]["status"] = ARGV[0]
|
130
|
+
end
|
94
131
|
|
95
132
|
if options.include? :pin
|
96
133
|
tit.use_pin(options[:pin])
|
@@ -99,7 +136,7 @@ def main
|
|
99
136
|
tit.get_access
|
100
137
|
|
101
138
|
# check for option errors
|
102
|
-
if Tit::READERS.include? options[:action]
|
139
|
+
if Tit::READERS.include? options[:action] and not options[:action].eql?(:user_timeline)
|
103
140
|
tit.abort("cannot provide geotag when reading") unless options[:payload].nil?
|
104
141
|
tit.abort("cannot notify unless polling") if (options[:wait].nil? and
|
105
142
|
not options[:notify].nil?)
|
@@ -118,8 +155,9 @@ def main
|
|
118
155
|
tit.error "got a networking error, are you connected to the intarbutts?"
|
119
156
|
puts e
|
120
157
|
exit(-1)
|
121
|
-
rescue NoMethodError
|
158
|
+
rescue NoMethodError => e
|
122
159
|
tit.error "might have gotten a networking error, check your intarbutts."
|
160
|
+
puts e
|
123
161
|
exit(-1)
|
124
162
|
end
|
125
163
|
else
|
@@ -129,8 +167,9 @@ def main
|
|
129
167
|
tit.error "got a networking error, are you connected to the intarbutts?"
|
130
168
|
puts e
|
131
169
|
exit(-1)
|
132
|
-
rescue NoMethodError
|
170
|
+
rescue NoMethodError => e
|
133
171
|
tit.error "might have gotten a networking error, check your intarbutts."
|
172
|
+
puts e
|
134
173
|
exit(-1)
|
135
174
|
rescue => e
|
136
175
|
tit.error "unknown error"
|
data/lib/tit.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require '
|
4
|
+
require 'fileutils'
|
5
5
|
require 'nokogiri'
|
6
|
-
require 'oauth
|
6
|
+
require 'oauth'
|
7
7
|
require 'time' # heh.
|
8
8
|
require 'yaml'
|
9
|
+
require 'htmlentities' # hate those <3
|
9
10
|
|
10
11
|
class String
|
12
|
+
URI_REGEX = %r"((?:(?:[^ :/?#]+):)(?://(?:[^ /?#]*))(?:[^ ?#]*)(?:\?(?:[^ #]*))?(?:#(?:[^ ]*))?)"
|
11
13
|
def wrapped(cols)
|
12
14
|
curlen = 0
|
13
15
|
split.inject([[]]) do |rows, word|
|
@@ -20,6 +22,21 @@ class String
|
|
20
22
|
end
|
21
23
|
end.map { |row| row.join(' ') }
|
22
24
|
end
|
25
|
+
def replace_with_expanded_url! (expanded)
|
26
|
+
replace(replace_with_expanded_url(expanded))
|
27
|
+
end
|
28
|
+
def replace_with_expanded_url(expanded)
|
29
|
+
replace_uris(/http:\/\/t.co\/[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY0123456789]/, expanded)
|
30
|
+
end
|
31
|
+
def replace_uris(old, newt)
|
32
|
+
split(URI_REGEX).collect do |s|
|
33
|
+
if s =~ URI_REGEX
|
34
|
+
s.gsub(old, newt.join)
|
35
|
+
else
|
36
|
+
s
|
37
|
+
end
|
38
|
+
end.join
|
39
|
+
end
|
23
40
|
end
|
24
41
|
|
25
42
|
class Time
|
@@ -74,16 +91,20 @@ end
|
|
74
91
|
Why are you reading the documentation, you cunt?
|
75
92
|
=end
|
76
93
|
class Tit
|
94
|
+
VERSION = [1, 2, 3]
|
95
|
+
|
96
|
+
RCFILE = File.join(ENV["HOME"], ".titrc")
|
77
97
|
RTFILE = File.join(ENV["HOME"], ".titrt")
|
78
98
|
ATFILE = File.join(ENV["HOME"], ".titat")
|
79
99
|
|
80
|
-
READERS = [:public, :home, :mentions]
|
100
|
+
READERS = [:public, :home, :mentions, :user_timeline]
|
81
101
|
WRITERS = [:update]
|
82
102
|
|
83
103
|
URLS = {
|
84
104
|
:public => "/statuses/public_timeline.xml",
|
85
105
|
:home => "/statuses/home_timeline.xml",
|
86
106
|
:mentions => "/statuses/mentions.xml",
|
107
|
+
:user_timeline => "/statuses/user_timeline.xml",
|
87
108
|
:update => "/statuses/update.xml"
|
88
109
|
}
|
89
110
|
|
@@ -93,9 +114,10 @@ class Tit
|
|
93
114
|
def initialize
|
94
115
|
@consumer = OAuth::Consumer.new(KEY, SECRET,
|
95
116
|
{ :site => "https://twitter.com" })
|
96
|
-
|
97
117
|
# get terminal width
|
98
118
|
@cols = %x[tput cols].to_i
|
119
|
+
# get status count
|
120
|
+
@prefs = YAML.load_file(RCFILE)
|
99
121
|
end
|
100
122
|
attr_accessor :opts
|
101
123
|
|
@@ -111,6 +133,9 @@ class Tit
|
|
111
133
|
File.open(RTFILE, "w") do |rt|
|
112
134
|
YAML.dump(request_token.params, rt)
|
113
135
|
end
|
136
|
+
File.open(RCFILE, "w") do |rc|
|
137
|
+
YAML.dump({count: 10}, rc)
|
138
|
+
end
|
114
139
|
tuts "Please visit '#{request_token.authorize_url}'."
|
115
140
|
tuts "When you finish, provide your pin with `tit --pin PIN'"
|
116
141
|
exit(0)
|
@@ -143,12 +168,28 @@ class Tit
|
|
143
168
|
"revoke your token."
|
144
169
|
end
|
145
170
|
|
146
|
-
def get_tits(action)
|
147
|
-
|
171
|
+
def get_tits(action, payload)
|
172
|
+
api_endpoint = URLS[action]
|
173
|
+
if(action == :user_timeline and not payload.nil?)
|
174
|
+
api_endpoint.concat("?screen_name=".concat(payload['user'])).concat("&count=#{@prefs[:count]}")
|
175
|
+
else
|
176
|
+
api_endpoint.concat("?count=#{@prefs[:count]}")
|
177
|
+
end
|
178
|
+
api_endpoint.concat("&include_entities=true")
|
179
|
+
coder = HTMLEntities.new
|
180
|
+
Nokogiri.XML(@access_token.get(api_endpoint).body).xpath("//status").map do |xml|
|
148
181
|
{
|
149
182
|
:username => xml.at_xpath("./user/name").content,
|
150
183
|
:userid => xml.at_xpath("./user/screen_name").content,
|
151
|
-
:text => xml.xpath("./text").map
|
184
|
+
:text => xml.xpath("./text").map do |n|
|
185
|
+
txt = coder.decode(n.content)
|
186
|
+
if not xml.xpath("./entities/urls").nil?
|
187
|
+
xml.xpath("./entities/urls/url").map do |url|
|
188
|
+
txt.replace_with_expanded_url! (url.xpath("./expanded_url").map { |expurl| expurl.content })
|
189
|
+
end
|
190
|
+
end
|
191
|
+
txt
|
192
|
+
end,
|
152
193
|
:timestamp => Time.parse(xml.at_xpath("./created_at").content),
|
153
194
|
:id => xml.at_xpath("./id").content.to_i,
|
154
195
|
:geo => xml.at_xpath("./geo").instance_eval do
|
@@ -231,7 +272,7 @@ class Tit
|
|
231
272
|
def run(options)
|
232
273
|
if READERS.include? options[:action]
|
233
274
|
if options[:wait].nil?
|
234
|
-
get_tits(options[:action]).reverse.each &method(:show_tit)
|
275
|
+
get_tits(options[:action], options[:payload]).reverse.each &method(:show_tit)
|
235
276
|
else
|
236
277
|
poll(options[:wait], options[:action], options[:notify])
|
237
278
|
end
|
@@ -239,9 +280,17 @@ class Tit
|
|
239
280
|
update options[:payload]
|
240
281
|
end
|
241
282
|
end
|
283
|
+
|
284
|
+
def update_count(count)
|
285
|
+
@prefs[:count] = count
|
286
|
+
File.open(RCFILE, "w") do |rc|
|
287
|
+
YAML.dump(@prefs, rc)
|
288
|
+
end
|
289
|
+
exit(0)
|
290
|
+
end
|
242
291
|
|
243
292
|
def tuts(*strs)
|
244
|
-
strs.each { |s| puts s.wrapped(@cols) }
|
293
|
+
strs.each { |s| puts s.to_s.wrapped(@cols) }
|
245
294
|
end
|
246
295
|
|
247
296
|
def error(msg)
|
@@ -250,7 +299,7 @@ class Tit
|
|
250
299
|
|
251
300
|
def abort(msg)
|
252
301
|
error(msg)
|
253
|
-
|
302
|
+
puts @opts
|
254
303
|
exit(-1)
|
255
304
|
end
|
256
305
|
end
|
data/tit.gemspec
CHANGED
@@ -5,17 +5,17 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{tit}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.2.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Leif Walsh"]
|
11
|
+
s.authors = ["Leif Walsh", "Parker Moore"]
|
12
12
|
s.date = %q{2010-01-26}
|
13
13
|
s.default_executable = %q{tit}
|
14
14
|
s.description = %q{a stupid fucking twitter client}
|
15
|
-
s.email = %q{leif.walsh@gmail.com}
|
15
|
+
s.email = [%q{leif.walsh@gmail.com}, %q{parkrmoore@gmail.com}]
|
16
16
|
s.executables = ["tit"]
|
17
17
|
s.extra_rdoc_files = [
|
18
|
-
|
18
|
+
"ChangeLog.markdown",
|
19
19
|
"LICENSE",
|
20
20
|
"README.markdown"
|
21
21
|
]
|
@@ -43,13 +43,16 @@ Gem::Specification.new do |s|
|
|
43
43
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
44
44
|
s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
|
45
45
|
s.add_runtime_dependency(%q<oauth>, [">= 0"])
|
46
|
+
s.add_runtime_dependency(%q<htmlentities>, [">= 0"])
|
46
47
|
else
|
47
48
|
s.add_dependency(%q<nokogiri>, [">= 0"])
|
48
49
|
s.add_dependency(%q<oauth>, [">= 0"])
|
50
|
+
s.add_dependency(%q<htmlentities>, [">= 0"])
|
49
51
|
end
|
50
52
|
else
|
51
53
|
s.add_dependency(%q<nokogiri>, [">= 0"])
|
52
54
|
s.add_dependency(%q<oauth>, [">= 0"])
|
55
|
+
s.add_dependency(%q<htmlentities>, [">= 0"])
|
53
56
|
end
|
54
57
|
end
|
55
58
|
|
metadata
CHANGED
@@ -1,39 +1,63 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 2
|
8
|
+
- 3
|
9
|
+
version: 1.2.3
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Leif Walsh
|
13
|
+
- Parker Moore
|
8
14
|
autorequire:
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date: 2010-01-26 00:00:00
|
18
|
+
date: 2010-01-26 00:00:00 -05:00
|
13
19
|
default_executable: tit
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: nokogiri
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
25
|
requirements:
|
21
26
|
- - ">="
|
22
27
|
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
23
30
|
version: "0"
|
24
|
-
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
25
33
|
- !ruby/object:Gem::Dependency
|
26
34
|
name: oauth
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
27
43
|
type: :runtime
|
28
|
-
|
29
|
-
|
44
|
+
version_requirements: *id002
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: htmlentities
|
47
|
+
prerelease: false
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
30
49
|
requirements:
|
31
50
|
- - ">="
|
32
51
|
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
33
54
|
version: "0"
|
34
|
-
|
55
|
+
type: :runtime
|
56
|
+
version_requirements: *id003
|
35
57
|
description: a stupid fucking twitter client
|
36
|
-
email:
|
58
|
+
email:
|
59
|
+
- leif.walsh@gmail.com
|
60
|
+
- parkrmoore@gmail.com
|
37
61
|
executables:
|
38
62
|
- tit
|
39
63
|
extensions: []
|
@@ -65,18 +89,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
89
|
requirements:
|
66
90
|
- - ">="
|
67
91
|
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 0
|
68
94
|
version: "0"
|
69
|
-
version:
|
70
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
96
|
requirements:
|
72
97
|
- - ">="
|
73
98
|
- !ruby/object:Gem::Version
|
99
|
+
segments:
|
100
|
+
- 0
|
74
101
|
version: "0"
|
75
|
-
version:
|
76
102
|
requirements: []
|
77
103
|
|
78
104
|
rubyforge_project:
|
79
|
-
rubygems_version: 1.3.
|
105
|
+
rubygems_version: 1.3.6
|
80
106
|
signing_key:
|
81
107
|
specification_version: 3
|
82
108
|
summary: stupid fucking twitter client
|