remi-tweet-tweet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,59 @@
1
+ tweet-tweet
2
+ ===========
3
+
4
+ tweet-tweet is a little script/utility that, when run, will perform any
5
+ user-defined functions such as popping up new tweets via Growl/etc
6
+
7
+ Install
8
+ -------
9
+
10
+ $ sudo gem sources --add http://gems.github.com
11
+ $ sudo gem install remi-tweet-tweet
12
+
13
+ Usage
14
+ -----
15
+
16
+ $ tweet-tweet
17
+
18
+ The first time tweet-tweet is run, it'll ask you for your twitter username and password.
19
+
20
+ Your username/password are encrypted and stored in ~/.tweettweet/auth
21
+
22
+ To manually set your username/password or reset them, run:
23
+
24
+ $ tweet-tweet-setup
25
+
26
+ How does it work?
27
+ -----------------
28
+
29
+ the idea is really simple. the latest tweets get passed to user-defined
30
+ functions (which you can define in your ~/.tweettweetrc
31
+
32
+ to use the functions that come out-of-the-box with tweet-tweet, you can
33
+ simply require them in your ~/.tweettweetrc, like so:
34
+
35
+ require 'tweet-tweet/growl'
36
+ require 'tweet-tweet/gnome'
37
+
38
+ def print_tweet tweet
39
+ puts "#{ tweet.user.name }: #{ tweet.text }"
40
+ end
41
+
42
+ TweetTweet.tweeters << :print_tweet
43
+
44
+ TweetTweet.tweeters << lambda { |tweet| puts "#{ tweet.user.name } says '#{ tweet.text }'" }
45
+
46
+ if you don't put anything in ~/.tweettweetrc, 'tweet-tweet/echo' will automatically be
47
+ required (a simple 'tweeter' which simply prints out the tweets)
48
+
49
+ tweet-tweet stores your latest tweets in ~/.tweettweet as well as
50
+ resources like local copies of users' avatars
51
+
52
+ to run tweet-tweet once, simply:
53
+
54
+ $ tweet-tweet
55
+
56
+ for now, i'm not writing a deamon for tweet-tweet. it's recommended
57
+ to run tweet-tweet via `watch`. to run once every minute:
58
+
59
+ $ watch -n 60 tweet-tweet
data/TODO ADDED
@@ -0,0 +1 @@
1
+ * move all encryption randomness to a class to abstract away the OpenSSL usage
data/bin/tweet-tweet ADDED
@@ -0,0 +1,3 @@
1
+ #! /usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../lib/tweet-tweet'
3
+ TweetTweet.run
@@ -0,0 +1,3 @@
1
+ #! /usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../lib/tweet-tweet'
3
+ TweetTweet.set_login_info *TweetTweet.get_login_info
@@ -0,0 +1,188 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'rubygems'
4
+
5
+ class TweetTweet
6
+ BASE_DIR = File.expand_path '~/.tweettweet'
7
+ RC_FILE = File.expand_path '~/.tweettweetrc'
8
+
9
+ class << self
10
+ # an array of methods / lambdas / whatever for passing the latest tweets to, one at a time
11
+ attr_accessor :tweeters
12
+ end
13
+
14
+ # runs 'tweet-tweet'
15
+ #
16
+ # gets latest tweets and, for each, passes them to TweetTweet.tweeters
17
+ def self.run
18
+ require 'twitter'
19
+ tweets = Twitter::Base.new(*TweetTweet.login_info).timeline
20
+ new_tweets = get_new tweets
21
+ puts "#{ new_tweets.length } new tweets"
22
+ save_images new_tweets
23
+ new_tweets.each do |tweet|
24
+ TweetTweet.tweeters.each do |tweeter|
25
+ run_tweeter tweeter, tweet
26
+ end
27
+ end
28
+ mark_as_read new_tweets
29
+ end
30
+
31
+ # runs a 'tweeter' given a tweet ... calls anything that responds_to #call, else assumes the name of a method
32
+ def self.run_tweeter tweeter, tweet
33
+ if tweeter.respond_to?:call
34
+ tweeter.call tweet
35
+ else
36
+ send tweeter.to_s, tweet
37
+ end
38
+ end
39
+
40
+ # returns Twitter client
41
+ def self.twitter
42
+ require 'twitter'
43
+ Twitter::Base.new(*TweetTweet.login_info)
44
+ end
45
+
46
+ # returns current twitter timeline
47
+ def self.timeline
48
+ twitter.timeline
49
+ end
50
+
51
+ # returns the path on the local filesystem for a particular tweet's avatar
52
+ def self.path_to_image tweet
53
+ File.join avatar_image_dir, "#{ tweet.user.screen_name }.jpg"
54
+ end
55
+
56
+ # saves avatars for tweets if we don't have them already
57
+ def self.save_images tweets
58
+ %w( open-uri fileutils ).each { |lib| require lib }
59
+ FileUtils.mkdir_p avatar_image_dir
60
+ tweets.each do |tweet|
61
+ unless File.file?path_to_image(tweet)
62
+ File.open(path_to_image(tweet),'w'){ |f| f << open(tweet.user.profile_image_url).read }
63
+ end
64
+ end
65
+ end
66
+
67
+ # returns the path on the local filesystem for a particular tweet
68
+ def self.path_to_tweet tweet
69
+ File.join tweet_dir, tweet.id.to_s
70
+ end
71
+
72
+ # returns an array of tweets that are new (haven't been read before), given an array of tweets
73
+ def self.get_new tweets
74
+ tweets.select { |tweet| !File.file? path_to_tweet(tweet) }
75
+ end
76
+
77
+ # takes a list of tweets and marks them all as having been seen so they won't be returned anymore by get_new
78
+ def self.mark_as_read tweets
79
+ %w( open-uri fileutils ).each { |lib| require lib }
80
+ FileUtils.mkdir_p tweet_dir
81
+ get_new(tweets).each { |tweet| FileUtils.touch path_to_tweet(tweet) }
82
+ end
83
+
84
+ # directory where user's avatars are stored
85
+ def self.avatar_image_dir
86
+ File.join TweetTweet::BASE_DIR, 'images'
87
+ end
88
+
89
+ # directory where previously read tweets are stored
90
+ def self.tweet_dir
91
+ File.join TweetTweet::BASE_DIR, 'tweets'
92
+ end
93
+
94
+ # file where we persist login information
95
+ def self.auth_file
96
+ File.join TweetTweet::BASE_DIR, 'auth'
97
+ end
98
+
99
+ def self.decrypt ciphertext, key, iv
100
+ require 'openssl'
101
+
102
+ cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
103
+ cipher.decrypt
104
+ cipher.key = key
105
+ cipher.iv = iv
106
+
107
+ plaintext = cipher.update ciphertext
108
+ plaintext << cipher.final
109
+ plaintext
110
+ end
111
+
112
+ def self.get_random_key
113
+ require 'openssl'
114
+ OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_key
115
+ end
116
+
117
+ def self.get_random_iv
118
+ require 'openssl'
119
+ OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_iv
120
+ end
121
+
122
+ def self.encrypt plaintext, key, iv
123
+ require 'openssl'
124
+
125
+ cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
126
+ cipher.encrypt
127
+ cipher.key = key
128
+ cipher.iv = iv
129
+
130
+ encrypted = cipher.update plaintext
131
+ encrypted << cipher.final
132
+ end
133
+
134
+ # gets the currently persisted login information, decrypted
135
+ def self.login_info
136
+ TweetTweet.set_login_info(*TweetTweet.get_login_info) unless File.file?TweetTweet::auth_file
137
+
138
+ require 'yaml'
139
+
140
+ auth = YAML::load(File.read(TweetTweet::auth_file))
141
+ key, iv = auth[:random]
142
+
143
+ username = decrypt auth[:username], key, iv
144
+ password = decrypt auth[:password], key, iv
145
+
146
+ return username, password
147
+ end
148
+
149
+ # prompts user for their login info
150
+ def self.get_login_info
151
+ print 'Twitter Username: '
152
+ username = gets.strip
153
+
154
+ print 'Twitter Password: '
155
+ system "stty -echo"
156
+ password = gets.strip
157
+ system "stty echo"
158
+ puts ''
159
+
160
+ return username, password
161
+ end
162
+
163
+ # encrypts and persists login information
164
+ def self.set_login_info username, password
165
+ %w( yaml openssl fileutils ).each { |lib| require lib }
166
+
167
+ key = get_random_key
168
+ iv = get_random_iv
169
+
170
+ encrypted_username = encrypt username, key, iv
171
+ encrypted_password = encrypt password, key, iv
172
+
173
+ auth = {
174
+ :username => encrypted_username,
175
+ :password => encrypted_password,
176
+ :random => [ key, iv ]
177
+ }
178
+
179
+ FileUtils.mkdir_p File.dirname(TweetTweet::auth_file)
180
+ File.open(TweetTweet::auth_file,'w'){ |f| f << auth.to_yaml }
181
+ end
182
+ end
183
+
184
+ TweetTweet.tweeters ||= []
185
+
186
+ eval File.read(TweetTweet::RC_FILE) if File.file? TweetTweet::RC_FILE
187
+
188
+ require 'tweet-tweet/echo' if TweetTweet.tweeters.empty?
@@ -0,0 +1,3 @@
1
+ # simple tweeter to simply echo (puts) tweet
2
+
3
+ TweetTweet.tweeters << lambda { |tweet| puts "#{ tweet.user.name }: #{ tweet.text }" }
@@ -0,0 +1,27 @@
1
+ class TweetTweet::GnomeNotify
2
+
3
+ EXPIRATION_IN_SECONDS = 2
4
+
5
+ # Convenience method to send an error notification message
6
+ #
7
+ # [stock_icon] Stock icon name of icon to display
8
+ # [title] Notification message title
9
+ # [message] Core message for the notification
10
+ def self.notify icon, title, message
11
+ options = "-t #{EXPIRATION_IN_SECONDS * 1000} -i '#{icon}'"
12
+ system "notify-send #{options} '#{title}' '#{message}'"
13
+ end
14
+
15
+ # method for poping up a new tweet notification
16
+ def self.tweet tweet
17
+ notify TweetTweet.path_to_image(tweet), tweet.user.name, tweet.text
18
+ end
19
+
20
+ # make this #callable
21
+ def self.call tweet
22
+ self.tweet tweet
23
+ end
24
+
25
+ end
26
+
27
+ TweetTweet.tweeters << TweetTweet::GnomeNotify
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "tweet-tweet"
3
+ s.version = "0.1.0"
4
+ s.date = "2008-06-19"
5
+ s.summary = "Custom Twitter Notifications"
6
+ s.email = "remi@remitaylor.com"
7
+ s.homepage = "http://github.com/remi/tweet-tweet"
8
+ s.description = "Twitter notifier for performing actions when there are new tweets"
9
+ s.has_rdoc = true
10
+ s.rdoc_options = ["--quiet", "--title", "domain-finder", "--opname", "index.html", "--line-numbers", "--main", "README", "--inline-source"]
11
+ s.extra_rdoc_files = ['README']
12
+ s.authors = ["remi Taylor"]
13
+
14
+ s.add_dependency("twitter", ["> 0.0.0"])
15
+ s.executables = ["tweet-tweet","tweet-tweet-setup"]
16
+ s.default_executable = "tweet-tweet"
17
+
18
+ # generate using: $ ruby -e "puts Dir['**/**'].select{|x| File.file?x}.inspect"
19
+ s.files = ["bin/tweet-tweet", "bin/tweet-tweet-setup", "lib/tweet-tweet/echo.rb", "lib/tweet-tweet/gnome.rb", "lib/tweet-tweet.rb", "README.markdown", "TODO", "tweet-tweet.gemspec"]
20
+
21
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remi-tweet-tweet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - remi Taylor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-19 00:00:00 -07:00
13
+ default_executable: tweet-tweet
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: twitter
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">"
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.0
23
+ version:
24
+ description: Twitter notifier for performing actions when there are new tweets
25
+ email: remi@remitaylor.com
26
+ executables:
27
+ - tweet-tweet
28
+ - tweet-tweet-setup
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - bin/tweet-tweet
35
+ - bin/tweet-tweet-setup
36
+ - lib/tweet-tweet/echo.rb
37
+ - lib/tweet-tweet/gnome.rb
38
+ - lib/tweet-tweet.rb
39
+ - README.markdown
40
+ - TODO
41
+ - tweet-tweet.gemspec
42
+ - README
43
+ has_rdoc: true
44
+ homepage: http://github.com/remi/tweet-tweet
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --quiet
48
+ - --title
49
+ - domain-finder
50
+ - --opname
51
+ - index.html
52
+ - --line-numbers
53
+ - --main
54
+ - README
55
+ - --inline-source
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.0.1
74
+ signing_key:
75
+ specification_version: 2
76
+ summary: Custom Twitter Notifications
77
+ test_files: []
78
+