jxl-muq 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+