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 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 -u "look at my stupid fucking tweet" -G 88.918:-34.879
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
@@ -1,5 +1,5 @@
1
1
  ---
2
- :patch: 2
3
- :minor: 1
2
+ :patch: 3
3
+ :minor: 2
4
4
  :major: 1
5
5
  :build:
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("-u", "--update [STATUS]",
35
- "Update status (read from STDIN if none given)") do |status|
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 || STDIN
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("-d", "--debug", "Show debugging information") do
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 'ftools'
4
+ require 'fileutils'
5
5
  require 'nokogiri'
6
- require 'oauth/consumer'
6
+ require 'oauth'
7
7
  require 'time' # heh.
8
8
  require 'yaml'
9
+ require 'htmlentities' # hate those &lt;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
- Nokogiri.XML(@access_token.get(URLS[action]).body).xpath("//status").map do |xml|
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 { |n| n.content },
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
- tuts @opts
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.1.2"
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
- "ChangeLog.markdown",
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
- version: 1.1.2
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 +01: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
- type: :runtime
18
- version_requirement:
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
- version:
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
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
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
- version:
55
+ type: :runtime
56
+ version_requirements: *id003
35
57
  description: a stupid fucking twitter client
36
- email: leif.walsh@gmail.com
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.5
105
+ rubygems_version: 1.3.6
80
106
  signing_key:
81
107
  specification_version: 3
82
108
  summary: stupid fucking twitter client