twt 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +150 -0
- data/bin/twt +485 -0
- metadata +71 -0
data/README.markdown
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
twt - A simple CLI Twitter client
|
2
|
+
=================================
|
3
|
+
twt is a Twitter client designed to be as easy as possible to be used from CLI. You'll never have to switch from your preferred console to the browser and check latest tweets anymore.
|
4
|
+
|
5
|
+
Installation
|
6
|
+
============
|
7
|
+
You need a working Ruby environment. On Linux and OS X, the default ruby 1.8.7 is OK, and it also work with Ruby 1.9.x
|
8
|
+
|
9
|
+
Then you have to install twt via rubygems:
|
10
|
+
|
11
|
+
% sudo gem install twt
|
12
|
+
|
13
|
+
This will also install a few dependencies.
|
14
|
+
|
15
|
+
If you are on Windows, well, just try and let me know if it works.
|
16
|
+
|
17
|
+
Since version 0.2.1, twt checks online for new version availability. If found, it remembers you to upgrade your gem.
|
18
|
+
|
19
|
+
Usage
|
20
|
+
=====
|
21
|
+
A short guide appears if you type:
|
22
|
+
|
23
|
+
% twt
|
24
|
+
|
25
|
+
Login
|
26
|
+
-----
|
27
|
+
This version relies on OAuth for authentication. This means that the very first time you try to do something, twt will launch your browser and ask Twitter for connection. You have to confirm the request (within the browser), copy the PIN it gives you back, and past the PIN after the prompt that twt is presenting you.
|
28
|
+
Since now, you are connected and each subsequent command would not require additional authentication, until you would logout.
|
29
|
+
|
30
|
+
Logout
|
31
|
+
------
|
32
|
+
If you want to remove your credentials, just type:
|
33
|
+
|
34
|
+
% twt logout
|
35
|
+
|
36
|
+
Read friends or user timelines
|
37
|
+
------------------------------
|
38
|
+
|
39
|
+
To read you friends timeline, your own timeline, and the tweets mentioning you, simply issue:
|
40
|
+
|
41
|
+
% twt friends
|
42
|
+
% twt user
|
43
|
+
% twt mention
|
44
|
+
|
45
|
+
You can also get a specific user's timeline (in the example, mine):
|
46
|
+
|
47
|
+
% twt user p4010
|
48
|
+
|
49
|
+
Post a message
|
50
|
+
--------------
|
51
|
+
To post a new message, issue:
|
52
|
+
|
53
|
+
% twt post "Ho! This is a nice new message from twt!"
|
54
|
+
|
55
|
+
Note that twt will automatically cut your message down to 137 characters and add tree points at the end ("...") if your original message would result longer than 140 characters. You will be noticed about this shortening operation.
|
56
|
+
|
57
|
+
*NEW in ver. 0.2!* If you are not connected to the Internet when you post a message, the message will be queued. You can view and manipulate your queued messages with the command:
|
58
|
+
|
59
|
+
% twt queue
|
60
|
+
0. test1
|
61
|
+
1. test2
|
62
|
+
2. test3
|
63
|
+
% twt dequeue 0 2 # deletes messages 0 and 2 from the queue
|
64
|
+
Messages 0, 2 dequeued
|
65
|
+
% twt dequeue # deletes all the queued messages
|
66
|
+
Message queue is now empty
|
67
|
+
|
68
|
+
When you go back online, you can then deliver all the queued messages with the command:
|
69
|
+
|
70
|
+
% twt deliver
|
71
|
+
Delivering 3 queued messages:
|
72
|
+
message 0... Succesfully posted "test1"
|
73
|
+
sent
|
74
|
+
message 1... Succesfully posted "test2"
|
75
|
+
sent
|
76
|
+
message 2... Succesfully posted "test3"
|
77
|
+
sent
|
78
|
+
Message queue is now empty
|
79
|
+
|
80
|
+
*NEW in ver. 0.3!* Direct messages (DMs) are supported. To send a direct message to @user, just type:
|
81
|
+
|
82
|
+
% twt dm @user "message test"
|
83
|
+
|
84
|
+
In order to read the list of your direct messages, just issue:
|
85
|
+
|
86
|
+
% twt dms
|
87
|
+
|
88
|
+
Monitor your followers
|
89
|
+
----------------------
|
90
|
+
If you want to monitor your followers and discover the name of the last ugly people that left your list, use the delta command:
|
91
|
+
|
92
|
+
% twt delta
|
93
|
+
|
94
|
+
*This is new since version 0.1.5.*
|
95
|
+
|
96
|
+
Follower management
|
97
|
+
-------------------
|
98
|
+
You can add one or more users by means of the following command:
|
99
|
+
|
100
|
+
% twt follow oneuser anotheruser
|
101
|
+
|
102
|
+
In the same way, to stop following a given user is a matter of:
|
103
|
+
|
104
|
+
% twt unfollow annoyinguser
|
105
|
+
|
106
|
+
Reset the environment
|
107
|
+
---------------------
|
108
|
+
twt keeps a few configuration variables (those marked as "sticky" in the short help) beside to the login in a hidden configuration file. If you want to reset to default values (and clean login information too), issue:
|
109
|
+
|
110
|
+
% twt reset
|
111
|
+
|
112
|
+
Options
|
113
|
+
-------
|
114
|
+
At the moment, the following options are supported:
|
115
|
+
|
116
|
+
- -cN: limits ANY subsequent query to N results (i.e. tweets). **If you set the number to 0 (zero) you only get the new messages since your last query** (sticky)
|
117
|
+
- -wN: format messages to be nicely represented in a console of width N (sticky)
|
118
|
+
- -r: prints out results in raw format (useful for debug or Twitter API inspection)
|
119
|
+
- -s: toggles the insertion of an empty line between each result (sticky)
|
120
|
+
- -p: toggles between compact and readable view for tweet heading (sticky)
|
121
|
+
- -kCOLOR: sets the color for tweet user name (sticky)
|
122
|
+
|
123
|
+
Valid color codes are:
|
124
|
+
|
125
|
+
- off => Turn off all attributes
|
126
|
+
- bright => Set bright mode
|
127
|
+
- underline => Set underline mode
|
128
|
+
- blink => Set blink mode
|
129
|
+
- inverse => Exchange foreground and background colors
|
130
|
+
- hide => Hide text (foreground color would be the same as background)
|
131
|
+
- black => Black text
|
132
|
+
- red => Red text
|
133
|
+
- green => Green text
|
134
|
+
- yellow => Yellow text
|
135
|
+
- blue => Blue text
|
136
|
+
- magenta => Magenta text
|
137
|
+
- cyan => Cyan text
|
138
|
+
- white => White text
|
139
|
+
- default => Default text color
|
140
|
+
|
141
|
+
Example: this will read the latest 10 tweets from your friends, and this limit of ten messages will remain valid for every subsequent call, until modified or until a reset:
|
142
|
+
|
143
|
+
% twt -c10 friends
|
144
|
+
|
145
|
+
To Dos
|
146
|
+
======
|
147
|
+
|
148
|
+
- Support more commands (eg search).
|
149
|
+
- Implement the daemon command together with the -t/--time, that will spawn a process that periodically checks for new tweets every given seconds. This will save a PID file on the ~/.twitter dir, that will be used to shut down that daemon (command name?).
|
150
|
+
- Accept suggestions.
|
data/bin/twt
ADDED
@@ -0,0 +1,485 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# untitled
|
3
|
+
#
|
4
|
+
# Created by Paolo Bosetti on 2009-12-03.
|
5
|
+
# Copyright (c) 2009 University of Trento. All rights reserved.
|
6
|
+
#
|
7
|
+
require 'rubygems'
|
8
|
+
require "twitter"
|
9
|
+
require "oauth"
|
10
|
+
require "optparse"
|
11
|
+
require "yaml"
|
12
|
+
require "pp"
|
13
|
+
require "json"
|
14
|
+
|
15
|
+
TWT_VERSION = "0.3.4"
|
16
|
+
VERSION_CHECK_PERIOD = 3600 * 8
|
17
|
+
PROGRAM_NAME = "Twt"
|
18
|
+
DATA_DIR = "#{ENV['HOME']}/.twitter"
|
19
|
+
AUTH_FILE = "cfg.yaml"
|
20
|
+
DEFAULT_COUNT = 10
|
21
|
+
DEFAULT_WIDTH = 80
|
22
|
+
COMMANDS = %w|logout post dm dms user friends mention delta reset queue dequeue deliver follow unfollow|
|
23
|
+
pts =<<EOD
|
24
|
+
126 75 68 121 132 104 139 132 73 131 123 87 99 100 126 106 104 136 119 109 101 93 117 129 97 86 97 68 90 73 76 69 124 132 99 93 135 130 130 108 136 75 121 140 124 91 137 92 94 87 130 71 131 100 74 90 70 103 139 99 138 93
|
25
|
+
EOD
|
26
|
+
UAO = pts.split.map {|b| b.to_i-20}.pack("c62")
|
27
|
+
|
28
|
+
class OptsError < Exception; end
|
29
|
+
class CredentialsError < Exception; end
|
30
|
+
|
31
|
+
if RUBY_VERSION =~ /^1\.9/
|
32
|
+
module Net
|
33
|
+
module HTTPHeader
|
34
|
+
def urlencode(str)
|
35
|
+
str = str.to_s
|
36
|
+
str.dup.force_encoding('ASCII-8BIT').gsub(/[^a-zA-Z0-9_\.\-]/){'%%%02x' % $&.ord}
|
37
|
+
end
|
38
|
+
private :urlencode
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class String
|
44
|
+
COLORS = {
|
45
|
+
:off => 0 , # Turn off all attributes
|
46
|
+
:bright => 1 , # Set bright mode
|
47
|
+
:underline => 4 , # Set underline mode
|
48
|
+
:blink => 5 , # Set blink mode
|
49
|
+
:inverse => 7 , # Exchange foreground and background colors
|
50
|
+
:hide => 8 , # Hide text (foreground color would be the same as background)
|
51
|
+
:black => 30, # Black text
|
52
|
+
:red => 31, # Red text
|
53
|
+
:green => 32, # Green text
|
54
|
+
:yellow => 33, # Yellow text
|
55
|
+
:blue => 34, # Blue text
|
56
|
+
:magenta => 35, # Magenta text
|
57
|
+
:cyan => 36, # Cyan text
|
58
|
+
:white => 37, # White text
|
59
|
+
:default => 39, # Default text color
|
60
|
+
}
|
61
|
+
|
62
|
+
def color(fg, bg=nil)
|
63
|
+
raise ArgumentError, "Wrong text color #{fg}" unless COLORS[fg]
|
64
|
+
raise ArgumentError, "Wrong background color #{bg}" if bg and not COLORS[bg]
|
65
|
+
return self if fg == :off or fg == :default
|
66
|
+
bg_col = "\e[#{COLORS[bg]}m" if bg
|
67
|
+
"\e[#{COLORS[fg]}m#{bg_col}#{self}\e[0m"
|
68
|
+
end
|
69
|
+
|
70
|
+
def partition(w)
|
71
|
+
if self.length <= w
|
72
|
+
return [self]
|
73
|
+
else
|
74
|
+
return [self[0...w], self[w..-1].partition(w)]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def partition_by_words(width, color=:green)
|
79
|
+
words = self.split("\s")
|
80
|
+
result = []
|
81
|
+
buffer = []
|
82
|
+
words.each do |w|
|
83
|
+
if (buffer*" ").length + w.length < width then
|
84
|
+
w = w.color(color) if w =~ /@.*/
|
85
|
+
w = w.color(:underline) if w =~ /(http)(s*):\/\//
|
86
|
+
buffer << w
|
87
|
+
else
|
88
|
+
result << buffer * " "
|
89
|
+
buffer = [w]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
result << buffer * " "
|
93
|
+
return result
|
94
|
+
end
|
95
|
+
|
96
|
+
def human
|
97
|
+
self.upcase.sub(/_/, ' ')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def warn(string)
|
102
|
+
super(string.color(:red))
|
103
|
+
end
|
104
|
+
|
105
|
+
class Twt
|
106
|
+
attr_accessor :raw
|
107
|
+
def initialize
|
108
|
+
Dir.mkdir(DATA_DIR) unless File.directory?(DATA_DIR)
|
109
|
+
begin
|
110
|
+
@cfg = File.open("#{DATA_DIR}/#{AUTH_FILE}") { |file| YAML.load(file) } || Hash.new
|
111
|
+
rescue
|
112
|
+
@cfg = Hash.new
|
113
|
+
end
|
114
|
+
|
115
|
+
@count = (@cfg[:count] || 0)
|
116
|
+
@latest = (@cfg[:latest] || {})
|
117
|
+
@color = (@cfg[:color] || :default).to_sym
|
118
|
+
@raw = false
|
119
|
+
end
|
120
|
+
|
121
|
+
def connect
|
122
|
+
consumer = OAuth::Consumer.new(UAO[0..20], UAO[21..-1],{ :site => "http://api.twitter.com", :scheme => :header })
|
123
|
+
unless (@cfg[:asecret] and @cfg[:atoken])
|
124
|
+
request_token = consumer.get_request_token(:oauth_callback => "oob")
|
125
|
+
url = request_token.authorize_url(:oauth_callback => "oob")
|
126
|
+
print "Redirecting you to twitter to authorize...\n"
|
127
|
+
case RUBY_PLATFORM
|
128
|
+
when /darwin/
|
129
|
+
system "open \"#{url}\""
|
130
|
+
when /mswin/
|
131
|
+
system "start #{url}"
|
132
|
+
else
|
133
|
+
puts "open #{url} in your preferred browser"
|
134
|
+
end
|
135
|
+
|
136
|
+
print "what was the PIN twitter provided you with?\n> "
|
137
|
+
pin = STDIN.gets.chomp
|
138
|
+
access_token = consumer.get_access_token(request_token, :oauth_verifier => pin)
|
139
|
+
self.config = {
|
140
|
+
:atoken => access_token.token,
|
141
|
+
:asecret => access_token.secret
|
142
|
+
}
|
143
|
+
end
|
144
|
+
Twitter.configure do |config|
|
145
|
+
config.consumer_key = UAO[0..20]
|
146
|
+
config.consumer_secret = UAO[21..-1]
|
147
|
+
config.oauth_token = @cfg[:atoken]
|
148
|
+
config.oauth_token_secret = @cfg[:asecret]
|
149
|
+
end
|
150
|
+
@client = Twitter::Client.new
|
151
|
+
end
|
152
|
+
|
153
|
+
def config=(h)
|
154
|
+
@cfg.merge!(h)
|
155
|
+
File.open("#{DATA_DIR}/#{AUTH_FILE}", "w") { |file|
|
156
|
+
YAML.dump(@cfg, file)
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def config
|
161
|
+
@cfg
|
162
|
+
end
|
163
|
+
|
164
|
+
def reset(*args)
|
165
|
+
if File.exist?("#{DATA_DIR}/#{AUTH_FILE}") then
|
166
|
+
File.unlink("#{DATA_DIR}/#{AUTH_FILE}")
|
167
|
+
else
|
168
|
+
warn "Already reset"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def logout(*args)
|
173
|
+
self.config = {:atoken=>nil,:asecret=>nil}
|
174
|
+
end
|
175
|
+
|
176
|
+
def query(kind, user=nil)
|
177
|
+
puts "SHOWING LAST #{@count == 0 ? "UNREAD" : @count} #{kind.to_s.human} MESSAGES:"
|
178
|
+
self.connect
|
179
|
+
actual_timeline = @client.method(kind.to_sym)
|
180
|
+
call_arg = Hash.new
|
181
|
+
if @cfg[:count] and @cfg[:count] > 0 then
|
182
|
+
call_arg = {:count=>@count}
|
183
|
+
elsif @latest[kind]
|
184
|
+
call_arg = {:since_id=>@latest[kind]}
|
185
|
+
end
|
186
|
+
if user then
|
187
|
+
call_arg[:id] = user
|
188
|
+
end
|
189
|
+
begin
|
190
|
+
tweets = actual_timeline.call(call_arg).reverse
|
191
|
+
rescue SocketError
|
192
|
+
warn "Connection error. Perhaps you're not connected to the Internet?"
|
193
|
+
return
|
194
|
+
rescue
|
195
|
+
warn "Unknown error during connection with Twitter. Error was:"
|
196
|
+
puts $!
|
197
|
+
return
|
198
|
+
end
|
199
|
+
tweets.each {|tweet|
|
200
|
+
if @raw then
|
201
|
+
pp tweet
|
202
|
+
else
|
203
|
+
@latest[kind.to_sym] = tweet.id
|
204
|
+
time = tweet.created_at
|
205
|
+
user = (kind == :direct_messages ? tweet.sender : tweet.user)
|
206
|
+
if @cfg[:compact] then
|
207
|
+
timeshort = time.strftime("%m%d%H%M")
|
208
|
+
head = "#{user.screen_name.color(@color)}/#{timeshort} "
|
209
|
+
indent = (user.screen_name.length+timeshort.length+2)
|
210
|
+
else
|
211
|
+
timeshort = time.strftime("%m/%d %H:%M")
|
212
|
+
head = "[#{user.screen_name.color(@color)} #{timeshort}] "
|
213
|
+
indent = (user.screen_name.length+timeshort.length+4)
|
214
|
+
end
|
215
|
+
text = tweet.text.partition_by_words((@cfg[:width] || DEFAULT_WIDTH)-indent, @color)
|
216
|
+
text = text*("\n"+" "*indent)
|
217
|
+
puts "#{head}#{text}"
|
218
|
+
puts if @cfg[:space]
|
219
|
+
end
|
220
|
+
}
|
221
|
+
self.config = {:latest => @latest}
|
222
|
+
end
|
223
|
+
|
224
|
+
def post(msg)
|
225
|
+
if msg == nil or msg.length == 0 then
|
226
|
+
warn "Tweet text must not be empty"
|
227
|
+
return
|
228
|
+
end
|
229
|
+
if msg.length >= 140 then
|
230
|
+
msg = msg[0...137]+"..."
|
231
|
+
msg_short = msg.partition_by_words((@cfg[:width] || DEFAULT_WIDTH)-4, @color) * ("\n"+" "*4)
|
232
|
+
warn "your message has been shortened to 140 chars:\n #{msg_short}\nSend anyway [Y/n]?"
|
233
|
+
reply = STDIN.getc.upcase
|
234
|
+
return if reply.upcase == 'N'
|
235
|
+
puts "sending #{msg.length}"
|
236
|
+
end
|
237
|
+
begin
|
238
|
+
self.connect
|
239
|
+
@client.update(msg)
|
240
|
+
puts "Succesfully posted \"#{msg}\""
|
241
|
+
rescue SocketError
|
242
|
+
warn "Error posting your message. Perhaps you're not connected to the Internet?"
|
243
|
+
queue = @cfg[:queue] || []
|
244
|
+
queue << msg
|
245
|
+
self.config = {:queue => queue}
|
246
|
+
puts "Your message has been queued. Issue 'twt deliver' next time you go online"
|
247
|
+
rescue
|
248
|
+
warn "Unknown error during connection with Twitter. Error was:"
|
249
|
+
puts $!
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def dm(args)
|
254
|
+
name, msg = args
|
255
|
+
begin
|
256
|
+
id = Twitter.user(name).id
|
257
|
+
rescue Twitter::Error::NotFound
|
258
|
+
warn "Check user name: @#{name} not found."
|
259
|
+
end
|
260
|
+
if msg == nil or msg.length == 0 then
|
261
|
+
warn "Tweet text must not be empty"
|
262
|
+
return
|
263
|
+
end
|
264
|
+
if msg.length >= 140 then
|
265
|
+
msg = msg[0...137]+"..."
|
266
|
+
msg_short = msg.partition_by_words((@cfg[:width] || DEFAULT_WIDTH)-4, @color) * ("\n"+" "*4)
|
267
|
+
warn "your message has been shortened to 140 chars:\n #{msg_short}\nSend anyway [Y/n]?"
|
268
|
+
reply = STDIN.getc.upcase
|
269
|
+
return if reply.upcase == 'N'
|
270
|
+
puts "sending #{msg.length}"
|
271
|
+
end
|
272
|
+
begin
|
273
|
+
self.connect
|
274
|
+
puts "sending #{msg} to #{id} "
|
275
|
+
@client.direct_message_create(id, msg)
|
276
|
+
puts "Succesfully posted \"#{msg}\" to user @#{name} (#{id})"
|
277
|
+
rescue SocketError
|
278
|
+
warn "Error posting your message. Perhaps you're not connected to the Internet?"
|
279
|
+
queue = @cfg[:queue] || []
|
280
|
+
queue << msg
|
281
|
+
self.config = {:queue => queue}
|
282
|
+
puts "Your message has been queued. Issue 'twt deliver' next time you go online"
|
283
|
+
rescue
|
284
|
+
warn "Unknown error during connection with Twitter. Error was:"
|
285
|
+
puts $!
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
def queue(*args)
|
291
|
+
if @cfg[:queue] and not @cfg[:queue].empty?
|
292
|
+
@cfg[:queue].each_with_index do |m,i|
|
293
|
+
puts "#{i}. #{m}"
|
294
|
+
end
|
295
|
+
else
|
296
|
+
puts "Message queue is empty"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def dequeue(list=[])
|
301
|
+
if list.empty?
|
302
|
+
self.config = {:queue => []}
|
303
|
+
puts "Message queue is now empty"
|
304
|
+
else
|
305
|
+
queue = @cfg[:queue] || []
|
306
|
+
list.map! {|e| e.to_i}
|
307
|
+
list.reject! {|i| i >= queue.length}
|
308
|
+
self.config = {:queue => queue-queue.values_at(*list)}
|
309
|
+
puts "Messages #{list * ", "} dequeued"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def deliver(*args)
|
314
|
+
begin
|
315
|
+
self.connect
|
316
|
+
@client.mentions
|
317
|
+
rescue SocketError
|
318
|
+
warn "Still not connected, unable to deliver. Messages remain in queue."
|
319
|
+
return
|
320
|
+
end
|
321
|
+
if @cfg[:queue] and not @cfg[:queue].empty?
|
322
|
+
puts "Delivering #{@cfg[:queue].length} queued messages:"
|
323
|
+
@cfg[:queue].each_with_index do |m,i|
|
324
|
+
print "message #{i}... "
|
325
|
+
self.post m
|
326
|
+
puts "sent"
|
327
|
+
end
|
328
|
+
self.dequeue
|
329
|
+
else
|
330
|
+
puts "Message queue is empty, cannot deliver"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def delta(*args)
|
335
|
+
self.connect
|
336
|
+
previous_ids = @cfg[:followers] || []
|
337
|
+
ids = @client.follower_ids.ids
|
338
|
+
difference_plus = ids - previous_ids
|
339
|
+
difference_minus = previous_ids - ids
|
340
|
+
puts "Last time followers: #{previous_ids.size}\nNow you have #{ids.size} follower(s)"
|
341
|
+
print "New followers (#{difference_plus.size}):\n "
|
342
|
+
puts difference_plus.size > 0 ? ids_to_names(difference_plus) * "\n " : "none"
|
343
|
+
print "Lost followers (#{difference_minus.size}):\n "
|
344
|
+
puts difference_minus.size > 0 ? ids_to_names(difference_minus) * "\n " : "none"
|
345
|
+
self.config = {:followers => ids}
|
346
|
+
rescue Twitter::Error::BadRequest
|
347
|
+
warn "Rate limit exceeded. Try again later"
|
348
|
+
rescue
|
349
|
+
puts $!
|
350
|
+
end
|
351
|
+
|
352
|
+
def ids_to_names(ary)
|
353
|
+
ary.map {|e|
|
354
|
+
user = Twitter.user(e)
|
355
|
+
"@#{user.screen_name.downcase}: #{user.name}"
|
356
|
+
}.sort
|
357
|
+
rescue Twitter::Error::ServiceUnavailable
|
358
|
+
warn "Twitter is over capacity. Try again later"
|
359
|
+
return []
|
360
|
+
rescue Twitter::Error::BadRequest
|
361
|
+
warn "Rate limit exceeded. Try again later"
|
362
|
+
return []
|
363
|
+
rescue
|
364
|
+
puts $!
|
365
|
+
end
|
366
|
+
|
367
|
+
def check_version
|
368
|
+
self.config = {:last_version_check => Time.now} unless @cfg[:last_version_check]
|
369
|
+
if (Time.now - @cfg[:last_version_check]) > VERSION_CHECK_PERIOD
|
370
|
+
gemcutter_response = Net::HTTP.get_response("rubygems.org","/api/v1/gems/twt.json")
|
371
|
+
current_version = JSON.parse(gemcutter_response.body)["version"]
|
372
|
+
must_upgrade = version_hash(current_version) > version_hash(TWT_VERSION)
|
373
|
+
self.config = {:last_version_check => Time.now} unless must_upgrade
|
374
|
+
return [must_upgrade, current_version]
|
375
|
+
else
|
376
|
+
return [false, current_version]
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def following(action, who)
|
381
|
+
self.connect
|
382
|
+
who.each do |user|
|
383
|
+
begin
|
384
|
+
case action
|
385
|
+
when :add
|
386
|
+
result = @client.friendship_create(user)
|
387
|
+
puts "You are now following #{user.color(@color)}'s updates"
|
388
|
+
when :remove
|
389
|
+
result = @client.friendship_destroy(user)
|
390
|
+
puts "You are no more following #{user.color(@color)}'s updates"
|
391
|
+
end
|
392
|
+
rescue
|
393
|
+
warn "Error in #{action == :add ? 'following' : 'unfollowing'} #{user.color(@color)}: #{$!}"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
def version_hash(string)
|
399
|
+
c = string.split(".")
|
400
|
+
return c[0]*10_000 + c[1]*100 + c[2]
|
401
|
+
end
|
402
|
+
private :version_hash
|
403
|
+
end
|
404
|
+
|
405
|
+
twt = Twt.new
|
406
|
+
opts = OptionParser.new
|
407
|
+
opts.banner = "Usage: twt [options] #{COMMANDS*'|'} [argument]\nOptions are:"
|
408
|
+
|
409
|
+
begin
|
410
|
+
opts.on("-cCOUNT", "--count COUNT", "Messages to get (sticky)", Integer) {|v|
|
411
|
+
twt.config = {:count => v}
|
412
|
+
}
|
413
|
+
opts.on("-wCOUNT", "--width COUNT", "Set message width (sticky)", Integer) {|v|
|
414
|
+
twt.config = {:width => v}
|
415
|
+
}
|
416
|
+
opts.on("-r", "--raw", "Output raw data", TrueClass) {|v|
|
417
|
+
twt.raw = v
|
418
|
+
}
|
419
|
+
opts.on("-s", "--space", "Toggle empty line between tweets (sticky)", TrueClass) {|v|
|
420
|
+
twt.config = {:space => !twt.config[:space]}
|
421
|
+
}
|
422
|
+
opts.on("-p", "--compact", "Toggle compact heading (sticky)", TrueClass) {|v|
|
423
|
+
twt.config = {:compact => !twt.config[:compact]}
|
424
|
+
}
|
425
|
+
opts.on("-kCOLOR", "--color COLOR", "Set color for user names (sticky)", String) {|v|
|
426
|
+
if String::COLORS.include? v.to_sym
|
427
|
+
twt.config = {:color => v}
|
428
|
+
else
|
429
|
+
warn "Supported colors are:\n#{String::COLORS.keys * ", "}.\nNo change has been made."
|
430
|
+
end
|
431
|
+
}
|
432
|
+
opts.on("-v", "--version", "Print program version") {
|
433
|
+
puts "twt version #{TWT_VERSION}"
|
434
|
+
exit
|
435
|
+
}
|
436
|
+
|
437
|
+
rest = opts.parse ARGV
|
438
|
+
CMD = rest.shift
|
439
|
+
ARG = rest
|
440
|
+
raise OptsError unless COMMANDS.include?(CMD)
|
441
|
+
|
442
|
+
if not File.exist?("#{DATA_DIR}/#{AUTH_FILE}") then
|
443
|
+
puts "Welcome! you're using twt version #{TWT_VERSION}.\nPlease drop a tweet to @P4010 if you like it!"
|
444
|
+
end
|
445
|
+
|
446
|
+
case CMD
|
447
|
+
when "logout"
|
448
|
+
twt.logout
|
449
|
+
when "post"
|
450
|
+
twt.post(ARG[0])
|
451
|
+
when "dm"
|
452
|
+
twt.dm(ARG)
|
453
|
+
when "dms"
|
454
|
+
twt.query(:direct_messages)
|
455
|
+
when "user"
|
456
|
+
twt.query(:user_timeline, ARG[0])
|
457
|
+
when "friends"
|
458
|
+
twt.query(:home_timeline)
|
459
|
+
when "mention"
|
460
|
+
twt.query(:mentions)
|
461
|
+
when "dequeue"
|
462
|
+
twt.dequeue(rest)
|
463
|
+
when "follow"
|
464
|
+
twt.following(:add, rest)
|
465
|
+
when "unfollow"
|
466
|
+
twt.following(:remove, rest)
|
467
|
+
else
|
468
|
+
twt.send(CMD, ARG) if twt.respond_to? CMD
|
469
|
+
end
|
470
|
+
|
471
|
+
if twt.check_version[0] then
|
472
|
+
warn "New twt version (#{twt.check_version[1]}) is available. You are currently using version #{TWT_VERSION}.\nUpdate with \"gem update twt\""
|
473
|
+
end
|
474
|
+
rescue OptsError
|
475
|
+
warn "Must provide a command (either #{COMMANDS.inspect})"
|
476
|
+
puts opts.to_s
|
477
|
+
exit
|
478
|
+
rescue OptionParser::InvalidArgument
|
479
|
+
warn "Invalid option argument"
|
480
|
+
puts opts.to_s
|
481
|
+
exit
|
482
|
+
rescue Twitter::Error::Unauthorized
|
483
|
+
warn "Invalid authentication. Login with twt login user:pass"
|
484
|
+
exit
|
485
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paolo Bosetti (@P4010)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: twitter
|
16
|
+
requirement: &70359835859760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.1.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70359835859760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: oauth
|
27
|
+
requirement: &70359835859140 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.4.5
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70359835859140
|
36
|
+
description: twt is a command line interface (CLI) Twitter client. Now you can monitor
|
37
|
+
your followers and queue your posts when you're not online!'
|
38
|
+
email: p4010@me.com
|
39
|
+
executables:
|
40
|
+
- twt
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- README.markdown
|
45
|
+
- bin/twt
|
46
|
+
homepage: http://github.com/pbosetti/twt
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- bin
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.10
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A no-fluff CLI for Twitter.
|
70
|
+
test_files: []
|
71
|
+
has_rdoc: false
|