twitterscrobble 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-08-14
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Nicholas E. Rabenau
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ PostInstall.txt
5
+ README.txt
6
+ Rakefile
7
+ bin/twitterscrobble
8
+ lib/twitterscrobble.rb
9
+ lib/twitterscrobble/preferences.rb
10
+ lib/twitterscrobble/status_line.rb
11
+ lib/twitterscrobble/twitter_scrobbler.rb
12
+ lib/twitterscrobble/version.rb
data/PostInstall.txt ADDED
@@ -0,0 +1 @@
1
+ For more information on twitterscrobble, see http://twitterscrobble.rubyforge.org
data/README.txt ADDED
@@ -0,0 +1,82 @@
1
+ = twitterscrobble
2
+
3
+ * http://twitterscrobble.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ This application posts recently played tracks from last.fm to Twitter.
8
+
9
+ == SYNOPSIS:
10
+
11
+ twitterscrobble [options]
12
+
13
+ For help use:
14
+
15
+ twitterscrobble -h
16
+
17
+ The following command reads all most recently played tracks from a last.fm account
18
+ and posts them to Twitter. All settings are read from the preferences file.
19
+
20
+ twitterscrobble
21
+
22
+ The following command prints the status line of recent tracks to stdout (not posting to Twitter), assuming a history value of 0 and not saving state.
23
+
24
+ twitterscrobble --no-post --history 0 --no-save
25
+
26
+ The following posts all recent tracks and store user names and password in the preferences file. This is suitable as the initial invocation, as it creates the preferences file for later invocation without the user detail.
27
+
28
+ twitterscrobble --scrobbler-user foo --twitter-user FooBar --twitter-password bar
29
+
30
+ == REQUIREMENTS:
31
+
32
+ * twitter
33
+ * scrobbler
34
+ * shorturl
35
+
36
+ == INSTALL:
37
+
38
+ * sudo gem install twitterscrobble
39
+
40
+ This script is probably run best from CRON:
41
+
42
+ $> crontab -l
43
+ # m h dom mon dow command
44
+ * * * * * /home/nerab/projects/atom2nntp/workspace/twitterscrobble/bin/twitterscrobble --loglevel warn --logfile /home/nerab/.twitterscrobble/twitterscrobble.log
45
+
46
+ The entry to the user's crontab can be made with
47
+
48
+ crontab -e
49
+
50
+ This will invoke the default editor for making changes to the user's crontab.
51
+
52
+ The library will announce itself as "twitterscrobble" on Twitter (by sending the appropriate source parameter). The twitter gem
53
+ still has a bug about this, so the source parameter will not work until {this bug}[http://jnunemaker.lighthouseapp.com/projects/14843/tickets/11-source-parameter-does-not-get-through#ticket-11-1] has been fixed.
54
+
55
+ = Author
56
+
57
+ Nicolas E. Rabenau, nerab@gmx.at
58
+
59
+ == LICENSE:
60
+
61
+ (The MIT License)
62
+
63
+ Copyright (c) 2008 Nicholas E. Rabenau
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining
66
+ a copy of this software and associated documentation files (the
67
+ 'Software'), to deal in the Software without restriction, including
68
+ without limitation the rights to use, copy, modify, merge, publish,
69
+ distribute, sublicense, and/or sell copies of the Software, and to
70
+ permit persons to whom the Software is furnished to do so, subject to
71
+ the following conditions:
72
+
73
+ The above copyright notice and this permission notice shall be
74
+ included in all copies or substantial portions of the Software.
75
+
76
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
77
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Nicholas E. Rabenau on 2008-8-14.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+
12
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
13
+ require 'twitterscrobble'
14
+
15
+ begin
16
+ prefs = Twitterscrobble::Preferences.new
17
+ Twitterscrobble::TwitterScrobbler.new(prefs).update
18
+ prefs.save
19
+ rescue
20
+ $stderr.puts "\nError: #{$!}"
21
+ end
@@ -0,0 +1,208 @@
1
+ require 'yaml'
2
+ require 'optparse'
3
+ require 'logger'
4
+ require 'active_support'
5
+
6
+ module Twitterscrobble
7
+
8
+ class SimpleLogger < Logger
9
+ def format_message(severity, timestamp, progname, msg)
10
+ "#{timestamp.to_s(:db)} #{severity} #{msg}\n"
11
+ end
12
+ end
13
+
14
+ class Preferences
15
+ PREFERENCES_FILE_NAME = "preferences.yaml"
16
+ APP_NAME = "twitterscrobble"
17
+ TO_BE_SAVED = [:history, :twitter_user, :scrobbler_user, :twitter_password]
18
+
19
+ attr_reader :logger
20
+
21
+ def initialize
22
+ parsed_args = parse_args
23
+
24
+ begin
25
+ if !parsed_args[:stateless]
26
+ @cfg = YAML.load(File.open(full_prefs_file_path))
27
+ else
28
+ @cfg = {}
29
+ end
30
+ rescue
31
+ @cfg = {}
32
+ end
33
+
34
+ @cfg.merge!(parsed_args) # args override cfg
35
+
36
+ if @cfg[:logfile]
37
+ @logger = SimpleLogger.new(@cfg[:logfile])
38
+ else
39
+ @logger = SimpleLogger.new(STDOUT)
40
+ end
41
+
42
+ if @cfg[:loglevel]
43
+ @logger.level = @cfg[:loglevel]
44
+ else
45
+ @logger.level = Logger::WARN
46
+ end
47
+
48
+ @logger.debug("Command line preferences: #{parsed_args.reject{|k,v| k == :twitter_password}.inspect}")
49
+ @logger.debug("Effective preferences after merging in preferences file #{full_prefs_file_path}:")
50
+ @logger.debug(@cfg.reject{|k,v| k == :twitter_password}.inspect)
51
+
52
+ if 0 < check_required_args.size
53
+ msg = ""
54
+ msg << "Required argument#{'s' if 1 < check_required_args.size} #{check_required_args.join(', ')} "
55
+ msg << "not found in preferences or command line parameters."
56
+ raise msg
57
+ end
58
+ end
59
+
60
+ def save
61
+ unless @cfg[:no_save]
62
+ Dir.mkdir(preferences_dir) if !File.exist?(preferences_dir)
63
+
64
+ File.open(full_prefs_file_path, 'w') do |out|
65
+ save_cfg = @cfg.reject{|k, v| !TO_BE_SAVED.include?(k)}
66
+ @logger.debug("Writing preferences: #{save_cfg.reject{|k,v| k == :twitter_password}.inspect}")
67
+ YAML.dump(save_cfg, out)
68
+ end
69
+
70
+ File.new(full_prefs_file_path).chmod(0600)
71
+ end
72
+ end
73
+
74
+ # delegates reading of attributes to @cfg
75
+ def method_missing(methodname, *args)
76
+ @cfg[methodname]
77
+ end
78
+
79
+ def history=(history_stamp)
80
+ @cfg[:history] = history_stamp
81
+ end
82
+
83
+ private
84
+ def check_required_args
85
+ missing = []
86
+
87
+ if @cfg[:twitter_user].blank?
88
+ missing << "twitter-user"
89
+ end
90
+
91
+ if @cfg[:twitter_password].blank?
92
+ missing << "twitter-password"
93
+ end
94
+
95
+ if @cfg[:scrobbler_user].blank?
96
+ missing << "scrobbler-user"
97
+ end
98
+ missing
99
+ end
100
+
101
+
102
+ # some code taken from http://redshift.sourceforge.net/preferences/doc/classes/Preferences.src/M000002.html
103
+ # which is under the Ruby license (which is MIT compatible, I believe)
104
+ def preferences_dir
105
+ dir = nil
106
+ app_name = APP_NAME
107
+
108
+ case RUBY_PLATFORM
109
+ when /win32/
110
+ dir =
111
+ ENV['APPDATA'] || # C:\Documents and Settings\name\Application Data
112
+ ENV['USERPROFILE'] || # C:\Documents and Settings\name
113
+ ENV['HOME']
114
+ else
115
+ dir = ENV['HOME'] || File.expand_path('~')
116
+ app_name = "." << APP_NAME
117
+ end
118
+
119
+ unless dir
120
+ raise EnvError, "Can't determine a preferences directory."
121
+ end
122
+
123
+ File.join(dir, app_name)
124
+ end
125
+
126
+ def parse_args
127
+ options = {}
128
+
129
+ op = OptionParser.new do |opts|
130
+
131
+ #
132
+ # general flags
133
+ #
134
+
135
+ opts.on("-h", "--help", "Prints this help text and exits") do |v|
136
+ puts op.to_s
137
+ exit
138
+ end
139
+
140
+ opts.on("-v", "--version", "Prints the version of this script and exits") do |v|
141
+ puts "#{Twitterscrobble.name}, v.#{VERSION::STRING}"
142
+ exit
143
+ end
144
+
145
+ opts.on("-q", "--quiet", "Don't write anything to stdout") do |v|
146
+ options[:quiet] = true
147
+ end
148
+
149
+ opts.on("-f <logfile>", "--logfile <logfile>", "Specify the name of the log file") do |v|
150
+ options[:logfile] = v
151
+ end
152
+
153
+ level_map = {:debug => Logger::DEBUG, :info => Logger::INFO, :warn => Logger::WARN, :error => Logger::ERROR, :fatal => Logger::FATAL}
154
+ opts.on("-l LEVEL", "--loglevel LEVEL", level_map, "Specify the log level (#{level_map.keys.join(', ')})") do |t|
155
+ options[:loglevel] = t
156
+ end
157
+
158
+ #
159
+ # user data
160
+ #
161
+
162
+ opts.on("--scrobbler-user MANDATORY", "Username for which recent tracks are pulled off last.fm") do |v|
163
+ options[:scrobbler_user] = v
164
+ end
165
+
166
+ opts.on("--twitter-user MANDATORY", "Twitter user the recent tracks are posted to") do |v|
167
+ options[:twitter_user] = v
168
+ end
169
+
170
+ opts.on("--twitter-password MANDATORY", "Twitter password") do |v|
171
+ options[:twitter_password] = v
172
+ end
173
+
174
+ #
175
+ # behavioral flags
176
+ #
177
+
178
+ opts.on("-n", "--no-post", "Do not post status line to Twitter, instead write it to stdout.") do |v|
179
+ options[:no_post] = true
180
+ end
181
+
182
+ opts.on("-HMANDATORY", "--history MANDATORY", "assume the supplied history value") do |v|
183
+ options[:history] = v
184
+ end
185
+
186
+ opts.on("-mMANDATORY", "--max MANDATORY", "post only the <max> most recent songs") do |v|
187
+ options[:max] = v.to_i
188
+ end
189
+
190
+ opts.on("-s", "--stateless", "Do not read and write preferences file, implies --no-save") do |v|
191
+ options[:stateless] = true
192
+ options[:no_save] = true
193
+ end
194
+
195
+ opts.on("--no-save", "do not write preferences file") do |v|
196
+ options[:no_save] = true
197
+ end
198
+ end
199
+ op.parse!
200
+
201
+ options
202
+ end
203
+
204
+ def full_prefs_file_path
205
+ File.join(preferences_dir, PREFERENCES_FILE_NAME)
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,75 @@
1
+ require 'shorturl'
2
+
3
+ module Twitterscrobble
4
+ class StatusLine
5
+ TWITTER_MAX_MSG_LEN = 140
6
+
7
+ def initialize(track)
8
+ @track = track
9
+ end
10
+
11
+ # TODO
12
+ # The algorithm to use the maximum number of characters is not the best yet.
13
+ # We should also try to cut down single fields that are overly long in favor of
14
+ # not loosing another short field.
15
+ #
16
+ # Example:
17
+ #
18
+ # track.name = "A"
19
+ # track.artist = "Very long artist name that is close to the maximum length of the whole line"
20
+ #
21
+ # The algorithm below would just return "A", but a better version would return "Very long ... - A".
22
+ #
23
+ #
24
+ def to_s
25
+ status = with_name
26
+
27
+ if with_artist.length <= TWITTER_MAX_MSG_LEN
28
+ status = with_artist
29
+ end
30
+
31
+ if with_album.length <= TWITTER_MAX_MSG_LEN
32
+ status = with_album
33
+ end
34
+
35
+ if with_url.length <= TWITTER_MAX_MSG_LEN
36
+ status = with_url
37
+ end
38
+
39
+ # if status.length > TWITTER_MAX_MSG_LEN # still too long, enforce length restriction
40
+ # status = "#{status[1, 137]}..."
41
+ # end
42
+
43
+ status
44
+ end
45
+
46
+ private
47
+ def with_name
48
+ "Now playing: #{CGI::escapeHTML(@track.name)}"
49
+ end
50
+
51
+ def with_artist
52
+ if @track.artist.blank?
53
+ with_name
54
+ else
55
+ "Now playing: #{CGI::escapeHTML(@track.artist)} - #{CGI::escapeHTML(@track.name)}"
56
+ end
57
+ end
58
+
59
+ def with_album
60
+ if @track.album.blank?
61
+ with_artist
62
+ else
63
+ "#{with_artist} (from #{CGI::escapeHTML(@track.album)})"
64
+ end
65
+ end
66
+
67
+ def with_url
68
+ if @track.url.blank?
69
+ with_album
70
+ else
71
+ "#{with_album} #{ShortURL.shorten(@track.url)}"
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,55 @@
1
+ require 'scrobbler'
2
+ require 'twitter'
3
+
4
+ module Twitterscrobble
5
+ #
6
+ # Read the latest tracks of a user from last.fm and post them to Twitter.
7
+ #
8
+ class TwitterScrobbler
9
+ TWITTER_SOURCE_PARAM = "twitterscrobble"
10
+
11
+ def initialize(prefs)
12
+ @prefs = prefs
13
+ @logger = @prefs.logger
14
+ end
15
+
16
+ def update
17
+ @logger.info("Reading recent tracks for #{@prefs.scrobbler_user}")
18
+
19
+ # Order by date_uts so that we can rely on tracks being steady
20
+ tracks = Scrobbler::User.new(@prefs.scrobbler_user).recent_tracks.sort{|x,y| x.date_uts <=> y.date_uts }
21
+
22
+ if !@prefs.max.blank? && 0 < @prefs.max
23
+ @logger.info("Limiting number of posted songs to #{@prefs.max}")
24
+ tracks = tracks[@prefs.max * -1, @prefs.max]
25
+ end
26
+
27
+ # Filter already posted tracks
28
+ tracks.reject!{|track|
29
+ if track.date_uts.to_i <= @prefs.history.to_i
30
+ @logger.info("Skipping #{track.artist} - #{track.name} because #{track.date_uts.to_i} <= #{@prefs.history.to_i}")
31
+ true
32
+ end
33
+ }
34
+
35
+ @logger.info("No new tracks since #{Time.at(@prefs.history.to_i).to_s}") if 0 == tracks.size
36
+
37
+ tracks.each{|track|
38
+ status = StatusLine.new(track)
39
+
40
+ begin
41
+ if @prefs.no_post
42
+ puts status
43
+ else
44
+ @logger.info("Posting new status: '#{status}'")
45
+ Twitter::Base.new(@prefs.twitter_user, @prefs.twitter_password).update(status, {:source => TWITTER_SOURCE_PARAM})
46
+ end
47
+
48
+ @prefs.history = track.date_uts.to_i
49
+ rescue
50
+ @logger.error("Error: #{$!}")
51
+ end
52
+ }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ module Twitterscrobble
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ Dir[File.join(File.dirname(__FILE__), 'twitterscrobble/**/*.rb')].each { |lib| require lib }
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/twitterscrobble'
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestTwitterscrobble < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twitterscrobble
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nicholas E. Rabenau
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-22 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.7.0
24
+ version:
25
+ description: Posts recently played tracks from last.fm to Twitter.
26
+ email:
27
+ - nerab@gmx.at
28
+ executables:
29
+ - twitterscrobble
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - History.txt
34
+ - License.txt
35
+ - Manifest.txt
36
+ - PostInstall.txt
37
+ - README.txt
38
+ files:
39
+ - History.txt
40
+ - License.txt
41
+ - Manifest.txt
42
+ - PostInstall.txt
43
+ - README.txt
44
+ - Rakefile
45
+ - bin/twitterscrobble
46
+ - lib/twitterscrobble.rb
47
+ - lib/twitterscrobble/preferences.rb
48
+ - lib/twitterscrobble/status_line.rb
49
+ - lib/twitterscrobble/twitter_scrobbler.rb
50
+ - lib/twitterscrobble/version.rb
51
+ has_rdoc: true
52
+ homepage: http://twitterscrobble.rubyforge.org
53
+ post_install_message: |
54
+ For more information on twitterscrobble, see http://twitterscrobble.rubyforge.org
55
+
56
+ rdoc_options:
57
+ - --main
58
+ - README.txt
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project: twitterscrobble
76
+ rubygems_version: 1.2.0
77
+ signing_key:
78
+ specification_version: 2
79
+ summary: Posts recently played tracks from last.fm to Twitter.
80
+ test_files:
81
+ - test/test_helper.rb
82
+ - test/test_twitterscrobble.rb