├── lib ├── tw │ ├── version.rb │ ├── client │ │ ├── error.rb │ │ ├── helper.rb │ │ ├── tweet.rb │ │ ├── stream.rb │ │ ├── auth.rb │ │ └── request.rb │ ├── app │ │ ├── helper.rb │ │ ├── opt_parser.rb │ │ ├── render.rb │ │ ├── main.rb │ │ └── cmds.rb │ └── conf.rb └── tw.rb ├── .gitignore ├── Gemfile ├── Rakefile ├── test ├── test_helper.rb ├── test_t_co.rb └── test_tw.rb ├── bin └── tw ├── samples ├── README.md ├── stream.rb ├── sample.rb └── update_profile_image.rb ├── LICENSE.txt ├── tw.gemspec ├── Gemfile.lock ├── README.md └── History.txt /lib/tw/version.rb: -------------------------------------------------------------------------------- 1 | module Tw 2 | VERSION = '1.3.1' 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *#* 3 | *~ 4 | .ruby-version 5 | pkg/* 6 | -------------------------------------------------------------------------------- /lib/tw/client/error.rb: -------------------------------------------------------------------------------- 1 | module Tw 2 | class Error < StandardError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in tw.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new do |t| 5 | t.pattern = "test/test_*.rb" 6 | end 7 | 8 | task :default => :test 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'minitest/autorun' 4 | 5 | $:.unshift File.expand_path '../lib', File.dirname(__FILE__) 6 | require 'tw' 7 | -------------------------------------------------------------------------------- /bin/tw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.expand_path '../lib', File.dirname(__FILE__) 3 | require 'rubygems' 4 | require 'tw' 5 | require 'tw/app/main' 6 | $stdout.sync = true 7 | 8 | app = Tw::App.new 9 | app.run ARGV 10 | -------------------------------------------------------------------------------- /lib/tw/app/helper.rb: -------------------------------------------------------------------------------- 1 | 2 | class String 3 | def colorize(pattern) 4 | self.split(/(#{pattern})/).map{|term| 5 | if term =~ /#{pattern}/ 6 | term = Rainbow(term).color(Tw::App::Render.color_code term).bright.underline 7 | end 8 | term 9 | }.join('') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # tw samples 2 | 3 | ## Tw API 4 | 5 | % ruby sample.rb hello woooorld 6 | 7 | 8 | ## Tw Stream API 9 | 10 | % ruby stream.rb 11 | 12 | 13 | ## Use Tw as front-end of twitter auth 14 | 15 | % tw --user:add 16 | % ruby upload_profile_image.rb image.jpg 17 | % ruby upload_profile_image.rb image.jpg shokai 18 | 19 | -------------------------------------------------------------------------------- /samples/stream.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.expand_path '../lib', File.dirname(__FILE__) 3 | require 'rubygems' 4 | require 'tw' 5 | 6 | user = ARGV.shift 7 | client = Tw::Client::Stream.new user 8 | 9 | loop do 10 | begin 11 | client.user_stream do |m| 12 | puts m 13 | end 14 | rescue StandardError, Timeout::Error => e 15 | puts e.message 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_t_co.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path 'test_helper', File.dirname(__FILE__) 3 | 4 | class TestTCo < MiniTest::Test 5 | 6 | def setup 7 | @client = Tw::Client.new 8 | @client.auth 9 | end 10 | 11 | def test_t_co_char_length 12 | msg = "blog書いた → http://shokai.org/blog/archives/6513" 13 | assert msg.char_length > msg.char_length_with_t_co 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /samples/sample.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.expand_path '../lib', File.dirname(__FILE__) 3 | require 'rubygems' 4 | require 'tw' 5 | 6 | if ARGV.empty? 7 | STDERR.puts "ruby #{$0} hello world" 8 | exit 1 9 | end 10 | 11 | msg = ARGV.join(' ') 12 | 13 | client = Tw::Client.new 14 | client.auth 15 | client.mentions.each do |m| 16 | puts m 17 | end 18 | 19 | begin 20 | p client.tweet msg 21 | rescue => e 22 | STDERR.puts e 23 | exit 1 24 | end 25 | -------------------------------------------------------------------------------- /samples/update_profile_image.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift File.expand_path '../lib', File.dirname(__FILE__) 3 | require 'rubygems' 4 | require 'tw' 5 | 6 | if ARGV.empty? 7 | STDERR.puts "ruby #{$0} image.jpg" 8 | STDERR.puts "ruby #{$0} image.jpg USERNAME" 9 | exit 1 10 | end 11 | 12 | icon, user = ARGV 13 | 14 | client = Tw::Client.new 15 | client.auth user 16 | 17 | File.open icon do |f| 18 | puts 'upload success' if Tw::Client.client.update_profile_image f 19 | end 20 | -------------------------------------------------------------------------------- /lib/tw/client/helper.rb: -------------------------------------------------------------------------------- 1 | 2 | class String 3 | 4 | def char_length 5 | self.split(//u).map{|i| i.bytes.length > 1 ? 2 : 1}.reduce(:+) 6 | end 7 | 8 | def char_length_with_t_co 9 | Tw::Conf.update_twitter_config 10 | len = self.char_length 11 | self.scan(Regexp.new "https?://[^\s]+").each do |url| 12 | len += (url =~ /^https/ ? Tw::Conf['twitter_config']['short_url_length_https'] : Tw::Conf['twitter_config']['short_url_length']) - url.char_length 13 | end 14 | len 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/tw/app/opt_parser.rb: -------------------------------------------------------------------------------- 1 | 2 | module Tw::App 3 | class Main 4 | def username?(arg) 5 | arg =~ /^@[a-zA-Z0-9_]+$/ ? arg.scan(/^@([a-zA-Z0-9_]+)$/)[0][0] : false 6 | end 7 | 8 | def listname?(arg) 9 | arg =~ /^@[a-zA-Z0-9_]+\/[a-zA-Z0-9_-]+$/ ? arg.scan(/^@([a-zA-Z0-9_]+)\/([a-zA-Z0-9_-]+)$/)[0] : false 10 | end 11 | 12 | def request?(arg) 13 | username?(arg) or listname?(arg) 14 | end 15 | 16 | def all_requests?(args) 17 | !args.map{|arg| request? arg}.include?(false) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/tw.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__)) unless 2 | $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) 3 | 4 | require 'oauth' 5 | require 'twitter' 6 | require 'time' 7 | require 'yaml' 8 | require 'time' 9 | require 'cgi' 10 | require 'json' 11 | require 'args_parser' 12 | require 'rainbow' 13 | require 'parallel' 14 | require 'launchy' 15 | require 'tw/conf' 16 | require 'tw/client/auth' 17 | require 'tw/client/request' 18 | require 'tw/client/tweet' 19 | require 'tw/client/stream' 20 | require 'tw/client/error' 21 | require 'tw/client/helper' 22 | require 'tw/version' 23 | 24 | module Tw 25 | class Conf 26 | REQUIRE_VERSION = '0.1.0' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/tw/client/tweet.rb: -------------------------------------------------------------------------------- 1 | module Tw 2 | class Tweet 3 | attr_reader :id, :user, :text, :time, :fav_count, :rt_count 4 | 5 | def initialize(opts) 6 | @id = opts[:id] 7 | @user = opts[:user] 8 | @text = opts[:text] 9 | @time = opts[:time].getlocal 10 | @fav_count = opts[:fav_count] 11 | @rt_count = opts[:rt_count] 12 | end 13 | 14 | def to_json(*a) 15 | { 16 | :id => @id, 17 | :user => @user, 18 | :text => @text, 19 | :time => @time, 20 | :fav_count => @fav_count, 21 | :rt_count => @rt_count 22 | }.to_json(*a) 23 | end 24 | 25 | def url 26 | "https://twitter.com/#{user}/status/#{id}" 27 | end 28 | 29 | def to_s 30 | "@#{user} #{text} - #{time} #{url}" 31 | end 32 | 33 | def format(fmt) 34 | self.instance_eval "\"#{fmt}\"" 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Sho Hashimoto 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tw.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'tw/version' 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = "tw" 7 | gem.version = Tw::VERSION 8 | gem.authors = ["Sho Hashimoto"] 9 | gem.email = ["hashimoto@shokai.org"] 10 | gem.description = %q{CUI Twitter Client.} 11 | gem.summary = gem.description 12 | gem.homepage = "http://shokai.github.io/tw" 13 | gem.license = "MIT" 14 | 15 | gem.files = `git ls-files`.split($/).reject{|i| i=="Gemfile.lock" } 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | gem.add_dependency 'twitter', '>= 6.2.0' 21 | gem.add_dependency 'oauth', '>= 0.5.4' 22 | gem.add_dependency 'json', '>= 2.1.0' 23 | gem.add_dependency 'rainbow', '>= 3.0.0' 24 | gem.add_dependency 'args_parser', '>= 0.2.0' 25 | gem.add_dependency 'parallel', '>= 1.12.0' 26 | gem.add_dependency 'launchy', '>= 2.4.3' 27 | 28 | gem.add_development_dependency 'rake' 29 | gem.add_development_dependency 'bundler', '~> 1.15' 30 | gem.add_development_dependency 'minitest' 31 | end 32 | -------------------------------------------------------------------------------- /test/test_tw.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path 'test_helper', File.dirname(__FILE__) 2 | 3 | class TestTw < MiniTest::Test 4 | 5 | def setup 6 | @client = Tw::Client.new 7 | @client.auth 8 | end 9 | 10 | def user?(str) 11 | return false unless str.kind_of? String 12 | str =~ /^[a-zA-Z0-9_]+$/ 13 | end 14 | 15 | def response?(arr) 16 | return false unless arr.kind_of? Array 17 | arr.each do |i| 18 | return false if !(i.id.class == Integer and 19 | (user? i.user or (user? i.user[:to] and user? i.user[:from])) and 20 | i.text.kind_of? String and 21 | i.time.kind_of? Time) 22 | end 23 | return true 24 | end 25 | 26 | def test_mentions 27 | assert response? @client.mentions 28 | end 29 | 30 | def test_home_timeline 31 | assert response? @client.home_timeline 32 | end 33 | 34 | def test_search 35 | assert response? @client.search('ruby') 36 | end 37 | 38 | def test_user_timeline 39 | assert response? @client.user_timeline 'shokai' 40 | end 41 | 42 | def test_list_timeline 43 | assert response? @client.list_timeline('shokai', 'tw-user') 44 | end 45 | 46 | def test_direct_message 47 | assert response? @client.direct_messages 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/tw/client/stream.rb: -------------------------------------------------------------------------------- 1 | 2 | module Tw 3 | class Client::Stream 4 | def initialize(user=nil) 5 | user = Tw::Auth.get_or_regist_user user 6 | @client = Twitter::Streaming::Client.new do |config| 7 | config.consumer_key = Conf['consumer_key'] 8 | config.consumer_secret = Conf['consumer_secret'] 9 | config.access_token = user['access_token'] 10 | config.access_token_secret = user['access_secret'] 11 | end 12 | end 13 | 14 | def user_stream(&block) 15 | raise ArgumentError, 'block not given' unless block_given? 16 | @client.user do |chunk| 17 | if tweet = get_if_tweet(chunk) 18 | yield tweet 19 | end 20 | end 21 | end 22 | 23 | def filter(*track_words, &block) 24 | raise ArgumentError, 'block not given' unless block_given? 25 | @client.filter :track => track_words.join(',') do |chunk| 26 | if tweet = get_if_tweet(chunk) 27 | yield tweet 28 | end 29 | end 30 | end 31 | 32 | private 33 | def get_if_tweet(chunk) 34 | return nil unless chunk.kind_of? Twitter::Tweet 35 | Tw::Tweet.new(:id => chunk.id, 36 | :user => chunk.user.screen_name, 37 | :text => chunk.text, 38 | :time => chunk.created_at) 39 | end 40 | 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/tw/app/render.rb: -------------------------------------------------------------------------------- 1 | module Tw::App 2 | class Render 3 | 4 | def self.silent=(bool) 5 | @@silent = bool ? true : false 6 | end 7 | 8 | def self.silent 9 | @@silent ||= false 10 | end 11 | 12 | def self.show_status_id=(bool) 13 | @@show_status_id = bool ? true : false 14 | end 15 | 16 | def self.show_status_id 17 | @@show_status_id ||= false 18 | end 19 | 20 | def self.puts(s) 21 | STDOUT.puts s unless silent 22 | end 23 | 24 | def self.color_code(str) 25 | colors = Rainbow::Color::Named::NAMES.keys - [:default, :black, :white] 26 | n = str.each_byte.map{|c| c.to_i}.inject{|a,b|a+b} 27 | return colors[n%colors.size] 28 | end 29 | 30 | def self.display(arr, format) 31 | arr = [arr] unless arr.kind_of? Array 32 | arr.flatten.inject({}){ 33 | |h,i| h[i.id]=i; h 34 | }.values.sort{|a,b| 35 | a.id <=> b.id 36 | }.each{|m| 37 | STDOUT.puts case format 38 | when 'text' 39 | user = m.user.kind_of?(Hash) ? "@#{m.user[:from]} > @#{m.user[:to]}" : "@#{m.user}" 40 | line = "#{m.time.strftime '[%m/%d %a] (%H:%M:%S)'} #{user} : #{CGI.unescapeHTML m.text}" 41 | line += " #{m.fav_count}Fav" if m.fav_count.to_i > 0 42 | line += " #{m.rt_count}RT" if m.rt_count.to_i > 0 43 | line += " <#{m.id}>" if show_status_id 44 | line.colorize(/@[a-zA-Z0-9_]+|\d+RT|\d+Fav/) 45 | when 'json' 46 | m.to_json 47 | else 48 | m.format format 49 | end 50 | } 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | tw (1.3.1) 5 | args_parser (>= 0.2.0) 6 | json (>= 2.1.0) 7 | launchy (>= 2.4.3) 8 | oauth (>= 0.5.4) 9 | parallel (>= 1.12.0) 10 | rainbow (>= 3.0.0) 11 | twitter (>= 6.2.0) 12 | 13 | GEM 14 | remote: https://rubygems.org/ 15 | specs: 16 | addressable (2.6.0) 17 | public_suffix (>= 2.0.2, < 4.0) 18 | args_parser (0.2.0) 19 | buftok (0.2.0) 20 | domain_name (0.5.20180417) 21 | unf (>= 0.0.5, < 1.0.0) 22 | equalizer (0.0.11) 23 | http (3.3.0) 24 | addressable (~> 2.3) 25 | http-cookie (~> 1.0) 26 | http-form_data (~> 2.0) 27 | http_parser.rb (~> 0.6.0) 28 | http-cookie (1.0.3) 29 | domain_name (~> 0.5) 30 | http-form_data (2.1.1) 31 | http_parser.rb (0.6.0) 32 | json (2.2.0) 33 | launchy (2.4.3) 34 | addressable (~> 2.3) 35 | memoizable (0.4.2) 36 | thread_safe (~> 0.3, >= 0.3.1) 37 | minitest (5.11.3) 38 | multipart-post (2.0.0) 39 | naught (1.1.0) 40 | oauth (0.5.4) 41 | parallel (1.17.0) 42 | public_suffix (3.0.3) 43 | rainbow (3.0.0) 44 | rake (12.3.0) 45 | simple_oauth (0.3.1) 46 | thread_safe (0.3.6) 47 | twitter (6.2.0) 48 | addressable (~> 2.3) 49 | buftok (~> 0.2.0) 50 | equalizer (~> 0.0.11) 51 | http (~> 3.0) 52 | http-form_data (~> 2.0) 53 | http_parser.rb (~> 0.6.0) 54 | memoizable (~> 0.4.0) 55 | multipart-post (~> 2.0) 56 | naught (~> 1.0) 57 | simple_oauth (~> 0.3.0) 58 | unf (0.1.4) 59 | unf_ext 60 | unf_ext (0.0.7.5) 61 | 62 | PLATFORMS 63 | ruby 64 | 65 | DEPENDENCIES 66 | bundler (~> 1.15) 67 | minitest 68 | rake 69 | tw! 70 | 71 | BUNDLED WITH 72 | 1.16.1 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tw 2 | == 3 | 4 | * CUI Twitter Client. 5 | * http://shokai.github.io/tw 6 | * https://rubygems.org/gems/tw 7 | 8 | 9 | Install 10 | ------- 11 | 12 | % gem install tw 13 | 14 | for Ruby 2.0 ~ 2.3 users 15 | 16 | % gem install tw --version=1.0.12 17 | 18 | 19 | Synopsis 20 | -------- 21 | 22 | show help 23 | 24 | % tw --help 25 | 26 | Tweet 27 | 28 | % tw hello hello 29 | % echo "hello" | tw --pipe 30 | % tw 'yummy!!' --file=food.jpg 31 | 32 | read Timeline 33 | 34 | % tw @username 35 | % tw @username @user2 @user3 36 | % tw @username/listname 37 | % tw --timeline 38 | % tw --search=ruby 39 | % tw --stream 40 | % tw --stream:filter=ruby,java --silent 41 | 42 | DM 43 | 44 | % tw --dm 45 | % tw --dm:to=shokai "hello!" 46 | 47 | Reply/Fav/RT 48 | 49 | % tw @shokai --id 50 | % tw "@shokai wow!!" --id=334749349588377601 51 | % tw --id=334749349588377601 52 | % tw --fav=334749349588377601 53 | % tw --rt=334749349588377601 54 | 55 | Delete 56 | 57 | % tw --del=334749349588377601 58 | 59 | Switch Accounts 60 | 61 | % tw --user:add 62 | open http://twitter.com/oauth/authorize?oauth_token=a1b2cDEF3456gh78 63 | input PIN Number: 19283746 64 | add "@user2" 65 | % tw --user:list 66 | * shokai 67 | user2 68 | user3 69 | (3 users) 70 | % tw hello --user=user2 71 | % tw --user:default=user2 72 | set default user "@user2" 73 | 74 | Format 75 | 76 | % tw --format=json 77 | % tw --stream --format="@#{user} #{text} - #{url}" 78 | 79 | 80 | Make Twitter Bot 81 | ---------------- 82 | stream reply bot 83 | 84 | % tw --silent --stream:filter=BOT_NAME --user=BOT_NAME --format="@#{user} OK" | tw --pipe --user=BOT_NAME 85 | 86 | 87 | Test 88 | ---- 89 | 90 | % gem install bundler 91 | % bundle install 92 | % bundle exec rake test 93 | 94 | 95 | Contributing 96 | ------------ 97 | 1. Fork it 98 | 2. Create your feature branch (`git checkout -b my-new-feature`) 99 | 3. Commit your changes (`git commit -am 'Add some feature'`) 100 | 4. Push to the branch (`git push origin my-new-feature`) 101 | 5. Create new Pull Request -------------------------------------------------------------------------------- /lib/tw/client/auth.rb: -------------------------------------------------------------------------------- 1 | module Tw 2 | class Client 3 | def self.client 4 | @@client 5 | end 6 | 7 | def self.client=(client) 8 | @@client = client 9 | end 10 | 11 | def auth(user=nil) 12 | self.class.client = @rest_client = Auth.auth(user) 13 | end 14 | end 15 | 16 | class Auth 17 | 18 | def self.auth(user=nil) 19 | user = get_or_regist_user user 20 | return Twitter::REST::Client.new do |c| 21 | c.consumer_key = Conf['consumer_key'] 22 | c.consumer_secret = Conf['consumer_secret'] 23 | c.access_token = user['access_token'] 24 | c.access_token_secret = user['access_secret'] 25 | end 26 | end 27 | 28 | def self.get_or_regist_user(user) 29 | return user if user.kind_of? Hash 30 | if user.kind_of? String 31 | raise ArgumentError, "user \"#{user}\" not exists." unless Conf['users'].include? user 32 | return Conf['users'][user] 33 | end 34 | unless user 35 | return Conf['users'][ Conf['default_user'] ] if Conf['default_user'] 36 | return regist_user 37 | end 38 | end 39 | 40 | def self.regist_user 41 | consumer = OAuth::Consumer.new(Conf['consumer_key'], Conf['consumer_secret'], 42 | :site => 'https://api.twitter.com') 43 | request_token = consumer.get_request_token 44 | puts "open #{request_token.authorize_url}" 45 | begin 46 | Launchy.open request_token.authorize_url 47 | rescue 48 | STDERR.puts "couldn't open web-browser" 49 | end 50 | print 'input PIN Number: ' 51 | verifier = STDIN.gets.strip 52 | access_token = request_token.get_access_token(:oauth_verifier => verifier) 53 | client = Tw::Client.new.auth('access_token' => access_token.token, 54 | 'access_secret' => access_token.secret) 55 | u = client.user 56 | Conf['users'][u.screen_name] = { 57 | 'access_token' => access_token.token, 58 | 'access_secret' => access_token.secret, 59 | 'id' => u.id 60 | } 61 | Conf['default_user'] = u.screen_name unless Conf['default_user'] 62 | Conf.save 63 | puts "add \"@#{u.screen_name}\"" 64 | return Conf['users'][u.screen_name] 65 | end 66 | 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/tw/conf.rb: -------------------------------------------------------------------------------- 1 | 2 | module Tw 3 | class Conf 4 | 5 | def self.default 6 | { 7 | 'version' => Tw::VERSION, 8 | 'consumer_key' => 'AYhhkOC8H2yTZyelz3uw', 9 | 'consumer_secret' => '28Ba8YyFDSPgoCYAmH5ANqOmT6qVS8gIhKnUiDbIpU', 10 | 'default_user' => nil, 11 | 'users' => {} 12 | } 13 | end 14 | 15 | def self.[](key) 16 | ENV[key] || conf[key] 17 | end 18 | 19 | def self.[]=(key,value) 20 | conf[key] = value 21 | end 22 | 23 | def self.conf_file 24 | @@conf_file ||= "#{ENV['HOME']}/.tw.yml" 25 | end 26 | 27 | def self.conf_file=(fpath) 28 | @@conf_file = fpath 29 | end 30 | 31 | def self.conf 32 | @@conf ||= ( 33 | res = default 34 | if File.exist? self.conf_file 35 | begin 36 | data = nil 37 | self.open_conf_file do |f| 38 | data = YAML::load f.read 39 | end 40 | if data['version'] < REQUIRE_VERSION 41 | puts "This is tw version #{Tw::VERSION}." 42 | puts "Your config file is old ("+data['version']+"). Reset tw settings?" 43 | puts "[Y/n]" 44 | res = data if STDIN.gets =~ /^n/i 45 | else 46 | res = data 47 | end 48 | rescue => e 49 | STDERR.puts e 50 | File.delete self.conf_file 51 | end 52 | end 53 | res 54 | ) 55 | end 56 | 57 | def self.to_yaml 58 | self.conf.to_yaml 59 | end 60 | 61 | def self.save 62 | open_conf_file('w+') do |f| 63 | f.write conf.to_yaml 64 | end 65 | end 66 | 67 | def self.update_twitter_config(force_update=false) 68 | if self['twitter_config'].kind_of? Hash and 69 | self['twitter_config']['last_updated_at']+60*60*24 > Time.now.to_i and 70 | !force_update 71 | return 72 | end 73 | self['twitter_config'] = {} 74 | self['twitter_config']['short_url_length'] = Tw::Client.client.configuration.short_url_length 75 | self['twitter_config']['short_url_length_https'] = Tw::Client.client.configuration.short_url_length_https 76 | self['twitter_config']['last_updated_at'] = Time.now.to_i 77 | self.save 78 | end 79 | 80 | private 81 | def self.open_conf_file(opt=nil, &block) 82 | if block_given? 83 | yield open(self.conf_file, opt) 84 | else 85 | return open(self.conf_file, opt) 86 | end 87 | File.chmod 0600, self.conf_file if File.exist? self.conf_file 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/tw/client/request.rb: -------------------------------------------------------------------------------- 1 | module Tw 2 | class Client 3 | 4 | def mentions 5 | @rest_client.mentions.map{|m| 6 | Tw::Tweet.new(:id => m.id, 7 | :user => m.user.screen_name, 8 | :text => m.text, 9 | :time => m.created_at, 10 | :fav_count => m.favorite_count, 11 | :rt_count => m.retweet_count) 12 | } 13 | end 14 | 15 | def search(word) 16 | @rest_client.search(word).take(20).map{|m| 17 | Tw::Tweet.new(:id => m.id, 18 | :user => m.user.screen_name, 19 | :text => m.text, 20 | :time => m.created_at, 21 | :fav_count => m.favorite_count, 22 | :rt_count => m.retweet_count) 23 | } 24 | end 25 | 26 | def home_timeline 27 | @rest_client.home_timeline.map{|m| 28 | Tw::Tweet.new(:id => m.id, 29 | :user => m.user.screen_name, 30 | :text => m.text, 31 | :time => m.created_at, 32 | :fav_count => m.favorite_count, 33 | :rt_count => m.retweet_count) 34 | } 35 | end 36 | 37 | def user_timeline(user) 38 | @rest_client.user_timeline(user).map{|m| 39 | Tw::Tweet.new(:id => m.id, 40 | :user => m.user.screen_name, 41 | :text => m.text, 42 | :time => m.created_at, 43 | :fav_count => m.favorite_count, 44 | :rt_count => m.retweet_count) 45 | } 46 | end 47 | 48 | def list_timeline(user,list) 49 | @rest_client.list_timeline(user, list).map{|m| 50 | Tw::Tweet.new(:id => m.id, 51 | :user => m.user.screen_name, 52 | :text => m.text, 53 | :time => m.created_at, 54 | :fav_count => m.favorite_count, 55 | :rt_count => m.retweet_count) 56 | } 57 | end 58 | 59 | def direct_messages 60 | [@rest_client.direct_messages.map{|m| 61 | Tw::Tweet.new(:id => m.id, 62 | :user => m.sender.screen_name, 63 | :text => m.text, 64 | :time => m.created_at) 65 | }, @rest_client.direct_messages_sent.map{|m| 66 | Tw::Tweet.new(:id => m.id, 67 | :user => { 68 | :from => m.sender.screen_name, 69 | :to => m.recipient.screen_name 70 | }, 71 | :text => m.text, 72 | :time => m.created_at) 73 | }].flatten 74 | end 75 | 76 | def tweet(message, opts={}) 77 | res = @rest_client.update message, opts 78 | puts res.text 79 | puts "https://twitter.com/#{res.user.screen_name}/status/#{res.id}" 80 | puts res.created_at 81 | end 82 | 83 | def tweet_with_file(message, file, opts={}) 84 | res = @rest_client.update_with_media message, file, opts 85 | puts res.text 86 | puts "https://twitter.com/#{res.user.screen_name}/status/#{res.id}" 87 | puts res.created_at 88 | end 89 | 90 | def direct_message_create(to, message) 91 | res = @rest_client.create_direct_message to, message 92 | puts res.text 93 | puts res.created_at 94 | end 95 | 96 | def show_status(status_id) 97 | res = @rest_client.status(status_id) 98 | line = CGI.unescapeHTML res.text 99 | line += " #{res.favorite_count}Fav" if res.favorite_count.to_i > 0 100 | line += " #{res.retweet_count}RT" if res.retweet_count.to_i > 0 101 | puts line.colorize(/@[a-zA-Z0-9_]+|\d+RT|\d+Fav/) 102 | puts "https://twitter.com/#{res.user.screen_name}/status/#{res.id}" 103 | puts res.created_at 104 | end 105 | 106 | def favorite(status_id) 107 | @rest_client.favorite status_id 108 | end 109 | 110 | def retweet(status_id) 111 | @rest_client.retweet status_id 112 | end 113 | 114 | def destroy_status(status_id) 115 | @rest_client.destroy_status status_id 116 | end 117 | 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 1.3.1 2018-04-15 2 | 3 | * use https (#79) 4 | * thank you for contributing @sachin21 5 | 6 | === 1.3.0 2018-02-18 7 | 8 | * 280 chars tweet (#75) 9 | 10 | === 1.2.1 2017-12-10 11 | 12 | * Fix deprecated warnings (#77) 13 | * update gems, use twitter v6.2.0, remove yajl-ruby from dependencies (#76) 14 | 15 | === 1.2.0 2017-08-20 16 | 17 | * fix --stream option (#73) 18 | 19 | === 1.1.0 2017-07-02 20 | 21 | * update rubygems - parallel, rainbow, launchy #72 22 | * fix gemspec for ruby 2.4.x #71 23 | 24 | === 1.0.12 2016-11-30 25 | 26 | * use https on tweet URL 27 | 28 | === 1.0.11 2015-10-19 29 | 30 | * bugfix search #65 31 | 32 | === 1.0.10 2015-10-18 33 | 34 | * support a timezone #66 35 | * thank you for contributing @kaihar4 36 | 37 | === 1.0.9 2014-05-14 38 | 39 | * merge multiple lines from STDIN into one tweet 40 | 41 | === 1.0.8 2014-01-28 42 | 43 | * fix for rainbow gem v2.0.x 44 | * fix for twitter gem v5.6.x 45 | 46 | === 1.0.7 2014-01-25 47 | 48 | * Add --delete option 49 | * thank you for contributing @janusadm 50 | 51 | === 1.0.6 2014-01-17 52 | 53 | * rescue Launchy::CommandNotFoundError #56 54 | 55 | === 1.0.5 2014-01-16 56 | 57 | * use HTTPS for OAuth::Consumer #55 58 | * thank you for contributing @ymrl 59 | 60 | === 1.0.4 2014-01-08 61 | 62 | * update gem dependencies 63 | 64 | === 1.0.3 2014-01-08 65 | 66 | * bugfix for rainbow 1.99.x #54 67 | * use launchy gem to open OAuth-URL #53 68 | 69 | === 1.0.2 2013-11-27 70 | 71 | * fix syntax error on ruby1.8.7 72 | 73 | === 1.0.1 2013-11-23 74 | 75 | * bugfix -user:add #51 76 | 77 | === 1.0.0 2013-11-21 78 | 79 | * fix for twitter gem v5.0.0 #50 80 | 81 | === 0.5.2 2013-09-19 82 | 83 | * do not show dialogue with "--yes" option 84 | 85 | === 0.5.1 2013-09-18 86 | 87 | * tweet with file % tw 'yummy!!' --file=food.jpg 88 | 89 | === 0.5.0 2013-08-26 90 | 91 | * show RT/Fav count on % tw --id=1234552234 92 | 93 | === 0.4.9 2013-08-22 94 | 95 | * show tweet by status_id % tw --id=1234543234 96 | 97 | === 0.4.8 2013-08-10 98 | 99 | * show Fav/RT count 100 | 101 | === 0.4.7 2013-08-09 102 | 103 | * modify DM send dialog 104 | * declare license in gemspec 105 | 106 | === 0.4.6 2013-07-28 107 | 108 | * unescape HTML in Tw::Client#show_status 109 | 110 | === 0.4.5 2013-07-27 111 | 112 | * fix listname regex, could contain '-' 113 | * thank you for contributing @takano32 114 | 115 | === 0.4.4 2013-07-14 116 | 117 | * silent if --format option 118 | 119 | === 0.4.3 2013-07-02 120 | 121 | * notify switch user 122 | 123 | === 0.4.2 2013-05-18 124 | 125 | * enable -id=(id) option in pipe-tweet 126 | 127 | === 0.4.1 2013-05-18 128 | 129 | * replty to status % tw "@shokai hello!" --id=1123454321 130 | 131 | === 0.4.0 2013-05-16 132 | 133 | * status_id % tw --id 134 | * RT tweet % tw --rt=12345136 135 | * Fav tweet % tw --fav=12345136 136 | 137 | === 0.3.9 2013-03-05 138 | 139 | * use http://api.twitter.com to auth 140 | * thank you for contributing @tdksk 141 | 142 | === 0.3.8 2013-01-04 143 | 144 | * use bundler gem template 145 | 146 | === 0.3.7 2012-10-19 147 | 148 | * use specify config file : --conf=FILEPATH 149 | 150 | === 0.3.6 2012-10-18 151 | 152 | * remove twitter-text gem 153 | 154 | === 0.3.5 2012-10-18 155 | 156 | * bugfix 157 | 158 | === 0.3.4 2012-10-18 159 | 160 | * add Tw::Client::Stream sample 161 | 162 | === 0.3.3 2012-10-18 163 | 164 | * count 140 chars with t.co 165 | 166 | === 0.3.2 2012-10-11 167 | 168 | * set config file permission 600 169 | 170 | === 0.3.1 2012-10-11 171 | 172 | * pipe DM 173 | 174 | === 0.3.0 2012-10-10 175 | 176 | * silent mode option : --silent 177 | * pipe tweet each line from STDIN 178 | * add Tw API sample 179 | * update README 180 | 181 | === 0.2.6 2012-10-10 182 | 183 | * bugfix merging users and lists 184 | 185 | === 0.2.5 2012-10-10 186 | 187 | * JSON print format : --format=json 188 | * custom print format : --format="#{user} #{text} #{id} #{time}" 189 | 190 | === 0.2.4 2012-10-06 191 | 192 | * require json 193 | 194 | === 0.2.3 2012-10-06 195 | 196 | * parallel request twitter API 197 | 198 | === 0.2.2 2012-10-06 199 | 200 | * unescape HTML 201 | 202 | === 0.2.1 2012-10-05 203 | 204 | * auto re-connect stream 205 | 206 | === 0.2.0 2012-10-04 207 | 208 | * user stream : --stream 209 | * filter stream : --stream:filter=WORD1,WORD2 210 | 211 | === 0.1.1 2012-10-04 212 | 213 | * command option detail on help 214 | 215 | === 0.1.0 2012-10-04 216 | 217 | * show direct messages : --dm 218 | * create direct message : --dm:to=USERNAME 219 | 220 | === 0.0.3 2012-10-03 221 | 222 | * show home timeline : --timeline 223 | 224 | === 0.0.2 2012-10-03 225 | 226 | * support multi user accounts : --user=NAME 227 | * tweet from pipe : --pipe 228 | * option : --search=WORD 229 | * show version : --version 230 | 231 | === 0.0.1 2012-09-15 232 | 233 | * first release 234 | -------------------------------------------------------------------------------- /lib/tw/app/main.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path 'opt_parser', File.dirname(__FILE__) 2 | require File.expand_path 'cmds', File.dirname(__FILE__) 3 | require File.expand_path 'render', File.dirname(__FILE__) 4 | require File.expand_path 'helper', File.dirname(__FILE__) 5 | 6 | module Tw::App 7 | 8 | def self.new 9 | Main.new 10 | end 11 | 12 | class Main 13 | def initialize 14 | on_exit do 15 | exit 0 16 | end 17 | 18 | on_error do 19 | exit 1 20 | end 21 | end 22 | 23 | def client 24 | @client ||= Tw::Client.new 25 | end 26 | 27 | def on_exit(&block) 28 | if block_given? 29 | @on_exit = block 30 | else 31 | @on_exit.call if @on_exit 32 | end 33 | end 34 | 35 | def on_error(&block) 36 | if block_given? 37 | @on_error = block 38 | else 39 | @on_error.call if @on_error 40 | end 41 | end 42 | 43 | def run(argv) 44 | @args = ArgsParser.parse argv, :style => :equal do 45 | arg :user, 'user account', :alias => :u 46 | arg 'user:add', 'add user' 47 | arg 'user:list', 'show user list' 48 | arg 'user:default', 'set default user' 49 | arg :timeline, 'show timeline', :alias => :tl 50 | arg :dm, 'show direct messages' 51 | arg 'dm:to', 'create direct message' 52 | arg :favorite, 'favorite tweet', :alias => :fav 53 | arg :retweet, 'retweet', :alias => :rt 54 | arg :delete, 'delete tweet', :alias => :del 55 | arg :search, 'search public timeline', :alias => :s 56 | arg :stream, 'show user stream', :alias => :st 57 | arg :status_id, 'show status_id', :alias => :id 58 | arg :file, 'upload file' 59 | arg :pipe, 'pipe tweet' 60 | arg :format, 'output format', :default => 'text' 61 | arg :silent, 'silent mode' 62 | arg :yes, 'do not show dialogue' 63 | arg :conf, 'config file', :default => Tw::Conf.conf_file 64 | arg :version, 'show version', :alias => :v 65 | arg :help, 'show help', :alias => :h 66 | 67 | validate :user, 'invalid user name' do |v| 68 | v =~ /^[a-zA-Z0-9_]+$/ 69 | end 70 | 71 | validate 'user:default', 'invalid user name' do |v| 72 | v =~ /^[a-zA-Z0-9_]+$/ 73 | end 74 | 75 | validate 'dm:to', 'invalid user name' do |v| 76 | v =~ /^[a-zA-Z0-9_]+$/ 77 | end 78 | 79 | validate :file, "file does not exists" do |v| 80 | File.exists? v 81 | end 82 | end 83 | 84 | if @args.has_option? :help 85 | STDERR.puts "Tw - Twitter client on Ruby v#{Tw::VERSION}" 86 | STDERR.puts " http://shokai.github.io/tw" 87 | STDERR.puts 88 | STDERR.puts @args.help 89 | STDERR.puts 90 | STDERR.puts "e.g." 91 | STDERR.puts "tweet tw hello world" 92 | STDERR.puts " echo 'hello' | tw --pipe" 93 | STDERR.puts " tw 'yummy!!' --file=food.jpg --yes" 94 | STDERR.puts "read tw @username" 95 | STDERR.puts " tw @username @user2 @user2/listname" 96 | STDERR.puts " tw --search=ruby" 97 | STDERR.puts " tw --stream" 98 | STDERR.puts " tw --stream:filter=ruby,java" 99 | STDERR.puts " tw --dm:to=username \"hello!\"" 100 | STDERR.puts "id tw @shokai --id" 101 | STDERR.puts " tw --id=334749349588377601" 102 | STDERR.puts 'reply tw "@shokai wow!!" --id=334749349588377601' 103 | STDERR.puts " tw --fav=334749349588377601" 104 | STDERR.puts " tw --rt=334749349588377601" 105 | STDERR.puts " tw --format=json" 106 | STDERR.puts ' tw --format="@#{user} #{text} - https://twitter.com/#{user}/#{id}"' 107 | STDERR.puts "delete tw --del=334749349588377601" 108 | on_exit 109 | end 110 | 111 | Render.silent = (@args.has_option? :silent or @args.has_param? :format) 112 | Render.show_status_id = @args[:status_id] 113 | Tw::Conf.conf_file = @args[:conf] 114 | 115 | regist_cmds 116 | 117 | cmds.each do |name, cmd| 118 | next unless @args[name] 119 | cmd.call @args[name], @args 120 | end 121 | 122 | auth 123 | if @args.argv.size < 1 124 | if @args.has_param? :status_id 125 | client.show_status @args[:status_id] 126 | else 127 | Render.display client.mentions, @args[:format] 128 | end 129 | elsif all_requests?(@args.argv) 130 | Render.display Parallel.map(@args.argv, :in_threads => @args.argv.size){|arg| 131 | if user = username?(arg) 132 | res = client.user_timeline user 133 | elsif (user, list = listname?(arg)) != false 134 | res = client.list_timeline(user, list) 135 | end 136 | res 137 | }, @args[:format] 138 | else 139 | message = @args.argv.join(' ') 140 | tweet_opts = {} 141 | if (len = message.char_length_with_t_co) > 280 142 | STDERR.puts "tweet too long (#{len} chars)" 143 | on_error 144 | else 145 | if @args.has_param? :status_id 146 | client.show_status @args[:status_id] 147 | puts "--" 148 | puts "reply \"#{message}\"? (#{len} chars)" 149 | tweet_opts[:in_reply_to_status_id] = @args[:status_id] 150 | else 151 | puts "tweet \"#{message}\"? (#{len} chars)" 152 | if @args.has_param? :file 153 | puts "upload \"#{@args[:file]}\"? (#{File.size @args[:file]} bytes)" 154 | end 155 | end 156 | unless @args.has_option? :yes 157 | puts '[Y/n]' 158 | on_exit if STDIN.gets.strip =~ /^n/i 159 | end 160 | end 161 | begin 162 | if @args.has_param? :file 163 | client.tweet_with_file message, File.open(@args[:file]), tweet_opts 164 | else 165 | client.tweet message, tweet_opts 166 | end 167 | rescue => e 168 | STDERR.puts e.message 169 | end 170 | end 171 | end 172 | 173 | private 174 | def auth 175 | return unless @args 176 | client.auth @args.has_param?(:user) ? @args[:user] : nil 177 | end 178 | 179 | end 180 | end 181 | -------------------------------------------------------------------------------- /lib/tw/app/cmds.rb: -------------------------------------------------------------------------------- 1 | module Tw::App 2 | class Main 3 | 4 | private 5 | def regist_cmds 6 | cmd :user do |v, opts| 7 | if v == true 8 | STDERR.puts 'e.g. tw "hello" --user=USERNAME' 9 | on_error 10 | else 11 | Render.puts "switch user -> @#{v}" 12 | end 13 | end 14 | 15 | cmd 'user:add' do |v, opts| 16 | Tw::Auth.regist_user 17 | on_exit 18 | end 19 | 20 | cmd 'user:list' do |v, opts| 21 | Tw::Conf['users'].keys.each do |name| 22 | puts name == Tw::Conf['default_user'] ? "* #{name}" : " #{name}" 23 | end 24 | puts "(#{Tw::Conf['users'].size} users)" 25 | on_exit 26 | end 27 | 28 | cmd 'user:default' do |v, opts| 29 | if v.class == String 30 | Tw::Conf['default_user'] = v 31 | Tw::Conf.save 32 | puts "set default user \"@#{Tw::Conf['default_user']}\"" 33 | else 34 | puts "@"+Tw::Conf['default_user'] if Tw::Conf['default_user'] 35 | STDERR.puts "e.g. tw --user:default=USERNAME" 36 | end 37 | on_exit 38 | end 39 | 40 | cmd :timeline do |v, opts| 41 | unless v.class == String 42 | auth 43 | Render.display client.home_timeline, opts[:format] 44 | on_exit 45 | end 46 | end 47 | 48 | cmd :dm do |v, opts| 49 | auth 50 | Render.display client.direct_messages, opts[:format] 51 | on_exit 52 | end 53 | 54 | cmd 'dm:to' do |to, opts| 55 | unless opts[:pipe] 56 | message = opts.argv.join(' ') 57 | len = message.char_length_with_t_co 58 | if len > 140 59 | STDERR.puts "message too long (#{len} chars)" 60 | on_error 61 | elsif len < 1 62 | STDERR.puts 'e.g. tw --dm:to=USERNAME "hello"' 63 | on_error 64 | else 65 | puts "DM to @#{to}" 66 | puts "\"#{message}\"? (#{len} chars)" 67 | unless opts.has_option? :yes 68 | puts '[Y/n]' 69 | on_exit if STDIN.gets.strip =~ /^n/i 70 | end 71 | auth 72 | client.direct_message_create to, message 73 | end 74 | on_exit 75 | end 76 | end 77 | 78 | cmd :favorite do |v, opts| 79 | if opts.has_param? :favorite 80 | id = opts[:favorite] 81 | auth 82 | client.show_status id 83 | puts 'Fav this?' 84 | unless opts.has_option? :yes 85 | puts '[Y/n]' 86 | on_exit if STDIN.gets.strip =~ /^n/i 87 | end 88 | puts "success!" if client.favorite id 89 | end 90 | on_exit 91 | end 92 | 93 | cmd :retweet do |v, opts| 94 | if opts.has_param? :retweet 95 | id = opts[:retweet] 96 | auth 97 | client.show_status id 98 | puts 'RT this?' 99 | unless opts.has_option? :yes 100 | puts '[Y/n]' 101 | on_exit if STDIN.gets.strip =~ /^n/i 102 | end 103 | puts "success!" if client.retweet id 104 | end 105 | on_exit 106 | end 107 | 108 | cmd :delete do |v, opts| 109 | if opts.has_param? :delete 110 | id = opts[:delete] 111 | auth 112 | client.show_status id 113 | puts 'Delete this?' 114 | unless opts.has_option? :yes 115 | puts '[Y/n]' 116 | on_exit if STDIN.gets.strip =~ /^n/i 117 | end 118 | puts "success!" if client.destroy_status id 119 | end 120 | on_exit 121 | end 122 | 123 | cmd :pipe do |v, opts| 124 | auth 125 | lines = STDIN.readlines.join 126 | lines.split(/(.{140})/u).select{|m|m.size>0}.each do |message| 127 | begin 128 | if opts.has_param? 'dm:to' 129 | puts to = opts['dm:to'] 130 | client.direct_message_create to, message 131 | else 132 | tweet_opts = {} 133 | tweet_opts[:in_reply_to_status_id] = opts[:status_id] if opts.has_param? :status_id 134 | client.tweet message, tweet_opts 135 | end 136 | rescue => e 137 | STDERR.puts e.message 138 | end 139 | end 140 | on_exit 141 | end 142 | 143 | cmd :search do |v, opts| 144 | if v.class == String 145 | auth 146 | Render.display client.search(v), opts[:format] 147 | on_exit 148 | else 149 | STDERR.puts "e.g. tw --search=ruby" 150 | on_error 151 | end 152 | end 153 | 154 | cmd :stream do |v, opts| 155 | stream = Tw::Client::Stream.new opts.has_param?(:user) ? opts[:user] : nil 156 | Render.puts "-- waiting stream.." 157 | loop do 158 | begin 159 | stream.user_stream do |s| 160 | Render.display s, opts[:format] 161 | end 162 | rescue Timeout::Error, SocketError => e 163 | sleep 5 164 | next 165 | rescue => e 166 | STDERR.puts e 167 | on_error 168 | end 169 | end 170 | on_exit 171 | end 172 | 173 | cmd 'stream:filter' do |v, opts| 174 | unless v.class == String 175 | STDERR.puts "e.g. tw --stream:filter=ruby,java" 176 | on_error 177 | else 178 | track_words = v.split(/\s*,\s*/) 179 | stream = Tw::Client::Stream.new opts.has_param?(:user) ? opts[:user] : nil 180 | Render.puts "-- waiting stream.. track \"#{track_words.join(',')}\"" 181 | loop do 182 | begin 183 | stream.filter track_words do |s| 184 | Render.display s, opts[:format] 185 | end 186 | rescue Timeout::Error, SocketError => e 187 | sleep 5 188 | next 189 | rescue => e 190 | STDERR.puts e 191 | on_error 192 | end 193 | end 194 | on_exit 195 | end 196 | end 197 | 198 | cmd :version do |v, opts| 199 | puts "tw version #{Tw::VERSION}" 200 | on_exit 201 | end 202 | end 203 | 204 | def cmd(name, &block) 205 | if block_given? 206 | cmds[name.to_sym] = block 207 | else 208 | return cmds[name.to_sym] 209 | end 210 | end 211 | 212 | def cmds 213 | @cmds ||= Hash.new 214 | end 215 | 216 | end 217 | end 218 | --------------------------------------------------------------------------------