tuomas-tweetwine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,4 @@
1
+ === 0.1.0 released 2009-04-22
2
+
3
+ * Initial release with minimal functionality.
4
+ * Usable for quickly checking friends' statuses and sending status updates.
data/README.rdoc ADDED
@@ -0,0 +1,70 @@
1
+ = Tweetwine
2
+
3
+ A simple but tasty Twitter agent for command line use, made for fun.
4
+
5
+ == Installation
6
+
7
+ Install Tweetwine as a RubyGem from GitHub:
8
+
9
+ $ sudo gem install tuomas-tweetwine --source http://gems.github.com
10
+
11
+ The program is compatible with Ruby 1.9.1.
12
+
13
+ == Usage
14
+
15
+ In the command line, run the program by entering
16
+
17
+ $ tweetwine [-a username:password] [command]
18
+
19
+ The program needs the user's username and password for authentication. This
20
+ information can be supplied either as an option to the program or via a
21
+ configuration file. Without option <tt>[-a]</tt>, the program attempts to
22
+ read the configuration file for authentication.
23
+
24
+ The supported commands are
25
+
26
+ <tt>friends</tt>:: Fetch friends' statuses (the contents of the user's home).
27
+ <tt>user [name]</tt>:: Fetch a specific user's statuses, identified by the argument; if given no argument, fetch your own statuses.
28
+ <tt>msg [status]</tt>:: Send a status update, but confirm the action first before actually sending. The status update can either be given as an argument or via STDIN if no argument is given.
29
+
30
+ If <tt>[command]</tt> is not given, it defaults to <tt>friends</tt>.
31
+
32
+ The configuration file, in <tt>~/.tweetwine</tt>, is in YAML syntax. It can
33
+ contain the following settings:
34
+
35
+ username: <your_username>
36
+ password: <your_password>
37
+ colorize: true|false
38
+
39
+ For all the options, see:
40
+
41
+ $ tweetwine -h
42
+
43
+ == Contacting
44
+
45
+ Please send feedback by email to Tuomas Kareinen < tkareine (at) gmail (dot)
46
+ com >.
47
+
48
+ == Legal notes
49
+
50
+ This software is licensed under the terms of the "MIT license":
51
+
52
+ Copyright (c) 2009 Tuomas Kareinen.
53
+
54
+ Permission is hereby granted, free of charge, to any person obtaining a copy
55
+ of this software and associated documentation files (the "Software"), to
56
+ deal in the Software without restriction, including without limitation the
57
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
58
+ sell copies of the Software, and to permit persons to whom the Software is
59
+ furnished to do so, subject to the following conditions:
60
+
61
+ The above copyright notice and this permission notice shall be included in
62
+ all copies or substantial portions of the Software.
63
+
64
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
65
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
66
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
67
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
68
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
69
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
70
+ IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require "rubygems"
2
+
3
+ full_name = "Tweetwine"
4
+ package_name = "tweetwine"
5
+ version = "0.1.0"
6
+
7
+ require "lib/#{package_name}"
8
+
9
+ require "rake/clean"
10
+
11
+ require "rake/gempackagetask"
12
+ spec = Gem::Specification.new do |s|
13
+ s.name = package_name
14
+ s.version = version
15
+ s.homepage = "http://github.com/tuomas/tweetwine"
16
+ s.summary = "A simple Twitter agent for command line use"
17
+ s.description = "A simple but tasty Twitter agent for command line use, made for fun."
18
+
19
+ s.author = "Tuomas Kareinen"
20
+ s.email = "tkareine@gmail.com"
21
+
22
+ s.platform = Gem::Platform::RUBY
23
+ s.files = FileList["Rakefile", "*.rdoc", "bin/**/*", "lib/**/*", "spec/**/*"].to_a
24
+ s.executables = ["tweetwine"]
25
+
26
+ s.add_dependency("json", ">= 1.1.4")
27
+ s.add_dependency("rest-client", ">= 0.9.2")
28
+
29
+ s.has_rdoc = true
30
+ s.extra_rdoc_files = FileList["*.rdoc"].to_a
31
+ s.rdoc_options << "--title" << "#{full_name} #{version}" \
32
+ << "--main" << "README.rdoc" \
33
+ << "--exclude" << "spec" \
34
+ << "--line-numbers"
35
+ end
36
+
37
+ Rake::GemPackageTask.new(spec) do |pkg|
38
+ pkg.need_zip = false
39
+ pkg.need_tar = true
40
+ end
41
+
42
+ desc "Generate a gemspec file"
43
+ task :gemspec do
44
+ File.open("#{spec.name}.gemspec", "w") do |f|
45
+ f.write spec.to_ruby
46
+ end
47
+ end
48
+
49
+ task :install => [:package] do
50
+ sh %{sudo gem install pkg/#{package_name}-#{version}.gem}
51
+ end
52
+
53
+ task :uninstall => [:clean] do
54
+ sh %{sudo gem uninstall #{package_name}}
55
+ end
56
+
57
+ require "rake/rdoctask"
58
+ desc "Create documentation"
59
+ Rake::RDocTask.new(:rdoc) do |rd|
60
+ rd.rdoc_dir = "rdoc"
61
+ rd.title = "#{full_name} #{version}"
62
+ rd.main = "README.rdoc"
63
+ rd.rdoc_files.include("*.rdoc", "lib/**/*.rb")
64
+ rd.options << "--line-numbers"
65
+ end
66
+
67
+ require "rake/testtask"
68
+ desc "Run tests"
69
+ Rake::TestTask.new do |t|
70
+ t.test_files = FileList["test/*_test.rb"]
71
+ t.verbose = true
72
+ t.warning = true
73
+ end
74
+
75
+ desc "Find code smells"
76
+ task :roodi do
77
+ sh %{roodi "**/*.rb"}
78
+ end
79
+
80
+ desc "Search unfinished parts of source code"
81
+ task :todo do
82
+ FileList["**/*.rb"].egrep /#.*(TODO|FIXME)/
83
+ end
84
+
85
+ task :default => :test
data/bin/tweetwine ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "getoptlong"
5
+ require File.dirname(__FILE__) << "/../lib/tweetwine"
6
+
7
+ include Tweetwine
8
+
9
+ class HelpNeeded < StandardError; end
10
+ class AlreadyReportedArgumentError < ArgumentError; end
11
+
12
+ DEFAULT_OPTIONS = {
13
+ :colorize => false,
14
+ :num_statuses => Client::MAX_NUM_STATUSES
15
+ }
16
+
17
+ def exit_with_error(why = nil)
18
+ puts "Error: #{why}" if why
19
+ exit(2)
20
+ end
21
+
22
+ def exit_with_usage(why = nil)
23
+ puts "#{why}\n\n" if why
24
+ puts <<-END
25
+ Usage: tweetwine [options...] [command]
26
+
27
+ The program needs the user's username and password for authentication.
28
+ This information can be given either as an option to the program or via a
29
+ configuration file. Without option [-a], the program attempts to read the
30
+ configuration file in YAML format at "~/.tweetwine" for authentication.
31
+
32
+ Argument [command] can be one of #{Client::COMMANDS.map {|cmd| "\"#{cmd}\"" }.join(", ")}.
33
+ If [command] is not given, it defaults to "#{Client::COMMANDS[0]}".
34
+
35
+ Options:
36
+
37
+ -a, --auth <username>:<password> Authentication data.
38
+ -c, --color Colorize output with ANSI escape codes.
39
+ -h, --help This help message.
40
+ -n, --num <num> The number of statuses to show, defaults
41
+ to #{DEFAULT_OPTIONS[:num_statuses]}.
42
+
43
+ END
44
+ exit(1)
45
+ end
46
+
47
+ def parse_args
48
+ options = DEFAULT_OPTIONS.dup
49
+
50
+ opts = GetoptLong.new(
51
+ [ "--auth", "-a", GetoptLong::REQUIRED_ARGUMENT ],
52
+ [ "--color", "-c", GetoptLong::NO_ARGUMENT ],
53
+ [ "--help", "-h", GetoptLong::NO_ARGUMENT ],
54
+ [ "--num", "-n", GetoptLong::REQUIRED_ARGUMENT ]
55
+ )
56
+
57
+ begin
58
+ opts.each do |opt, arg|
59
+ case opt
60
+ when "--auth" then options[:username], options[:password] = arg.split(":", 2)
61
+ when "--color" then options[:colorize] = true
62
+ when "--help" then raise HelpNeeded
63
+ when "--num"
64
+ arg = arg.to_i
65
+ if (1..Client::MAX_NUM_STATUSES).include? arg
66
+ options[:num_statuses] = arg
67
+ else
68
+ raise ArgumentError, "Invalid number of statuses to show -- must be between 1..#{Client::MAX_NUM_STATUSES}"
69
+ end
70
+ end
71
+ end
72
+ rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument
73
+ raise AlreadyReportedArgumentError
74
+ end
75
+
76
+ unless options[:username]
77
+ begin
78
+ config = Tweetwine::Config.load(ENV["HOME"] + "/.tweetwine")
79
+ options[:username], options[:password] = [config.username, config.password]
80
+ options[:colorize] = config.colorize if config.colorize?
81
+ rescue Exception => e
82
+ raise ArgumentError, "No auth info given as argument and no configuration file (~/.tweetwine) found."
83
+ end
84
+ end
85
+
86
+ command = if ARGV.empty? then Client::COMMANDS[0] else ARGV.shift end
87
+ raise ArgumentError, "Unknown command." unless Client::COMMANDS.include? command
88
+
89
+ [options, command, ARGV]
90
+ end
91
+
92
+ begin
93
+ options, command, args = parse_args
94
+ client = Client.new(options)
95
+ client.send(command.to_sym, *args)
96
+ rescue HelpNeeded, AlreadyReportedArgumentError
97
+ exit_with_usage
98
+ rescue ArgumentError => e
99
+ exit_with_usage e.message
100
+ rescue ClientError => e
101
+ exit_with_error e.message
102
+ end
@@ -0,0 +1,81 @@
1
+ require "json"
2
+ require "rest_client"
3
+
4
+ module Tweetwine
5
+ class ClientError < RuntimeError; end
6
+
7
+ class Client
8
+ COMMANDS = %w{friends user msg}
9
+
10
+ MAX_NUM_STATUSES = 20
11
+ MAX_STATUS_LENGTH = 140
12
+
13
+ def initialize(options)
14
+ @username, password = options[:username].to_s, options[:password].to_s
15
+ @base_url = "https://#{@username}:#{password}@twitter.com/"
16
+ @colorize = options[:colorize] || false
17
+ @num_statuses = options[:num_statuses] || MAX_NUM_STATUSES
18
+ end
19
+
20
+ def friends
21
+ print_statuses JSON.parse(get("statuses/friends_timeline.json?count=#{@num_statuses}"))
22
+ end
23
+
24
+ def user(user = @username)
25
+ print_statuses JSON.parse(get("statuses/user_timeline/#{user}.json?count=#{@num_statuses}"))
26
+ end
27
+
28
+ def msg(status = nil)
29
+ unless status
30
+ printf "New status: "
31
+ status = $stdin.gets
32
+ end
33
+ if confirm_user_action("Really send?")
34
+ msg = status[0...MAX_STATUS_LENGTH]
35
+ body = {:status => msg }
36
+ status = JSON.parse(post("statuses/update.json", body))
37
+ puts "Sent status update.\n\n"
38
+ print_statuses([status])
39
+ else
40
+ puts "Cancelled."
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def get(rest_url)
47
+ rest_client_action(:get, @base_url + rest_url)
48
+ end
49
+
50
+ def post(rest_url, body)
51
+ rest_client_action(:post, @base_url + rest_url, body)
52
+ end
53
+
54
+ def rest_client_action(action, *args)
55
+ RestClient.send(action, *args)
56
+ rescue RestClient::Exception => e
57
+ raise ClientError, e.message
58
+ end
59
+
60
+ def confirm_user_action(msg)
61
+ printf "#{msg} [yN] "
62
+ confirmation = $stdin.gets.strip
63
+ confirmation.downcase[0,1] == "y"
64
+ end
65
+
66
+ def print_statuses(statuses)
67
+ statuses.each do |status|
68
+ time_diff_value, time_diff_unit = Util.humanize_time_diff(Time.now, status["created_at"])
69
+ from_user = status["user"]["screen_name"]
70
+ from_user = Util.colorize(:green, from_user) if @colorize
71
+ text = status["text"]
72
+ text = Util.colorize(:red, text, /@\w+/) if @colorize
73
+ puts <<-END
74
+ #{from_user}, #{time_diff_value} #{time_diff_unit} ago:
75
+ #{text}
76
+
77
+ END
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,25 @@
1
+ require "yaml"
2
+
3
+ module Tweetwine
4
+ class Config
5
+ def self.load(file)
6
+ new(file)
7
+ end
8
+
9
+ private_class_method :new
10
+
11
+ def initialize(file)
12
+ @config = YAML.load(File.read(file))
13
+ end
14
+
15
+ def method_missing(sym)
16
+ key = sym.to_s
17
+ if key[-1,1] == "?"
18
+ result = @config.has_key? key[0...-1]
19
+ else
20
+ result = @config[key]
21
+ end
22
+ result
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ require "time"
2
+
3
+ module Tweetwine
4
+ module Util
5
+ COLOR_CODES = {
6
+ :green => "\033[32m",
7
+ :red => "\033[31m"
8
+ }
9
+
10
+ def self.colorize(color, str, matcher = nil)
11
+ color_code = COLOR_CODES[color.to_sym]
12
+
13
+ unless matcher
14
+ colorize_str(color_code, str)
15
+ else
16
+ str.gsub(matcher) { |s| colorize_str(color_code, s) }
17
+ end
18
+ end
19
+
20
+ def self.humanize_time_diff(from, to)
21
+ from = Time.parse(from.to_s) unless from.is_a? Time
22
+ to = Time.parse(to.to_s) unless to.is_a? Time
23
+
24
+ difference = (to - from).to_i.abs
25
+
26
+ value, unit = case difference
27
+ when 0..59 then [difference, "sec"]
28
+ when 60..3599 then [(difference/60.0).round, "min"]
29
+ when 3600..86399 then [(difference/3600.0).round, "hour"]
30
+ else [(difference/86400.0).round, "day"]
31
+ end
32
+
33
+ [value, pluralize_unit(value, unit)]
34
+ end
35
+
36
+ private
37
+
38
+ def self.colorize_str(color_code, str)
39
+ "#{color_code}#{str}\033[0m"
40
+ end
41
+
42
+ def self.pluralize_unit(value, unit)
43
+ if ["hour", "day"].include?(unit) && value > 1
44
+ unit = unit + "s"
45
+ end
46
+ unit
47
+ end
48
+ end
49
+ end
data/lib/tweetwine.rb ADDED
@@ -0,0 +1,3 @@
1
+ %w{util config client}.each do |f|
2
+ require File.dirname(__FILE__) << "/tweetwine/#{f}"
3
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tuomas-tweetwine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tuomas Kareinen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-22 00:00:00 -07:00
13
+ default_executable: tweetwine
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rest-client
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.2
34
+ version:
35
+ description: A simple but tasty Twitter agent for command line use, made for fun.
36
+ email: tkareine@gmail.com
37
+ executables:
38
+ - tweetwine
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - CHANGELOG.rdoc
43
+ - README.rdoc
44
+ files:
45
+ - Rakefile
46
+ - CHANGELOG.rdoc
47
+ - README.rdoc
48
+ - bin/tweetwine
49
+ - lib/tweetwine
50
+ - lib/tweetwine/client.rb
51
+ - lib/tweetwine/config.rb
52
+ - lib/tweetwine/util.rb
53
+ - lib/tweetwine.rb
54
+ has_rdoc: true
55
+ homepage: http://github.com/tuomas/tweetwine
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --title
59
+ - Tweetwine 0.1.0
60
+ - --main
61
+ - README.rdoc
62
+ - --exclude
63
+ - spec
64
+ - --line-numbers
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.2.0
83
+ signing_key:
84
+ specification_version: 2
85
+ summary: A simple Twitter agent for command line use
86
+ test_files: []
87
+