nakajima-twitter-client 0.0.2

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.
@@ -0,0 +1,25 @@
1
+ module Twitter
2
+ module Delegation
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def delegate(*args)
9
+ opts = args.last.is_a?(Hash) ? args.pop : {}
10
+ if target = opts[:to]
11
+ args.each do |sym|
12
+ class_eval(<<-END, __FILE__, __LINE__)
13
+ def #{sym}(*args, &block)
14
+ send(#{target.inspect}) \
15
+ .send(#{sym.inspect}, *args, &block)
16
+ end
17
+ END
18
+ end
19
+ else
20
+ raise ArgumentError, "You must pass a target as :to option"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module Twitter
2
+ # Namespace for constants and exceptions related to the Twitter API.
3
+ module API
4
+ # Everything goes to Twitter for now
5
+ BASE = 'twitter.com'
6
+
7
+ # Everything goes through JSON for now
8
+ FORMAT = 'json'
9
+
10
+ # Raised when API returns a 401 response status.
11
+ class Unauthorized < StandardError ; end
12
+ end
13
+ end
@@ -0,0 +1,52 @@
1
+ module Twitter
2
+ class Connection
3
+ include Delegation
4
+
5
+ attr_reader :session
6
+
7
+ delegate :username, :password, :to => :session
8
+
9
+ def initialize(session)
10
+ @session = session
11
+ end
12
+
13
+ # Make GET request with HTTP auth
14
+ def get(path, params={})
15
+ request(:get, path, params)
16
+ end
17
+
18
+ # Make post request with HTTP auth
19
+ def post(path, params={})
20
+ request(:post, path, params) do |req|
21
+ req.set_form_data(params)
22
+ end
23
+ end
24
+
25
+ # Ensure authentication credentials are valid
26
+ def authenticate!
27
+ @response ||= get('/account/verify_credentials')
28
+ end
29
+
30
+ private
31
+
32
+ # Wrap common request logic for POST/GET requests
33
+ def request(verb, path, params)
34
+ path = path + '.' + Twitter::API::FORMAT
35
+ res = Net::HTTP.start(Twitter::API::BASE) do |http|
36
+ req = Net::HTTP.const_get(verb.to_s.capitalize).new(path)
37
+ req.basic_auth username, password
38
+ yield req if block_given?
39
+ res = http.request(req)
40
+ check_response(res)
41
+ end
42
+ end
43
+
44
+ # Ensure response is valid
45
+ def check_response(res)
46
+ case res
47
+ when Net::HTTPUnauthorized then raise API::Unauthorized.new
48
+ else res
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ module Twitter
2
+ # Most of the logic in twitter-client lives in here. Responsible
3
+ # for maintaining/persisting credentials.
4
+ class Session
5
+ attr_reader :username, :password
6
+ attr_writer :filename
7
+
8
+ def initialize(username, password, opts={})
9
+ @username, @password = username, password
10
+ @connection = opts[:connected]
11
+ end
12
+
13
+ # Post a via the current session.
14
+ # TODO The Session class should be infrastructural. Domain logic like
15
+ # this should go elsewhere.
16
+ def tweet!(msg)
17
+ connection.post('/statuses/update', :status => msg) ; msg
18
+ end
19
+
20
+ # Ensure validity of credentials
21
+ def connect!
22
+ connection(true)
23
+ end
24
+
25
+ # Provide verification that session is able to be used
26
+ def connected?
27
+ if @connection
28
+ true
29
+ end
30
+ end
31
+
32
+ def ==(other)
33
+ [username, password] == [other.username, other.password]
34
+ end
35
+
36
+ private
37
+
38
+ def connection(force=false)
39
+ (@connection and not force) ? @connection : begin
40
+ @connection = Connection.new(self)
41
+ @connection.authenticate!
42
+ @connection
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,52 @@
1
+ module Twitter
2
+ class Store
3
+ attr_writer :filename
4
+
5
+ def initialize(env={})
6
+ @env = env
7
+ @sessions = @env[:sessions]
8
+ end
9
+
10
+ # Persist the current session to a dot file
11
+ def save
12
+ f = File.new(File.join(ENV['HOME'], filename), 'w+')
13
+ f << yaml_dump
14
+ f.close
15
+ end
16
+
17
+ # Attempt to load the current session from a dot file
18
+ def load
19
+ YAML.load_file(File.join(ENV['HOME'], filename)) || {}
20
+ end
21
+
22
+ # Allow custom name for dot file used to persist sessions
23
+ def filename
24
+ @filename || '.twitter'
25
+ end
26
+
27
+ def sessions
28
+ @sessions ||= begin
29
+ load[:sessions].inject({}) do |res, (key, val)|
30
+ password = crypter.decrypt_string(val)
31
+ res[key] = Session.new(key, password, :connected => true)
32
+ res
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def crypter
40
+ @crypter ||= Crypt::Blowfish.new('twitter-crypt-key')
41
+ end
42
+
43
+ def yaml_dump
44
+ session_data = sessions.inject({}) do |res, session|
45
+ res[session.username] = crypter.encrypt_string(session.password)
46
+ res
47
+ end
48
+
49
+ { :sessions => session_data }.to_yaml
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,77 @@
1
+ module Twitter
2
+ class << self
3
+ # Reset session
4
+ def reset!
5
+ sessions.clear
6
+ @username = nil
7
+ end
8
+
9
+ def use(username)
10
+ @username = username
11
+ end
12
+
13
+ def save(filename=nil)
14
+ store = Store.new(:sessions => sessions.values)
15
+ store.filename = filename if filename
16
+ store.save
17
+ end
18
+
19
+ def load(filename=nil)
20
+ store = Store.new
21
+ store.filename = filename if filename
22
+ if store.load
23
+ @sessions = store.sessions
24
+ @username = @sessions.keys.first
25
+ end
26
+ end
27
+
28
+ # Delegate method missing calls to session
29
+ def method_missing(sym, *args, &blk)
30
+ if authenticated? and delegating?(sym)
31
+ current_session.send(sym, *args, &blk)
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ # Check for presence of session or try to load saved session
38
+ def authenticated?(loaded=false)
39
+ return true if current_session and current_session.connected?
40
+ if not loaded
41
+ load
42
+ authenticated?(true)
43
+ end
44
+ end
45
+
46
+ # Create new session from passed credentials
47
+ def authenticate(username, password)
48
+ session = Session.new(username, password)
49
+ session.connect!
50
+ sessions[username] = session
51
+ @username = username
52
+ end
53
+
54
+ # Load saved session from a .twitter file
55
+ def load_session
56
+ session = Session.new(nil, nil)
57
+ if session.load
58
+ @session = session ; true
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def current_session
65
+ sessions[@username]
66
+ end
67
+
68
+ def sessions
69
+ @sessions ||= {}
70
+ end
71
+
72
+ # Check to see if Twitter is delegating this method to session
73
+ def delegating?(sym)
74
+ Session.public_instance_methods.include?(sym.to_s)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,49 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+
3
+ # standard library
4
+ require 'rubygems'
5
+ require 'fileutils'
6
+ require 'net/http'
7
+ require 'yaml'
8
+ require 'cgi'
9
+
10
+ # Gems
11
+ begin
12
+ require 'crypt/blowfish'
13
+ rescue LoadError
14
+ $NO_CRYPT = true
15
+ # Fake encrypter if it's not available
16
+ module Crypt
17
+ class Blowfish
18
+ def initialize(*args) end
19
+ def decrypt_string(str); str end
20
+ def encrypt_string(str); str end
21
+ end
22
+ end
23
+ puts " Warning!"
24
+ puts
25
+ puts " The crypt/blowfish gem was not found."
26
+ puts " As a result, saved session file will not be encrypted."
27
+ puts " Run `gem install crypt` to install encryption libraries."
28
+ end
29
+
30
+ # Extensions
31
+ require 'core_ext/delegation'
32
+
33
+ # Source files
34
+ require 'twitter/api'
35
+ require 'twitter/store'
36
+ require 'twitter/session'
37
+ require 'twitter/connection'
38
+ require 'twitter/twitter'
39
+
40
+ # Hpricot-y constant-method
41
+ def Twitter(*args, &block)
42
+ if args.empty? and not block_given?
43
+ Twitter
44
+ else
45
+ if args.length == 1
46
+ Twitter.tweet!(*args)
47
+ end
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nakajima-twitter-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Pat Nakajima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-30 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: patnakajima@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/core_ext
26
+ - lib/core_ext/delegation.rb
27
+ - lib/twitter
28
+ - lib/twitter/api.rb
29
+ - lib/twitter/connection.rb
30
+ - lib/twitter/session.rb
31
+ - lib/twitter/store.rb
32
+ - lib/twitter/twitter.rb
33
+ - lib/twitter-client.rb
34
+ has_rdoc: true
35
+ homepage: http://github.com/nakajima/twitter-client
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Simple, clean, redundant.
60
+ test_files: []
61
+