jxl-muq 0.0.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.
Files changed (5) hide show
  1. data/README.markdown +27 -0
  2. data/bin/muq +201 -0
  3. data/lib/muq.rb +265 -0
  4. data/muq.gemspec +15 -0
  5. metadata +91 -0
data/README.markdown ADDED
@@ -0,0 +1,27 @@
1
+ # Muq, a Muxtape CLI
2
+ Basic command line interface for <http://muxtape.com>.
3
+
4
+ ## Usage
5
+ See `muq up -h` and `muq dn -h` for details.
6
+
7
+ Muxtape's limits are 12 songs per tape and 10MB per song.
8
+
9
+ ### Upload examples
10
+ `muq up favorite.mp3 --user=bob --pass=bob --email=bob@example.com`
11
+ Create a new account for Bob and upload his favorite tune.
12
+
13
+ `muq up -u=larry -p=kensentme`
14
+ Upload every mp3 in the current directory to Larry's existing account.
15
+
16
+ `muq up some.mp3,another.mp3 -u=larry -p=kensentme --banner="Larry's Tape" --caption="Wooo" --color=ffdddd`
17
+ Upload mp3s and set banner, caption and color.
18
+
19
+ ### Download examples
20
+ `muq dn hiphop` or `mux dn http://hiphop.muxtape.com/`
21
+ Download a single tape.
22
+
23
+ `muq dn hip,hop,rap,rnb`
24
+ Download multiple tapes.
25
+
26
+ ## Bugs?
27
+ Fork and fix, thanks!
data/bin/muq ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../lib/muq'
3
+ require 'main'
4
+
5
+ Main do
6
+ mixin :login do
7
+ option('user', 'u') do
8
+ description 'Login (and subdomain) name.'
9
+ argument :required
10
+ required
11
+ end
12
+
13
+ option('pass', 'p') do
14
+ description 'Pwnage.'
15
+ argument :required
16
+ required
17
+ end
18
+
19
+ option('email', 'e') do
20
+ description 'Email address used to register a new account.'
21
+ argument :required
22
+ end
23
+
24
+ #option('random', 'r') do
25
+ # description 'Generate random user/pass/email if absent.'
26
+ #end
27
+ end
28
+
29
+ mixin :settings do
30
+ option('banner') do
31
+ description 'Title.'
32
+ argument :required
33
+ end
34
+
35
+ option('caption') do
36
+ description 'Subtitle.'
37
+ argument :required
38
+ end
39
+
40
+ option('color') do
41
+ description 'Color. (hex value without # prefix)'
42
+ argument :required
43
+ end
44
+ end
45
+
46
+ mixin :upload do
47
+ argument('mp3s') do
48
+ description 'Comma separated list of MP3s or directories to upload.'
49
+ cast :list_of_string
50
+ default "."
51
+ end
52
+ end
53
+
54
+ mixin :download do
55
+ argument('tapes') do
56
+ description 'Comma separated list of tape(s) to download.'
57
+ argument :required
58
+ cast :list_of_string
59
+ end
60
+
61
+ option('tracks') do
62
+ description 'Command separated list of track(s) to download.'
63
+ argument :required
64
+ cast :list_of_integer
65
+ end
66
+
67
+ argument('dir') do
68
+ description 'Base directory. A subdirectory is created for every tape.'
69
+ validate{|dir| File.directory?(dir)}
70
+ default "."
71
+ optional
72
+ end
73
+ end
74
+
75
+ mode :up do
76
+ mixin :login
77
+ mixin :settings
78
+ mixin :upload
79
+ end
80
+
81
+ mode :dn do
82
+ mixin :download
83
+ end
84
+
85
+ def run
86
+ params.each {|p| instance_variable_set("@#{p.name}", p.value)}
87
+
88
+ m = Muq.new
89
+
90
+ if @mode == "dn"
91
+ @tapes.each do |tape|
92
+ user = tape.scan(/(\w+)\.muxtape\.com/)
93
+ user = tape.scan(/\w+/) if user.empty?
94
+ puts "Downloading #{user}'s tape..."
95
+ FileUtils.mkdir_p("#{@dir}/#{user}")
96
+ songs = m.songs(user)
97
+ get = []
98
+ if @tracks
99
+ @tracks.each do |track|
100
+ get << songs[track-1]
101
+ end
102
+ else
103
+ get = songs
104
+ end
105
+ total = get.size
106
+ count = 1
107
+ get.each do |song|
108
+ puts "[#{count}/#{total}] #{song[:name]}"
109
+ m.download(song[:url], "#{@dir}/#{user}/#{song[:file]}")
110
+ count += 1
111
+ end
112
+ end
113
+ exit
114
+ end
115
+
116
+ #if @random
117
+ # @user ||= m.rnd_user
118
+ # @pass ||= m.rnd_pass
119
+ # @email ||= m.rnd_email
120
+ #end
121
+
122
+ if @mp3s
123
+ q = []
124
+ @mp3s.each do |mp3s|
125
+ if File.directory?(mp3s)
126
+ q += m.find_mp3s(mp3s)
127
+ elsif File.file?(mp3s)
128
+ q += mp3s.to_a
129
+ end
130
+ q = m.remove_dupes(q, @user)
131
+ begin
132
+ m.check_queue(q)
133
+ rescue Muq::TooManyFiles, Muq::FileTooLarge => e
134
+ puts e.to_s
135
+ exit 1
136
+ end
137
+ end
138
+ puts "Valid upload queue."
139
+ q_valid = true
140
+ end
141
+
142
+ if @user && @pass then
143
+ begin
144
+ m.login(@user, @pass)
145
+ puts "Valid login."
146
+ rescue Muq::InvalidLogin => e
147
+ puts e.to_s
148
+ if @email
149
+ puts "Trying to create account."
150
+ begin
151
+ m.register( @user, @pass, @email )
152
+ rescue Muq::NameTooLong, Muq::NameInUse, Muq::EmailInUse => e
153
+ puts e.to_s
154
+ m.logout
155
+ end
156
+ puts "Account '#{@user}' created with password '#{@pass}'."
157
+ m.set_color(m.rnd_color)
158
+ m.add_favorite('jxl') # nice index under 'favorited' ;)
159
+ else
160
+ puts "Can't create account without email."
161
+ m.logout
162
+ end
163
+ end
164
+
165
+ if @banner
166
+ m.set_banner(@banner)
167
+ puts "Banner set to '#{@banner}'"
168
+ end
169
+
170
+ if @caption
171
+ m.set_caption(@caption)
172
+ puts "Caption set to '#{@caption}'"
173
+ end
174
+
175
+ if @color
176
+ m.set_color(@color)
177
+ puts "Color set to '#{@color}'"
178
+ end
179
+
180
+ if q_valid
181
+ begin
182
+ total = q.size
183
+ count = 1
184
+ q.each do |file|
185
+ name = File.basename(file)
186
+ puts "[#{count}/#{total}] #{name}"
187
+ m.upload(file)
188
+ count += 1
189
+ end
190
+ rescue Muq::LimitReached, Muq::InvalidUpload => e
191
+ puts e.to_s
192
+ m.logout
193
+ end
194
+ puts "Done! (URL: http://#{@user}.#{m.host}/ PW: #{@pass})"
195
+ end
196
+ m.logout(:win)
197
+ else
198
+ help!
199
+ end
200
+ end
201
+ end
data/lib/muq.rb ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'tmpdir'
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ end
10
+
11
+ require 'curb'
12
+ require 'hpricot'
13
+ require 'id3lib'
14
+
15
+ class Muq
16
+ attr_accessor :c, :host
17
+
18
+ # the curb
19
+ def initialize()
20
+ @host = "muxtape.com"
21
+ @c = Curl::Easy.new do |c|
22
+ c.headers["User-Agent"] = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.14) Gecko/20080404 Iceweasel/2.0.0.14 (Debian-2.0.0.14-1)"
23
+ c.cookiejar = "#{Dir.tmpdir}/cookies.muq"
24
+ c.enable_cookies = true
25
+ c.verbose = false
26
+ end
27
+ @c
28
+ end
29
+
30
+ # create a new account
31
+ def register(user, pass, email)
32
+ raise NameTooLong, "Name too long." unless user.length < 32
33
+ post("#{@host}/create", {:name => user, :pass => pass, :email => email})
34
+ raise NameInUse, "Name in use." unless name_available?
35
+ raise EmailInUse, "Email in use." unless email_available?
36
+ end
37
+
38
+ # use existing account
39
+ def login(user, pass)
40
+ post("#{@host}/login", {:name => user, :pass => pass})
41
+ raise InvalidLogin, "Invalid login." unless valid_login?
42
+ end
43
+
44
+ # quit
45
+ def logout(win=false)
46
+ get("#{@host}/logout")
47
+ File.delete("#{Dir.tmpdir}/cookies.muq")
48
+ exit 1 unless win
49
+ exit 0
50
+ end
51
+
52
+ # find mp3s and sort in reverse order
53
+ # (the last uploaded file is displayed first on site)
54
+ def find_mp3s(dir)
55
+ Dir.glob("#{dir}/*.mp3").sort.reverse
56
+ end
57
+
58
+ # check if the queue is within the limits
59
+ def check_queue(queue)
60
+ amount = queue.size
61
+ raise TooManyFiles, "Too many files. (#{amount})" unless amount < 12
62
+ queue.each do |file|
63
+ raise FileTooLarge, "#{File.basename(file)} is too large." unless filesize_ok?(file)
64
+ end
65
+ end
66
+
67
+ # remove already uploaded mp3s from the queue
68
+ def remove_dupes(queue, user)
69
+ songs = just_songs(user)
70
+ new_queue = []
71
+ queue.each do |file|
72
+ tags = ID3Lib::Tag.new(file)
73
+ artist = tags.artist
74
+ title = tags.title
75
+ new_queue << file unless songs.include?("#{artist} - #{title}")
76
+ end
77
+ new_queue
78
+ end
79
+
80
+ # build an array of songs for the specified user
81
+ def just_songs(user)
82
+ doc = Hpricot(get("#{user}.#{@host}"))
83
+ out = []
84
+ doc.search("//li[@class='song']/div[@class='name']").each do |song|
85
+ out << song.inner_text.strip
86
+ end
87
+ out
88
+ end
89
+
90
+ # build an array of songs for the specified user
91
+ # (including download url and filename)
92
+ def songs(user)
93
+ doc = Hpricot(get("#{user}.#{@host}"))
94
+ scripts = doc.search("//script[@type='text/javascript']").inner_text
95
+ kettle = scripts.match(/new Kettle\((.*)\)/)[1]
96
+ split = kettle.scan(/\[.+?\]/)
97
+ hex = eval("Array.new(#{split[0]})")
98
+ sig = eval("Array.new(#{split[1]})")
99
+ out = []
100
+ if hex.size == sig.size
101
+ total = hex.size
102
+ total.times do |i|
103
+ name = doc.search("//li[@id='song#{hex[i]}']/div[@class='name']").inner_text.strip
104
+ url = "http://muxtape.s3.amazonaws.com/songs/"
105
+ url += "#{hex[i]}?PLEASE=DO_NOT_STEAL_MUSIC&#{sig[i]}" # rly sry lulz
106
+ out << {:name => name, :file => filename(name, i+1), :url => url}
107
+ end
108
+ end
109
+ out
110
+ end
111
+
112
+ # file upload
113
+ def upload(file)
114
+ @c.url = "#{@host}/upload"
115
+ raise LimitReached, "Song limit reached." unless room_left?
116
+ @c.multipart_form_post = true
117
+ @c.http_post(Curl::PostField.file('file', file))
118
+ @c.multipart_form_post = false
119
+ raise InvalidUpload, "Can't handle encoding." unless valid_upload?
120
+ end
121
+
122
+ # file download
123
+ def download(url, file)
124
+ Curl::Easy.download(url, file)
125
+ end
126
+
127
+ # set the banner/title
128
+ def set_banner(banner="")
129
+ set(:command => "bannercaption", :args => "none", :banner => banner)
130
+ end
131
+
132
+ # set the caption/sub-title
133
+ def set_caption(caption="")
134
+ set(:command => "bannercaption", :args => "none", :caption => caption)
135
+ end
136
+
137
+ # set the header color (hex value without # prefix)
138
+ def set_color(color="")
139
+ set(:command => "color", :args => color)
140
+ end
141
+
142
+ # change the password
143
+ def set_pass(pass)
144
+ set(:command => "password", :args => %Q{{"password1":"#{pass}","password2":"#{pass}"}} )
145
+ end
146
+
147
+ # add a user to favorites
148
+ def add_favorite(user)
149
+ set(:command => "favorite", :args => user)
150
+ end
151
+
152
+ # remove a user from favorites
153
+ def rm_favorite(user)
154
+ set(:command => "unfavorite", :args => user)
155
+ end
156
+
157
+ # random values
158
+ def rnd_user
159
+ rnd_string(rnd_size, false)
160
+ end
161
+
162
+ def rnd_pass
163
+ rnd_string
164
+ end
165
+
166
+ def rnd_email
167
+ "#{rnd_string}@#{rnd_string}.#{rnd_string(rnd_size(2,4), false)}"
168
+ end
169
+
170
+ def rnd_color
171
+ "#{rnd_string(6, true, "a", "f")}"
172
+ end
173
+
174
+ private
175
+ # http get
176
+ def get(url)
177
+ @c.url = url
178
+ @c.http_get
179
+ @c.body_str
180
+ end
181
+
182
+ # http post
183
+ def post(url, hash)
184
+ @c.url = url
185
+ fields = []
186
+ hash.each_pair do |key, value|
187
+ fields << Curl::PostField.content(key.to_s, value).to_s
188
+ end
189
+ @c.http_post(url, fields)
190
+ end
191
+
192
+ # change a setting
193
+ def set(hash)
194
+ post("#{@host}/settings/ajax", hash)
195
+ end
196
+
197
+ # grep body
198
+ def cant_find(regex)
199
+ @c.body_str.grep(regex).empty?
200
+ end
201
+
202
+ # errors checks
203
+ def filesize_ok?(file)
204
+ File.size(file) < (10 * 1024 * 1024)
205
+ end
206
+
207
+ def email_available?
208
+ cant_find(/already a user with that email address/)
209
+ end
210
+
211
+ def name_available?
212
+ cant_find(/that name is unavailable/)
213
+ end
214
+
215
+ def valid_login?
216
+ cant_find(/Invalid name or password\./)
217
+ end
218
+
219
+ def logged_in?
220
+ cant_find(/You must be \<a href=\"\/login\"\>logged in\<\/a\>/)
221
+ end
222
+
223
+ def room_left?
224
+ cant_find(/reached your limit of 12 songs/)
225
+ end
226
+
227
+ def valid_upload?
228
+ cant_find(/Try it one more time, if it fails again/)
229
+ end
230
+
231
+ # random-fu
232
+ def rnd_size(from=1, to=10)
233
+ (from + rand * to).floor
234
+ end
235
+
236
+ def rnd_string(size=rnd_size, num=true, from="a", to="z")
237
+ chars = (from..to).to_a
238
+ chars += ('0'..'9').to_a unless num == false
239
+ (1..size).collect{|a| chars[rand(chars.size)] }.join
240
+ end
241
+
242
+ # funky filename
243
+ def filename(name, nr)
244
+ file = name.downcase
245
+ file = file.gsub(" - ", "-")
246
+ file = file.gsub("/", "")
247
+ file = file.gsub(".", "")
248
+ file = file.gsub(",", "")
249
+ file = file.gsub(" ", " ")
250
+ file = file.gsub(" ", "_")
251
+ file = "#{"%02.0f" % nr}-#{file}-muq.mp3"
252
+ end
253
+
254
+ # you FAIL
255
+ class FAIL < RuntimeError; end
256
+ class InvalidLogin < FAIL; end
257
+ class NotLoggedIn < FAIL; end
258
+ class NameTooLong < FAIL; end
259
+ class NameInUse < FAIL; end
260
+ class EmailInUse < FAIL; end
261
+ class TooManyFiles < FAIL; end
262
+ class FileTooLarge < FAIL; end
263
+ class InvalidUpload < FAIL; end
264
+ class LimitReached < FAIL; end
265
+ end
data/muq.gemspec ADDED
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "muq"
3
+ s.version = "0.0.1"
4
+ s.summary = "Muxtape.com CLI"
5
+ s.description = "Command line interface for Muxtape."
6
+ s.author = "Ron Damen"
7
+ s.email = "jxl@ganja.nl"
8
+ s.homepage = "http://github.com/jxl/muq/"
9
+ s.files = ["README.markdown", "muq.gemspec", "bin/muq", "lib/muq.rb"]
10
+ s.executables = "muq"
11
+ s.add_dependency("main", ["> 0.0.0"])
12
+ s.add_dependency("curb", ["> 0.0.0"])
13
+ s.add_dependency("hpricot", ["> 0.0.0"])
14
+ s.add_dependency("id3lib-ruby", ["> 0.0.0"])
15
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jxl-muq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ron Damen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-04-26 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: main
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
+ - !ruby/object:Gem::Dependency
25
+ name: curb
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">"
30
+ - !ruby/object:Gem::Version
31
+ version: 0.0.0
32
+ version:
33
+ - !ruby/object:Gem::Dependency
34
+ name: hpricot
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.0
41
+ version:
42
+ - !ruby/object:Gem::Dependency
43
+ name: id3lib-ruby
44
+ version_requirement:
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">"
48
+ - !ruby/object:Gem::Version
49
+ version: 0.0.0
50
+ version:
51
+ description: Command line interface for Muxtape.
52
+ email: jxl@ganja.nl
53
+ executables:
54
+ - muq
55
+ extensions: []
56
+
57
+ extra_rdoc_files: []
58
+
59
+ files:
60
+ - README.markdown
61
+ - muq.gemspec
62
+ - bin/muq
63
+ - lib/muq.rb
64
+ has_rdoc: false
65
+ homepage: http://github.com/jxl/muq/
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.0.1
87
+ signing_key:
88
+ specification_version: 2
89
+ summary: Muxtape.com CLI
90
+ test_files: []
91
+