Skip to content

Instantly share code, notes, and snippets.

@siyo
Created December 7, 2010 17:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save siyo/732069 to your computer and use it in GitHub Desktop.
Save siyo/732069 to your computer and use it in GitHub Desktop.
oreore twitter cli client
#
# this file format is YAML
#
acount_basic:
screen_name1:
user: screen_name1
password: password1
screen_name2:
user: screen_name2
password: password2
oauth:
consumer_key: ov6TGW2mibW0BrJsRfg7DQ
consumer_secret: RgaRRSLPAOxt8lcgZCiJP6guaBISg5XbM6ZL8YCnGs
access_token_secret: fizz
access_token: buzz
#!/usr/bin/env ruby
require 'logger'
require 'rubygems'
require 'rubytter'
require 'tweetstream'
require 'pp'
require 'yaml_waml'
require 'twitpic'
require 'time'
require 'readline'
require 'terminal-color'
require 'ruby-growl'
require 'observer'
class MyOAuth
require 'oauth'
def initialize(conf)
@consumer_key = conf.fetch('consumer_key')
@consumer_secret = conf.fetch('consumer_secret')
@access_token = conf.fetch('access_token')
@access_secret = conf.fetch('access_token_secret')
@site = 'http://twitter.com'
end
def consumer
@consmer ||= OAuth::Consumer.new( @consumer_key,
@consumer_secret,
:site => @site )
end
def token
@token ||= OAuth::AccessToken.new( consumer,
@access_token,
@access_secret )
end
end
class TwStream
include Observable
def initialize(conf, twitter, options={})
raise ArgumentError unless twitter.is_a? Rubytter
@twitter = twitter
@logger = Logger.new(STDOUT)
@client = TweetStream::Client.new(conf.fetch('user'), conf.fetch('password'))
@tl_confs = timeline_confs(options)
@client.on_error {|error| abort('Error!' + error) }
@client.on_limit {|skip_count| @logger.warn "Skipped #{skip_count}" }
end
def run
query = {}
query[:follow] = @tl_confs[:tl_users].inject([]){|s,u| s += u[:ids]} if @tl_confs[:tl_users]
query[:track] = @tl_confs[:search_words].map{|e| e[:word]} if @tl_confs[:search_words]
@client.filter(query){|st, cl|
changed
notify_observers(st,@tl_confs)
}
end
private
def timeline_confs(options)
confs = {}
if options.key? :tl_users
confs[:tl_users] = []
options[:tl_users].each_with_index{|user,i|
tbl = {}
tbl[:user] = user
tbl[:ids] = @twitter.friends_ids(user)
tbl[:index] = i
confs[:tl_users] << tbl
}
end
if options.key? :search_words
confs[:search_words] = []
options[:search_words].each_with_index{|word,i|
tbl = {}
tbl[:word] = word
tbl[:index] = i
confs[:search_words] << tbl
}
end
confs
end
end
class TwStreamStatus
def update(status,confs={})
@status = status
conf = nil
if confs[:tl_users]
conf = confs[:tl_users].detect{|e| e[:ids].include?(@status.user.id)}
end
if confs[:search_words]
conf = confs[:search_words].detect{|e| /#{e[:word]}/i =~ @status.text}
end
return unless conf
@conf = conf
output
end
end
class TwStreamStatusAnsiTerm < TwStreamStatus
COLORS = [:white,:cyan,:magenta,:green,:red,:yellow,:blue]
def output
puts format_status
end
def format_status
@conf[:user] = 'pub' unless @conf[:user]
color = COLORS[@conf[:index] % COLORS.size]
strtime = Time.parse(@status.created_at).strftime("%X")
"%-24s %s %s %s" % [ (@status.user.screen_name).make_colorized(color),
@status.text.gsub(/\n+/," ").make_colorized(color),
("at " + strtime).make_colorized(:black),
("/ via " + @conf[:user]).make_colorized(:black), ]
end
end
class TwStreamStatusGrowl < TwStreamStatus
GROWL_TYPE = "user streams"
def initialize
@growl = Growl.new('localhost', "Tw streams", [GROWL_TYPE])
end
def output
via_user = @conf[:user] ? " via #{@conf[:user]}" : ""
search_word = @conf[:word] ? " (#{@conf[:word]})" : ""
@growl.notify( GROWL_TYPE,
@status.user.screen_name + via_user + search_word,
@status.text )
end
end
class Tw
require 'optparse'
attr_reader :twitter
def initialize
@conf_path = "#{ENV['HOME']}/.tw"
@conf = YAML.load_file(@conf_path)
access_token = MyOAuth.new(@conf['oauth']).token
@twitter = OAuthRubytter.new(access_token)
@api_list = Rubytter.api_settings
end
def run(argv)
api = ""
photo = ""
disp_format = "yaml"
api_list = false
stream_users = []
stream_words = []
stream_outputs = ['stdout']
account = "siyo"
argv.options{|opt|
opt.banner="#{$0} your status [options]"
opt.on('-e API','twitter API (syntax: api args...)'){|v| api = v}
opt.on('-f format','display format of twitter API result [yaml(default), timeline(or tl)]'){|v| disp_format = v}
opt.on('-p photo','post image file to twitpic'){|v| photo = v}
opt.on('-a', '--account ACCOUNT',''){|v| account = v}
opt.on('--stream-user USER1, USER2...','following ids of specific user streams. '){|v| stream_users = v.split(",")}
opt.on('--stream-word WORD1, WORD2...','search words in user streams.[e.g. stdout,growl]'){|v| stream_words = v.split(",")}
opt.on('--stream-output OUT1,OUTP2...',''){|v| stream_outputs = v.split(",")}
opt.on('--apilist','display twitter API list'){
@api_list.each{|e| puts "%-24s ... %s" % e }
exit
}
opt.on('-d','--diff-followers', 'diff saved followers ids in ~/.tw'){
diff_followers_ids
exit
}
opt.parse!
}
if not ( stream_users.empty? && stream_words.empty?)
options = {}
options[:tl_users] = stream_users unless stream_users.empty?
options[:search_words] = stream_words unless stream_words.empty?
stream = TwStream.new(@conf['account_basic'][account],@twitter,options)
stream.add_observer(TwStreamStatusAnsiTerm.new) if stream_outputs.detect{|e| /(stdout|term)/i =~ e}
stream.add_observer(TwStreamStatusGrowl.new) if stream_outputs.detect{|e| /growl/i =~ e}
stream.run
exit
end
if not api.empty?
send_api(api, argv, disp_format)
end
if not photo.empty?
conf = @conf['account_basic'][account]
argv << to_twitpic(conf['user'],conf['password'],photo)
end
if argv.empty?
ARGF.each{|e|
next if e.empty?
puts e
@twitter.update e
}
else
puts str = argv.join(" ")
@twitter.update( str )
end
end
private
def send_api(api, args, disp_format='yaml')
raise "'#{api}' is not API." unless @api_list.map{|e| e[0]}.include? api
ret = @twitter.send(api, *args)
exit unless ret
yaml = YAML.dump(ret)
puts yaml2disp_format(yaml, disp_format)
exit
end
def to_twitpic(user,password,file)
ret = TwitPic.new(user,password).upload(file)
ret[:mediaurl]
end
def yaml2disp_format(yaml, disp_format)
case disp_format
when 'yaml','yml'
yaml
when 'timeline','tl'
str = ""
YAML.load(yaml).each_with_index{|e,i|
str << "%-2d %s: %s / %s\n" % [ i + 1,
e[:user][:screen_name],
e[:text].gsub("\n", " "),
Time.parse(e[:created_at]).strftime("%X") ]
}
str
else
raise "invalid display format : '#{format}'"
end
end
def save_followers_ids
ids = @twitter.followers_ids(@twitter.login)
@conf['followers_ids'] = ids
File.open(@conf_path,"wb"){|f| f.write(YAML.dump(@conf))}
end
def diff_followers_ids
saved_ids = @conf['followers_ids']
current_ids = @twitter.followers_ids(@twitter.login)
puts "number of followers : %d -> %d" % [saved_ids.size, current_ids.size]
proc = Proc.new{|id|
begin
str = @twitter.user(id).screen_name
rescue => e
str = e.to_s
end
str
}
str = (current_ids - saved_ids).inject(""){ |s,e| s << "+ %s\n" % [proc.call(e)] }
str = (saved_ids - current_ids).inject(str){|s,e| s << "- %s\n" % [proc.call(e)] }
if str.empty?
return
else
puts str
end
while buf = Readline.readline("save followers ids? : [y/N] ")
break if buf.empty?
case buf
when /y/i
save_followers_ids
puts "saved."
break
when /n/i
break
else
next
end
end
end
end
Tw.new.run(ARGV) if __FILE__ == $0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment