pat-maddox-twinkies 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION.yml +4 -0
- data/bin/twinkies +41 -0
- data/lib/twinkies/friend_searcher.rb +14 -0
- data/lib/twinkies/item.rb +41 -0
- data/lib/twinkies/server.rb +58 -0
- data/lib/twinkies/url_list.rb +33 -0
- data/lib/twinkies.rb +6 -0
- data/spec/item_spec.rb +46 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/url_list_spec.rb +48 -0
- metadata +89 -0
data/VERSION.yml
ADDED
data/bin/twinkies
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ostruct'
|
4
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
5
|
+
require 'twinkies'
|
6
|
+
|
7
|
+
def run_app(base_dir)
|
8
|
+
config = OpenStruct.new
|
9
|
+
Dir.chdir(base_dir) do
|
10
|
+
eval File.read('twinkies_config.rb')
|
11
|
+
config.tweet_db = File.expand_path(config.tweet_db)
|
12
|
+
end
|
13
|
+
require 'twinkies/server'
|
14
|
+
Sinatra::Application.default_options[:env] = :production
|
15
|
+
Twinkies::Server.new(config).run
|
16
|
+
end
|
17
|
+
|
18
|
+
if ARGV.size == 1
|
19
|
+
username = ARGV.first
|
20
|
+
|
21
|
+
if File.directory?(username)
|
22
|
+
run_app username
|
23
|
+
else
|
24
|
+
FileUtils.mkdir(username)
|
25
|
+
Dir.chdir(username) do
|
26
|
+
File.open('twinkies_config.rb', 'w+') do |file|
|
27
|
+
file << "config.username='#{username}'\n"
|
28
|
+
file << "config.password='CHANGE_ME'\n"
|
29
|
+
file << "config.tweet_db='#{username}_tweets.db'\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
else
|
34
|
+
if File.exists?('twinkies_config.rb')
|
35
|
+
run_app '.'
|
36
|
+
else
|
37
|
+
puts "Usage: twinkies [username]"
|
38
|
+
puts "\ttwinkies padillac - creates a new dir named padillac with a config file if none exists, or runs a twinkie instance with the config in that dir"
|
39
|
+
puts "\ttwinkies - will run a twinkies instance with the config in the current dir"
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Twinkies
|
2
|
+
class FriendSearcher
|
3
|
+
def initialize(username, password)
|
4
|
+
@username = username
|
5
|
+
@password = password
|
6
|
+
end
|
7
|
+
|
8
|
+
def search(term)
|
9
|
+
Twitter::Base.new(@username, @password).timeline.select do |tweet|
|
10
|
+
tweet.text.include?('http')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Twinkies
|
2
|
+
class Item
|
3
|
+
include DataMapper::Resource
|
4
|
+
property :id, Integer, :serial => true
|
5
|
+
property :twitter_id, Integer
|
6
|
+
property :link, String
|
7
|
+
property :user, String
|
8
|
+
property :created_at, DateTime
|
9
|
+
property :text, Text, :lazy => false
|
10
|
+
|
11
|
+
def tweet=(tweet)
|
12
|
+
self.twitter_id = tweet.id
|
13
|
+
self.text = tweet.text
|
14
|
+
self.user = tweet.user.screen_name
|
15
|
+
self.created_at = tweet.created_at
|
16
|
+
end
|
17
|
+
|
18
|
+
def save
|
19
|
+
@existing = Item.first(:twitter_id => twitter_id)
|
20
|
+
if @existing && @existing.link == link
|
21
|
+
self.id = @existing.id
|
22
|
+
true
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.latest
|
29
|
+
all :order => [:twitter_id.desc], :limit => 100
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.refresh(username, password)
|
33
|
+
raise "Must provide credentials" if username.nil? || password.nil?
|
34
|
+
|
35
|
+
tweets = FriendSearcher.new(username, password).search('http')
|
36
|
+
UrlList.new(tweets) {|t| t.text}.each do |item|
|
37
|
+
create :link => item[:url], :tweet => item[:item]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
|
3
|
+
module Twinkies
|
4
|
+
class Server
|
5
|
+
def initialize(config)
|
6
|
+
@tweet_db = config.tweet_db
|
7
|
+
@username = config.username
|
8
|
+
@password = config.password
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
setup_db
|
13
|
+
start_tweet_refresher
|
14
|
+
handle_request
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup_db
|
18
|
+
DataMapper.setup(:default, "sqlite3://#{@tweet_db}")
|
19
|
+
DataMapper.auto_upgrade!
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_tweet_refresher
|
23
|
+
Thread.abort_on_exception = true
|
24
|
+
Thread.new do
|
25
|
+
loop do
|
26
|
+
puts "refreshing tweets..."
|
27
|
+
Item.refresh @username, @password
|
28
|
+
sleep 60
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_request
|
34
|
+
username = @username # builder must instance_eval?
|
35
|
+
get '/feed.xml' do
|
36
|
+
builder do |xml|
|
37
|
+
xml.instruct!
|
38
|
+
xml.rss :version => '2.0' do
|
39
|
+
xml.channel do
|
40
|
+
xml.title "#{username}'s twitter URL feed"
|
41
|
+
xml.description "#{username}'s twitter URL feed"
|
42
|
+
xml.link "http://twitter.com/#{username}"
|
43
|
+
|
44
|
+
Item.latest.each do |tweet|
|
45
|
+
xml.item do
|
46
|
+
xml.title "#{tweet.user} - #{tweet.text}"
|
47
|
+
xml.link tweet.link
|
48
|
+
xml.pubDate tweet.created_at
|
49
|
+
xml.guid tweet.id, :isPermaLink => false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Twinkies
|
2
|
+
class UrlList
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(*strings)
|
6
|
+
strings = strings.first if strings.size == 1 && strings.first.is_a?(Array)
|
7
|
+
@urls = strings.inject([]) do |urls, string|
|
8
|
+
if block_given?
|
9
|
+
yield(string).scan(/http:\/\/\S+/) {|s| urls << {:item => string, :url => s}}
|
10
|
+
urls
|
11
|
+
else
|
12
|
+
urls += string.scan(/http:\/\/\S+/)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
@urls.each &block
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
@urls.inspect
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
@urls.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
@urls == other
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/twinkies.rb
ADDED
data/spec/item_spec.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
module Twinkies
|
4
|
+
describe Item do
|
5
|
+
describe "created" do
|
6
|
+
before(:each) do
|
7
|
+
@tweet = Twitter::Status.new
|
8
|
+
@tweet.id = 123
|
9
|
+
@tweet.text = "hello mang!"
|
10
|
+
@tweet.created_at = DateTime.parse('7/24/1985')
|
11
|
+
@user = Twitter::User.new
|
12
|
+
@user.screen_name = 'padillac'
|
13
|
+
@tweet.user = @user
|
14
|
+
@item = Item.create :tweet => @tweet, :link => "http://foo"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should get the twitter ID from the tweet id" do
|
18
|
+
@item.twitter_id.should == 123
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should get the text from the tweet" do
|
22
|
+
@item.text.should == "hello mang!"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should get the screen name from the tweet" do
|
26
|
+
@item.user.should == 'padillac'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should get the date from the tweet" do
|
30
|
+
@item.created_at.should == DateTime.parse('7/24/1985')
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "created with the same twitter ID" do
|
34
|
+
it "should return existing record if URLs are the same" do
|
35
|
+
i = Item.create :tweet => @tweet, :link => "http://foo"
|
36
|
+
i.should == @item
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should create a new item if URLs are different" do
|
40
|
+
i = Item.create :tweet => @tweet, :link => "http://bar"
|
41
|
+
i.should_not == @item
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
require 'twinkies'
|
5
|
+
|
6
|
+
DataMapper.setup(:default, 'sqlite3::memory:')
|
7
|
+
|
8
|
+
Spec::Runner.configure do |config|
|
9
|
+
config.before(:each) { DataMapper.auto_migrate! }
|
10
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Twinkies
|
5
|
+
describe UrlList do
|
6
|
+
it "should find a url from a string" do
|
7
|
+
UrlList.new('http://foo').should include('http://foo')
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should find a url from a list" do
|
11
|
+
UrlList.new('http://foo', 'http://bar').
|
12
|
+
should include('http://foo', 'http://bar')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should find a url from an array" do
|
16
|
+
UrlList.new(['http://foo', 'http://bar']).
|
17
|
+
should include('http://foo', 'http://bar')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should find a url from a string with extra chars" do
|
21
|
+
UrlList.new('I like http://foo !!').should include('http://foo')
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not find extra characters" do
|
25
|
+
UrlList.new('I like http://foo !!').
|
26
|
+
should_not include('I', 'like')
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should find multiple urls from a single string" do
|
30
|
+
UrlList.new('here: http://foo & http://bar').
|
31
|
+
should include('http://foo', 'http://bar')
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have the enumerable methods" do
|
35
|
+
UrlList.new('http://foo http://bar', 'http://baz').
|
36
|
+
map.should == ['http://foo', 'http://bar', 'http://baz']
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "working with objects instead of strings" do
|
40
|
+
it "should get the strings based on a block" do
|
41
|
+
tweet = OpenStruct.new(:text => 'http://foo http://bar')
|
42
|
+
UrlList.new(tweet) {|t| t.text }.
|
43
|
+
should == [{:item => tweet, :url => 'http://foo'},
|
44
|
+
{:item => tweet, :url => 'http://bar'}]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pat-maddox-twinkies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pat Maddox
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-09 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sinatra
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: dm-core
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0"
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: do_sqlite3
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "0"
|
41
|
+
version:
|
42
|
+
description: TODO
|
43
|
+
email: pat.maddox@gmail.com
|
44
|
+
executables:
|
45
|
+
- twinkies
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
extra_rdoc_files: []
|
49
|
+
|
50
|
+
files:
|
51
|
+
- VERSION.yml
|
52
|
+
- bin/twinkies
|
53
|
+
- lib/twinkies.rb
|
54
|
+
- lib/twinkies
|
55
|
+
- lib/twinkies/friend_searcher.rb
|
56
|
+
- lib/twinkies/server.rb
|
57
|
+
- lib/twinkies/url_list.rb
|
58
|
+
- lib/twinkies/item.rb
|
59
|
+
- spec/spec_helper.rb
|
60
|
+
- spec/url_list_spec.rb
|
61
|
+
- spec/item_spec.rb
|
62
|
+
has_rdoc: false
|
63
|
+
homepage: http://github.com/pat-maddox/twinkies
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.2.0
|
85
|
+
signing_key:
|
86
|
+
specification_version: 2
|
87
|
+
summary: TODO
|
88
|
+
test_files: []
|
89
|
+
|