dschn-twitter 0.3.7.1
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/History.txt +106 -0
- data/License.txt +19 -0
- data/Manifest.txt +71 -0
- data/README.txt +84 -0
- data/Rakefile +4 -0
- data/bin/twitter +15 -0
- data/config/hoe.rb +74 -0
- data/config/requirements.rb +17 -0
- data/examples/blocks.rb +15 -0
- data/examples/direct_messages.rb +28 -0
- data/examples/favorites.rb +20 -0
- data/examples/friends_followers.rb +25 -0
- data/examples/friendships.rb +13 -0
- data/examples/identica_timeline.rb +7 -0
- data/examples/location.rb +8 -0
- data/examples/posting.rb +9 -0
- data/examples/replies.rb +26 -0
- data/examples/search.rb +17 -0
- data/examples/sent_messages.rb +26 -0
- data/examples/timeline.rb +33 -0
- data/examples/twitter.rb +27 -0
- data/examples/verify_credentials.rb +13 -0
- data/lib/twitter.rb +21 -0
- data/lib/twitter/base.rb +260 -0
- data/lib/twitter/cli.rb +328 -0
- data/lib/twitter/cli/config.rb +9 -0
- data/lib/twitter/cli/helpers.rb +97 -0
- data/lib/twitter/cli/migrations/20080722194500_create_accounts.rb +13 -0
- data/lib/twitter/cli/migrations/20080722194508_create_tweets.rb +16 -0
- data/lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb +9 -0
- data/lib/twitter/cli/migrations/20080722214606_create_configurations.rb +13 -0
- data/lib/twitter/cli/models/account.rb +33 -0
- data/lib/twitter/cli/models/configuration.rb +13 -0
- data/lib/twitter/cli/models/tweet.rb +20 -0
- data/lib/twitter/direct_message.rb +22 -0
- data/lib/twitter/easy_class_maker.rb +43 -0
- data/lib/twitter/rate_limit_status.rb +19 -0
- data/lib/twitter/search.rb +94 -0
- data/lib/twitter/status.rb +22 -0
- data/lib/twitter/user.rb +37 -0
- data/lib/twitter/version.rb +9 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/spec/base_spec.rb +109 -0
- data/spec/cli/helper_spec.rb +35 -0
- data/spec/direct_message_spec.rb +35 -0
- data/spec/fixtures/followers.xml +706 -0
- data/spec/fixtures/friends.xml +609 -0
- data/spec/fixtures/friends_for.xml +584 -0
- data/spec/fixtures/friends_lite.xml +192 -0
- data/spec/fixtures/friends_timeline.xml +66 -0
- data/spec/fixtures/public_timeline.xml +148 -0
- data/spec/fixtures/rate_limit_status.xml +7 -0
- data/spec/fixtures/search_results.json +1 -0
- data/spec/fixtures/status.xml +25 -0
- data/spec/fixtures/user.xml +38 -0
- data/spec/fixtures/user_timeline.xml +465 -0
- data/spec/search_spec.rb +89 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/status_spec.rb +40 -0
- data/spec/user_spec.rb +42 -0
- data/tasks/deployment.rake +50 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/twitter.gemspec +49 -0
- data/website/css/common.css +47 -0
- data/website/images/terminal_output.png +0 -0
- data/website/index.html +156 -0
- metadata +180 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateTweets < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :tweets do |t|
|
4
|
+
t.datetime :occurred_at
|
5
|
+
t.boolean :truncated, :favorited, :user_protected, :default => false
|
6
|
+
t.integer :twitter_id, :user_id, :in_reply_to_status_id, :in_reply_to_user_id, :user_followers_count
|
7
|
+
t.text :body
|
8
|
+
t.string :source, :user_name, :user_screen_name, :user_location, :user_description, :user_profile_image_url, :user_url
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :tweets
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Account < ActiveRecord::Base
|
2
|
+
named_scope :current, :conditions => {:current => true}
|
3
|
+
|
4
|
+
has_many :tweets, :dependent => :destroy
|
5
|
+
|
6
|
+
def self.add(hash)
|
7
|
+
username = hash.delete(:username)
|
8
|
+
account = find_or_initialize_by_username(username)
|
9
|
+
account.attributes = hash
|
10
|
+
account.save
|
11
|
+
set_current(account) if new_active_needed?
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.active
|
15
|
+
current.first
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.set_current(account_or_id)
|
19
|
+
account = account_or_id.is_a?(Account) ? account_or_id : find(account_or_id)
|
20
|
+
account.update_attribute :current, true
|
21
|
+
Account.update_all "current = 0", "id != #{account.id}"
|
22
|
+
account
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.new_active_needed?
|
26
|
+
self.current.count == 0 && self.count > 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{current? ? '*' : ' '} #{username}"
|
31
|
+
end
|
32
|
+
alias to_str to_s
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Configuration < ActiveRecord::Base
|
2
|
+
serialize :data
|
3
|
+
|
4
|
+
def self.[](key)
|
5
|
+
key = find_by_key(key.to_s)
|
6
|
+
key.nil? ? nil : key.data
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.[]=(key, data)
|
10
|
+
c = find_or_create_by_key(key.to_s)
|
11
|
+
c.update_attribute :data, data
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Tweet < ActiveRecord::Base
|
2
|
+
belongs_to :account
|
3
|
+
|
4
|
+
def self.create_from_tweet(account, s)
|
5
|
+
tweet = account.tweets.find_or_initialize_by_twitter_id(s.id)
|
6
|
+
tweet.body = s.text
|
7
|
+
tweet.occurred_at = s.created_at
|
8
|
+
|
9
|
+
%w[truncated favorited in_reply_to_status_id in_reply_to_user_id source].each do |m|
|
10
|
+
tweet.send("#{m}=", s.send(m))
|
11
|
+
end
|
12
|
+
|
13
|
+
%w[id followers_count name screen_name location description
|
14
|
+
profile_image_url url protected].each do |m|
|
15
|
+
tweet.send("user_#{m}=", s.user.send(m))
|
16
|
+
end
|
17
|
+
tweet.save!
|
18
|
+
tweet
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Twitter
|
2
|
+
class DirectMessage
|
3
|
+
include EasyClassMaker
|
4
|
+
|
5
|
+
attributes :id, :text, :sender_id, :recipient_id, :created_at, :sender_screen_name, :recipient_screen_name
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Creates a new status from a piece of xml
|
9
|
+
def new_from_xml(xml)
|
10
|
+
DirectMessage.new do |d|
|
11
|
+
d.id = (xml).at('id').innerHTML
|
12
|
+
d.text = (xml).get_elements_by_tag_name('text').innerHTML
|
13
|
+
d.sender_id = (xml).at('sender_id').innerHTML
|
14
|
+
d.recipient_id = (xml).at('recipient_id').innerHTML
|
15
|
+
d.created_at = (xml).at('created_at').innerHTML
|
16
|
+
d.sender_screen_name = (xml).at('sender_screen_name').innerHTML
|
17
|
+
d.recipient_screen_name = (xml).at('recipient_screen_name').innerHTML
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# This is pretty much just a macro for creating a class that allows
|
2
|
+
# using a block to initialize stuff and to define getters and setters
|
3
|
+
# really quickly.
|
4
|
+
module Twitter
|
5
|
+
module EasyClassMaker
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# creates the attributes class variable and creates each attribute's accessor methods
|
13
|
+
def attributes(*attrs)
|
14
|
+
@@attributes = attrs
|
15
|
+
@@attributes.each { |a| attr_accessor a }
|
16
|
+
end
|
17
|
+
|
18
|
+
# read method for attributes class variable
|
19
|
+
def self.attributes; @@attributes end
|
20
|
+
end
|
21
|
+
|
22
|
+
# allows for any class that includes this to use a block to initialize
|
23
|
+
# variables instead of assigning each one seperately
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
#
|
27
|
+
# instead of...
|
28
|
+
#
|
29
|
+
# s = Status.new
|
30
|
+
# s.foo = 'thing'
|
31
|
+
# s.bar = 'another thing'
|
32
|
+
#
|
33
|
+
# you can ...
|
34
|
+
#
|
35
|
+
# Status.new do |s|
|
36
|
+
# s.foo = 'thing'
|
37
|
+
# s.bar = 'another thing'
|
38
|
+
# end
|
39
|
+
def initialize
|
40
|
+
yield self if block_given?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Twitter
|
2
|
+
class RateLimitStatus
|
3
|
+
include EasyClassMaker
|
4
|
+
|
5
|
+
attributes :reset_time_in_seconds, :reset_time, :remaining_hits, :hourly_limit
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# Creates a new rate limi status from a piece of xml
|
9
|
+
def new_from_xml(xml)
|
10
|
+
RateLimitStatus.new do |s|
|
11
|
+
s.reset_time_in_seconds = xml.at('reset-time-in-seconds').inner_html.to_i
|
12
|
+
s.reset_time = Time.parse xml.at('reset-time').inner_html
|
13
|
+
s.remaining_hits = xml.at('remaining-hits').inner_html.to_i
|
14
|
+
s.hourly_limit = xml.at('hourly-limit').inner_html.to_i
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
gem 'httparty'
|
2
|
+
require 'httparty'
|
3
|
+
|
4
|
+
module Twitter
|
5
|
+
class Search
|
6
|
+
include HTTParty
|
7
|
+
include Enumerable
|
8
|
+
base_uri 'search.twitter.com'
|
9
|
+
|
10
|
+
attr_reader :result, :query
|
11
|
+
|
12
|
+
def initialize(q=nil)
|
13
|
+
clear
|
14
|
+
containing(q) unless q.blank?
|
15
|
+
end
|
16
|
+
|
17
|
+
def from(user)
|
18
|
+
@query[:q] << "from:#{user}"
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def to(user)
|
23
|
+
@query[:q] << "to:#{user}"
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def referencing(user)
|
28
|
+
@query[:q] << "@#{user}"
|
29
|
+
self
|
30
|
+
end
|
31
|
+
alias :references :referencing
|
32
|
+
alias :ref :referencing
|
33
|
+
|
34
|
+
def containing(word)
|
35
|
+
@query[:q] << "#{word}"
|
36
|
+
self
|
37
|
+
end
|
38
|
+
alias :contains :containing
|
39
|
+
|
40
|
+
# adds filtering based on hash tag ie: #twitter
|
41
|
+
def hashed(tag)
|
42
|
+
@query[:q] << "##{tag}"
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# lang must be ISO 639-1 code ie: en, fr, de, ja, etc.
|
47
|
+
#
|
48
|
+
# when I tried en it limited my results a lot and took
|
49
|
+
# out several tweets that were english so i'd avoid
|
50
|
+
# this unless you really want it
|
51
|
+
def lang(lang)
|
52
|
+
@query[:lang] = lang
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Limits the number of results per page
|
57
|
+
def per_page(num)
|
58
|
+
@query[:rpp] = num
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Only searches tweets since a given id.
|
63
|
+
# Recommended to use this when possible.
|
64
|
+
def since(since_id)
|
65
|
+
@query[:since_id] = since_id
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Search tweets by longitude, latitude and a given range.
|
70
|
+
# Ranges like 25km and 50mi work.
|
71
|
+
def geocode(long, lat, range)
|
72
|
+
@query[:geocode] = [long, lat, range].join(',')
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Clears all the query filters to make a new search
|
77
|
+
def clear
|
78
|
+
@query = {}
|
79
|
+
@query[:q] = []
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# If you want to get results do something other than iterate over them.
|
84
|
+
def fetch
|
85
|
+
@query[:q] = @query[:q].join(' ')
|
86
|
+
self.class.get('/search.json', {:query => @query})
|
87
|
+
end
|
88
|
+
|
89
|
+
def each
|
90
|
+
@result = fetch()
|
91
|
+
@result['results'].each { |r| yield r }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Twitter
|
2
|
+
class Status
|
3
|
+
include EasyClassMaker
|
4
|
+
|
5
|
+
attributes :created_at, :id, :text, :user, :source, :truncated, :in_reply_to_status_id, :in_reply_to_user_id, :favorited
|
6
|
+
|
7
|
+
# Creates a new status from a piece of xml
|
8
|
+
def self.new_from_xml(xml)
|
9
|
+
s = new
|
10
|
+
s.id = (xml).at('id').innerHTML
|
11
|
+
s.created_at = (xml).at('created_at').innerHTML
|
12
|
+
s.text = (xml).get_elements_by_tag_name('text').innerHTML
|
13
|
+
s.source = (xml).at('source').innerHTML
|
14
|
+
s.truncated = (xml).at('truncated').innerHTML == 'false' ? false : true
|
15
|
+
s.favorited = (xml).at('favorited').innerHTML == 'false' ? false : true
|
16
|
+
s.in_reply_to_status_id = (xml).at('in_reply_to_status_id').innerHTML
|
17
|
+
s.in_reply_to_user_id = (xml).at('in_reply_to_user_id').innerHTML
|
18
|
+
s.user = User.new_from_xml(xml.at('user')) if (xml).at('user')
|
19
|
+
s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/twitter/user.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Twitter
|
2
|
+
class User
|
3
|
+
include EasyClassMaker
|
4
|
+
|
5
|
+
attributes :id, :name, :screen_name, :status, :location, :description, :url,
|
6
|
+
:profile_image_url, :profile_background_color, :profile_text_color, :profile_link_color,
|
7
|
+
:profile_sidebar_fill_color, :profile_sidebar_border_color, :friends_count, :followers_count,
|
8
|
+
:favourites_count, :statuses_count, :utc_offset , :protected
|
9
|
+
|
10
|
+
# Creates a new user from a piece of xml
|
11
|
+
def self.new_from_xml(xml)
|
12
|
+
u = new
|
13
|
+
u.id = (xml).at('id').innerHTML
|
14
|
+
u.name = (xml).at('name').innerHTML
|
15
|
+
u.screen_name = (xml).at('screen_name').innerHTML
|
16
|
+
u.location = (xml).at('location').innerHTML
|
17
|
+
u.description = (xml).at('description').innerHTML
|
18
|
+
u.url = (xml).at('url').innerHTML
|
19
|
+
u.profile_image_url = (xml).at('profile_image_url').innerHTML
|
20
|
+
|
21
|
+
# optional, not always present
|
22
|
+
u.profile_background_color = (xml).at('profile_background_color').innerHTML if (xml).at('profile_background_color')
|
23
|
+
u.profile_text_color = (xml).at('profile_text_color').innerHTML if (xml).at('profile_text_color')
|
24
|
+
u.profile_link_color = (xml).at('profile_link_color').innerHTML if (xml).at('profile_link_color')
|
25
|
+
u.profile_sidebar_fill_color = (xml).at('profile_sidebar_fill_color').innerHTML if (xml).at('profile_sidebar_fill_color')
|
26
|
+
u.profile_sidebar_border_color = (xml).at('profile_sidebar_border_color').innerHTML if (xml).at('profile_sidebar_border_color')
|
27
|
+
u.friends_count = (xml).at('friends_count').innerHTML if (xml).at('friends_count')
|
28
|
+
u.followers_count = (xml).at('followers_count').innerHTML if (xml).at('followers_count')
|
29
|
+
u.favourites_count = (xml).at('favourites_count').innerHTML if (xml).at('favourites_count')
|
30
|
+
u.statuses_count = (xml).at('statuses_count').innerHTML if (xml).at('statuses_count')
|
31
|
+
u.utc_offset = (xml).at('utc_offset').innerHTML if (xml).at('utc_offset')
|
32
|
+
u.protected = (xml).at('protected').innerHTML == 'false' ? false : true if (xml).at('protected')
|
33
|
+
u.status = Status.new_from_xml(xml.at('status')) if (xml).at('status')
|
34
|
+
u
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/script/txt2html
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
begin
|
5
|
+
require 'newgem'
|
6
|
+
rescue LoadError
|
7
|
+
puts "\n\nGenerating the website requires the newgem RubyGem"
|
8
|
+
puts "Install: gem install newgem\n\n"
|
9
|
+
exit(1)
|
10
|
+
end
|
11
|
+
require 'redcloth'
|
12
|
+
require 'syntax/convertors/html'
|
13
|
+
require 'erb'
|
14
|
+
require File.dirname(__FILE__) + '/../lib/twitter/version.rb'
|
15
|
+
|
16
|
+
version = Twitter.git::VERSION::STRING
|
17
|
+
download = 'http://rubyforge.org/projects/twitter'
|
18
|
+
|
19
|
+
class Fixnum
|
20
|
+
def ordinal
|
21
|
+
# teens
|
22
|
+
return 'th' if (10..19).include?(self % 100)
|
23
|
+
# others
|
24
|
+
case self % 10
|
25
|
+
when 1: return 'st'
|
26
|
+
when 2: return 'nd'
|
27
|
+
when 3: return 'rd'
|
28
|
+
else return 'th'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Time
|
34
|
+
def pretty
|
35
|
+
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def convert_syntax(syntax, source)
|
40
|
+
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
41
|
+
end
|
42
|
+
|
43
|
+
if ARGV.length >= 1
|
44
|
+
src, template = ARGV
|
45
|
+
template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
|
46
|
+
|
47
|
+
else
|
48
|
+
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
|
49
|
+
exit!
|
50
|
+
end
|
51
|
+
|
52
|
+
template = ERB.new(File.open(template).read)
|
53
|
+
|
54
|
+
title = nil
|
55
|
+
body = nil
|
56
|
+
File.open(src) do |fsrc|
|
57
|
+
title_text = fsrc.readline
|
58
|
+
body_text = fsrc.read
|
59
|
+
syntax_items = []
|
60
|
+
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
|
61
|
+
ident = syntax_items.length
|
62
|
+
element, syntax, source = $1, $2, $3
|
63
|
+
syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
|
64
|
+
"syntax-temp-#{ident}"
|
65
|
+
}
|
66
|
+
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
67
|
+
body = RedCloth.new(body_text).to_html
|
68
|
+
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
|
69
|
+
end
|
70
|
+
stat = File.stat(src)
|
71
|
+
created = stat.ctime
|
72
|
+
modified = stat.mtime
|
73
|
+
|
74
|
+
$stdout << template.result(binding)
|