deadlist 1.0.1 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cc3ef0661dbc9d67bc88bae8f63b3b42a273e3376647cfee377b9466c23c0d1
4
- data.tar.gz: 5f2424f582c3c5c27f102cf842d64083c35b1d20e675e5513b1767014b064673
3
+ metadata.gz: a7a8fcc71296e7c196d233a170985d82a143da192420557a869ec6924bc165bc
4
+ data.tar.gz: 1bffff8c94561151b48ec9adabcdb418e7df13971b810f7deb61d5f4ceda678b
5
5
  SHA512:
6
- metadata.gz: 5e1ca2667a3a681a0119da589919ed8486b416d7963eeb63a318605a4a0d0d1ad85d02b5c8b086587cbe92101992aedaf9fa6bb39a953ae864ddad32f154ad5a
7
- data.tar.gz: 1f64dfee13234319fcbc450192d510e900add592839b32956950fa40c9c8e0cff1383edaec206fc5febaa88d4c1a265011cc089548f7e157265273a8e27097a2
6
+ metadata.gz: 142ff3d85a10c2ead5b00a14692dbbd0736c3f9b0c8b7ff0ec5a1c92b278bd11038459ae2fc0f175b85035f7222fa8f7a705541315a60bda213beccc1e6247c8
7
+ data.tar.gz: 6c86fae1e34b504a2385e241ec97b54c1944db52caa4025d6d03f982c8d2199c1c07ec500277d0966a5b0c2d30a3383f844dc995d84087f7149dbd324b3f0d85
@@ -0,0 +1,74 @@
1
+ class ArgumentParser
2
+ def self.parse(args, version)
3
+ params = {}
4
+
5
+ parser = OptionParser.new do |opts|
6
+ opts.banner = "Usage: deadlist [options]"
7
+ opts.separator ""
8
+ opts.separator "Required options:"
9
+
10
+ opts.on("-i", "--id ID", "ID of show(s) to download (comma-separated for multiple)") do |id|
11
+ params[:ids] = id.split(',').map(&:strip)
12
+ end
13
+
14
+ opts.on("-f", "--format FORMAT", "Format to download (mp3, flac, ogg)") do |format|
15
+ params[:format] = format.downcase
16
+ end
17
+
18
+ opts.separator ""
19
+ opts.separator "Other options:"
20
+
21
+ opts.on("-d", "--directory PATH", "Directory to save show(s) to. Will default to /shows/ in the execution directory") do |dir|
22
+ params[:directory] = dir
23
+ end
24
+
25
+ opts.on("-h", "--help", "Show this help") do
26
+ puts opts
27
+ exit
28
+ end
29
+
30
+ opts.on("-v", "--version", "Show version") do
31
+ puts "deadlist v#{version}"
32
+ exit
33
+ end
34
+
35
+ opts.on('-q', '--quiet', 'Run silently') do
36
+ params[:quiet] = true
37
+ end
38
+
39
+ opts.on('--dry-run', 'List tracks to be downloaded') do
40
+ params[:dry_run] = true
41
+ end
42
+ end
43
+
44
+ parser.parse!(args)
45
+ validate_required_params!(params, parser)
46
+ params
47
+ rescue OptionParser::InvalidOption => e
48
+ puts "Error: #{e.message}"
49
+ puts parser
50
+ exit(1)
51
+ end
52
+
53
+ private
54
+
55
+ def self.validate_required_params!(params, parser)
56
+ has_ids = params[:ids]&.any?
57
+ has_format = params[:format]
58
+
59
+ # If one is provided, both must be provided
60
+ if !has_ids && !has_format
61
+ puts "Error: Arguments are required for DeadList, try --help for more info"
62
+ puts parser
63
+ exit(1)
64
+ elsif has_ids && !has_format
65
+ puts "Error: --format is required when --id is provided"
66
+ puts parser
67
+ exit(1)
68
+ elsif has_format && !has_ids
69
+ puts "Error: --id is required when --format is provided"
70
+ puts parser
71
+ exit(1)
72
+ end
73
+ end
74
+ end
@@ -1,52 +1,29 @@
1
1
  # The Client class manages HTML scraping and parsing for the CLI and other classes above it. Any HTML work should be handled here.
2
2
  class Client
3
- # Returns a show_data object for helping in the creation of a new Show
4
- def scrape_show_info(show_link)
5
- doc = get_page_source(show_link)
6
- track_divs = doc.css('div[itemprop="track"]')
3
+ def query_show_info(show_id)
4
+ url = "https://archive.org/metadata/#{show_id}"
5
+ response = HTTParty.get(url)
7
6
 
8
- show_data = {
9
- date: extract_metadata(doc, itemprop: 'datePublished'),
10
- location: extract_metadata(doc, label: 'Location'),
11
- venue: extract_metadata(doc, label: 'Venue'),
12
- transferred_by: extract_metadata(doc, label: 'Transferred by'),
13
- duration: extract_metadata(doc, label: 'Run time'),
14
- tracks: extract_track_data(track_divs)
15
- }
16
-
17
- return show_data
18
- rescue => e
19
- puts "\n❌ Data extraction failed: #{e.message}"
20
- end
21
-
22
- private
23
-
24
- # Returns nokogiri-fied page HTML for use in scraping show info
25
- def get_page_source(show_link)
26
- return Nokogiri::HTML(HTTParty.get(show_link).body)
27
- rescue => e
28
- puts "\n❌ Scraping failed: #{e.message}"
29
- end
30
-
31
- # Handles finding of values via 'label' and 'itemprop' Xpath values
32
- def extract_metadata(doc, label: nil, itemprop: nil)
33
- if label
34
- # For dt/dd metadata pairs
35
- doc.xpath("//dt[normalize-space(text())='#{label}']/following-sibling::dd").first&.text&.strip
36
- elsif itemprop
37
- # For itemprop attributes
38
- doc.xpath("//*[@itemprop='#{itemprop}']").first&.content&.strip
7
+ unless response.success?
8
+ raise "API request failed: #{response.code}"
39
9
  end
40
- end
41
10
 
42
- # Hunts through track-divs for data required to create Tracks
43
- def extract_track_data(track_divs)
44
- track_divs.each_with_index.map do |div, i|
45
- {
46
- pos: i + 1,
47
- name: div.css('meta[itemprop="name"]').first&.[]('content'),
48
- links: div.css('link[itemprop="associatedMedia"]').map { |link| link['href'] }
49
- }
11
+ unless response["metadata"]
12
+ raise "Invalid show ID: #{show_id}"
50
13
  end
14
+
15
+ show_data = {
16
+ date: response["metadata"]["date"],
17
+ location: response["metadata"]["coverage"],
18
+ venue: response["metadata"]["venue"],
19
+ transferred_by: response["metadata"]["transferer"],
20
+ duration: response["metadata"]["runtime"],
21
+ dir: response["metadata"]["identifier"],
22
+ files: response["files"]
23
+ }
24
+
25
+ show_data
26
+ rescue HTTParty::Error, StandardError => e
27
+ raise "Failed to fetch show data: #{e.message}"
51
28
  end
52
29
  end
@@ -1,19 +1,41 @@
1
1
  # A simple class to download files to a given directory. Expects details for the filename and a link.
2
2
  # One Downloader should be created / show being downloaded. Downloaders can run on seperate threads for getting many hows at once.
3
3
  class Downloader
4
- def initialize(path, format)
4
+ BASE_API_URL = 'https://archive.org'
5
+
6
+ def initialize(path, format, logger: Logger.new($stdout))
7
+ @logger = logger
5
8
  @path = path
6
9
  @format = format
7
10
  end
11
+
12
+ def download_url_for_show(show_id)
13
+ "#{BASE_API_URL}/download/#{show_id}/"
14
+ end
8
15
 
9
16
  # Goes to a link (assuming the format is already validated), and gets the file, saving with argument names.
10
- def get(pos, name, link)
11
- uri = URI.parse(link); raise ArgumentError, "Only HTTP(S) URLs allowed" unless uri.is_a?(URI::HTTP)
12
-
17
+ def get(root_url, track_object)
18
+ uri = URI.parse(root_url + track_object.filename); raise ArgumentError, "Only HTTP(S) URLs allowed" unless uri.is_a?(URI::HTTP)
13
19
  download = uri.open
14
- filename = "#{@path}/#{pos} -- #{name}.#{@format}"
15
- IO.copy_stream(download, filename)
20
+
21
+ # Extract disc number from filename
22
+ disc_match = track_object.filename.match(/(?<!g)d(\d+)/)
23
+ sanitized_title = track_object.title.gsub('/', '-')
24
+
25
+ if disc_match
26
+ # Multi-disc: use disc-track format (1-01, 2-01, etc.)
27
+ disc_num = disc_match[1]
28
+ padded_track = track_object.pos.rjust(2, '0')
29
+ filename = "#{@path}/#{disc_num}-#{padded_track} -- #{sanitized_title}.#{@format}"
30
+ else
31
+ # Single disc: regular format
32
+ filename = "#{@path}/#{track_object.pos} -- #{sanitized_title}.#{@format}"
33
+ end
34
+
35
+ IO.copy_stream(download, filename)
36
+ true
16
37
  rescue => e
17
- puts "❌ Download failed: #{e.message}"
38
+ @logger.error "❌ Download failed for '#{track_object.title}': #{e.message}"
39
+ false
18
40
  end
19
41
  end
data/lib/deadlist/cli.rb CHANGED
@@ -2,69 +2,92 @@ require_relative 'cli/client'
2
2
  require_relative 'cli/downloader'
3
3
  require_relative 'models/show'
4
4
  require_relative 'models/track'
5
+ require_relative 'cli/argument_parser'
5
6
  require 'fileutils'
7
+ require 'optparse'
6
8
 
7
9
  # The CLI is the 'session' created by the main class, managing arguments passed in and housing methods for scraping and downloading shows.
8
10
  class CLI
9
- def initialize(version, args)
11
+ attr_reader :args, :show
12
+
13
+ def initialize(version, args, logger: Logger.new($stdout))
10
14
  @version = version
11
15
  @args = {}
12
16
  @show = nil
17
+ @logger = logger
18
+ @logger.formatter = proc do |severity, datetime, progname, msg|
19
+ "#{msg}\n"
20
+ end
13
21
 
14
22
  startup_text
15
23
  parse_arguments(args)
16
24
  end
17
25
 
18
- # Reads arguments passed at the command line and maps them to an instance object
19
- def parse_arguments(args)
20
- args.each do |arg|
21
- key, value = arg.split('=')
22
- @args[key.tr('--', '').to_sym] = value
23
- end
24
- end
25
-
26
26
  # Creates new show object with link given populated with metadata and track details
27
- def scrape_links
28
- @show = Show.new(@args[:show])
29
- puts "\n💿 #{@show.tracks.length} tracks found!"
27
+ def create_show
28
+ show_id = @args[:ids].first
29
+ extracted_id = extract_show_id(show_id)
30
+ @show = Show.new(extracted_id, @args[:format], logger: @logger)
31
+
32
+ @logger.info "💿 #{@show.name} - #{@show.tracks.length} tracks found!"
30
33
  rescue => e
31
- puts "\n❌ Scraping failed: #{e.message}"
34
+ @logger.error "❌ Scraping failed: #{e.message}"
32
35
  end
33
36
 
34
- # Validates format isn't for test, and passes directory + format arguments to the download method of a Show
37
+ # Downloads show tracks or displays dry-run preview
35
38
  def download_show
36
- download_format = @args[:format]
37
-
38
- if download_format == "test"
39
- puts "Test Download, skipping"
40
- elsif @show.has_format?(download_format)
41
- download_path = setup_directories(@show)
42
- @show.download_tracks(download_path, download_format)
39
+ if @args[:dry_run]
40
+ @logger.info "🔍 Dry Run: #{@show.name} will be downloaded with #{@show.tracks.count} tracks"
41
+ @show.tracks.each do |track|
42
+ @logger.info " #{track.pos} - #{track.title}"
43
+ end
43
44
  else
44
- puts "\n❌ #{download_format} not found for this show! #{@show.tracks[0].available_formats} available"
45
+ download_directory = setup_directories(@show, @args[:directory])
46
+ @show.download_tracks(download_directory)
45
47
  end
48
+ rescue => e
49
+ @logger.error "❌ Download failed: #{e.message}"
46
50
  end
47
51
 
48
52
  private
49
53
 
50
54
  # Deadlist starts with some friendly text
51
55
  def startup_text
52
- puts "\n\n"
53
- puts '='*52
54
- puts "🌹⚡️ One man gathers what another man spills... ⚡️🌹"
55
- puts '='*52
56
+ @logger.info '='*52
57
+ @logger.info "🌹⚡️ One man gathers what another man spills... ⚡️🌹"
58
+ @logger.info '='*52
59
+ end
60
+
61
+ # Reads arguments passed at the command line and maps them to an instance object
62
+ def extract_show_id(show_input)
63
+ if show_input.include?('archive.org/details/')
64
+ show_input.split('/details/').last
65
+ else
66
+ show_input
67
+ end
68
+ end
69
+
70
+ def parse_arguments(args)
71
+ @args = ArgumentParser.parse(args, @version)
56
72
  end
57
73
 
58
74
  # Configures directories that will be used by the downloader
59
- def setup_directories(show, base_path = Dir.pwd)
60
- # Create base shows directory
61
- shows_dir = File.join(base_path, "shows")
62
- FileUtils.mkdir_p(shows_dir)
63
-
64
- # Create specific show directory
65
- show_dir = File.join(shows_dir, show.name)
75
+ def setup_directories(show, custom_path = nil)
76
+ if custom_path
77
+ # Custom path: use it directly
78
+ base_dir = custom_path
79
+ else
80
+ # Default: add shows subdirectory
81
+ base_dir = File.join(Dir.pwd, "shows")
82
+ end
83
+
84
+ FileUtils.mkdir_p(base_dir)
85
+
86
+ show_dir = File.join(base_dir, show.name)
66
87
  FileUtils.mkdir_p(show_dir)
67
88
 
68
- return show_dir
89
+ show_dir
90
+ rescue => e
91
+ @logger.error "❌ Directory creation failed: #{e.message}"
69
92
  end
70
- end
93
+ end
@@ -1,58 +1,80 @@
1
1
  # Object to handle Show data and the array of Track objects to be used in downloading.
2
2
  class Show
3
+ AUDIO_FORMATS = %w[mp3 flac ogg m4a].freeze
4
+
3
5
  attr_reader :name, :venue, :date, :location, :duration, :transferred_by, :tracks, :available_formats
4
6
 
5
- def initialize(download_url)
6
- @show_link = download_url
7
+ def initialize(show_id, format, logger: Logger.new($stdout))
8
+ @logger = logger
9
+ @show_id = show_id
10
+ @format = format
7
11
  @name = nil
8
12
  @date = nil
9
13
  @location = nil
10
14
  @venue = nil
11
15
  @duration = nil
12
16
  @transferred_by = nil
13
- @available_formats = []
17
+ @url = nil
14
18
  @tracks = nil
15
19
 
16
20
  set_show_info
17
21
  end
18
22
 
19
- # Returns whether or not a given format is available for this show
20
- def has_format?(requested_format)
21
- @tracks[0].has_format?(requested_format)
22
- end
23
-
24
23
  # Initializes a Downloader and passes track details
25
- def download_tracks(path, format)
26
- dl = Downloader.new(path, format)
24
+ def download_tracks(path)
25
+ dl = Downloader.new(path, @format, logger: @logger)
26
+ download_url = dl.download_url_for_show(@show_id)
27
+ succesful_downloads = 0
27
28
 
28
29
  @tracks.each do |track|
29
- track_link = track.url_for_format(format)
30
+ @logger.info "⬇️ Downloading #{track.pos} - #{track.title}..."
30
31
 
31
- dl.get(track.pos, track.name, track_link)
32
+ if dl.get(download_url, track)
33
+ succesful_downloads += 1
34
+ end
32
35
 
33
- puts "⚡️ #{track.pos} - #{track.name} downloaded successfully"
36
+ @logger.info "⚡️ #{track.pos} - #{track.title} downloaded successfully"
34
37
  end
38
+
39
+ @logger.info "✅ Downloaded #{succesful_downloads}/#{@tracks.length} tracks successfully!"
35
40
  end
36
41
 
37
42
  private
38
43
 
39
44
  # On initialization, show variables are extracted from the HTML data scraped by the Client.
40
45
  def set_show_info
41
- show_data = Client.new.scrape_show_info(@show_link)
42
-
46
+ show_data = Client.new.query_show_info(@show_id)
47
+
43
48
  @date = show_data[:date]
44
49
  @location = show_data[:location]
45
50
  @venue = show_data[:venue]
46
51
  @duration = show_data[:duration]
47
52
  @transferred_by = show_data[:transferred_by]
48
53
  @name = "#{show_data[:date]} - #{show_data[:venue]} - #{show_data[:location]}"
49
- @tracks = set_tracks(show_data[:tracks])
54
+ @tracks = set_tracks(show_data[:files])
55
+ @url = "https://archive.org/metadata/#{show_data[:dir]}/"
50
56
 
51
- puts "🌹💀 Downloading #{name}"
57
+ @logger.info "🌹💀 Show #{name} found!"
52
58
  end
53
59
 
54
60
  # Converts track lists to Track objects
55
- def set_tracks(track_data)
56
- @tracks = track_data.map { |track| Track.new(track) }
61
+ def set_tracks(files)
62
+ audio_files = files.select { |file| audio_file?(file) }
63
+ .select { |file| matches_format?(file, @format) }
64
+
65
+ if audio_files.empty?
66
+ @logger.error "❌ No #{@format} files found"
67
+ return []
68
+ end
69
+
70
+ @tracks = audio_files.map.with_index(1) { |track, index| Track.new(track, index) }
71
+ end
72
+
73
+ def audio_file?(file)
74
+ AUDIO_FORMATS.include?(File.extname(file["name"]).delete('.').downcase)
75
+ end
76
+
77
+ def matches_format?(file, format)
78
+ File.extname(file["name"]).delete('.').downcase == format
57
79
  end
58
80
  end
@@ -1,24 +1,9 @@
1
1
  class Track
2
- attr_reader :pos, :name, :links
2
+ attr_reader :pos, :title, :filename
3
3
 
4
- def initialize(track_data)
5
- @pos = track_data[:pos]
6
- @name = track_data[:name]
7
- @links = track_data[:links]
8
- end
9
-
10
- # Returns formats available for a given track via the links
11
- def available_formats
12
- @available_formats ||= links.map { |url| File.extname(url).delete('.') }
13
- end
14
-
15
- # Based on the format argument, returns one link containing that format
16
- def url_for_format(format)
17
- links.find { |url| url.end_with?(".#{format}") }
18
- end
19
-
20
- # Returns boolean if a format exists for this Track
21
- def has_format?(format)
22
- available_formats.include?(format)
4
+ def initialize(track_data, index)
5
+ @pos = track_data["track"] || index
6
+ @title = track_data["title"]
7
+ @filename = track_data["name"]
23
8
  end
24
9
  end
@@ -0,0 +1,3 @@
1
+ class DeadList
2
+ VERSION = '1.2.0'
3
+ end
data/lib/deadlist.rb CHANGED
@@ -1,29 +1,63 @@
1
1
  require 'httparty'
2
- require 'nokogiri'
3
2
  require 'open-uri'
4
3
  require 'pry'
4
+ require 'logger'
5
5
 
6
- require_relative 'deadlist/cli.rb'
6
+ require_relative 'deadlist/version'
7
+ require_relative 'deadlist/cli'
7
8
 
8
9
  # Main DeadList class.
9
10
  class DeadList
10
- HOSTNAME = 'https://www.archive.org/'
11
+ attr_reader :current_version
11
12
 
12
- def initialize
13
- @current_version = '1.0.1'
14
- @hostname = HOSTNAME
13
+ def initialize(logger: Logger.new($stdout))
14
+ @logger = logger
15
+ @logger.level = Logger::INFO # Default level
16
+ @logger.formatter = proc do |severity, datetime, progname, msg|
17
+ "#{msg}\n"
18
+ end
19
+ @current_version = VERSION
15
20
  end
16
21
 
17
- def run
18
- # Start a new CLI session
19
- # In future this could be abstracted to pass the show link vs all args, so a 'session' is started per show.
20
- session = CLI.new(@current_version, ARGV)
22
+ def run(argv = ARGV)
23
+ # Parse arguments to get show IDs and options
24
+ parsed_args = ArgumentParser.parse(argv, @current_version)
25
+ show_ids = parsed_args[:ids]
26
+
27
+ # Check for --quiet flag and adjust logger level
28
+ if parsed_args[:quiet]
29
+ @logger.level = Logger::ERROR
30
+ end
31
+
32
+ # Process each show
33
+ show_ids.each_with_index do |show_id, index|
34
+ @logger.info "📻 Processing show #{index + 1}/#{show_ids.count}: #{show_id}"
35
+
36
+ # Build arguments for this specific show
37
+ show_argv = ['--id', show_id, '--format', parsed_args[:format]]
38
+ show_argv += ['--directory', parsed_args[:directory]] if parsed_args[:directory]
39
+ show_argv += ['--quiet'] if parsed_args[:quiet]
40
+ show_argv += ['--dry-run'] if parsed_args[:dry_run]
41
+
42
+ # Create CLI session for this show
43
+ session = CLI.new(@current_version, show_argv, logger: @logger)
21
44
 
22
45
  # Scrape links and metadata for given show
23
- session.scrape_links
46
+ session.create_show
47
+
48
+ # Check if show has tracks in requested format
49
+ if session.show && session.show.tracks.empty?
50
+ @logger.error "❌ #{show_id} not available in #{parsed_args[:format]} format"
51
+ if session.show.available_formats && !session.show.available_formats.empty?
52
+ @logger.error " Available formats: #{session.show.available_formats.join(', ')}"
53
+ end
54
+ @logger.error " Skipping..."
55
+ next # Skip to next show
56
+ end
24
57
 
25
- # Create folder with show date and begin track downloads if format matches
58
+ # Create folder and begin track downloads
26
59
  session.download_show
60
+ end
27
61
  end
28
62
  end
29
63
 
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deadlist
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nazwr
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2025-12-14 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: httparty
@@ -23,20 +24,6 @@ dependencies:
23
24
  - - "~>"
24
25
  - !ruby/object:Gem::Version
25
26
  version: '0.21'
26
- - !ruby/object:Gem::Dependency
27
- name: nokogiri
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '1.10'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '1.10'
40
27
  description: A Ruby gem for downloading Grateful Dead concert recordings from the
41
28
  Internet Archive
42
29
  email: nathan@azotiwright.com
@@ -48,15 +35,17 @@ files:
48
35
  - bin/deadlist
49
36
  - lib/deadlist.rb
50
37
  - lib/deadlist/cli.rb
38
+ - lib/deadlist/cli/argument_parser.rb
51
39
  - lib/deadlist/cli/client.rb
52
40
  - lib/deadlist/cli/downloader.rb
53
41
  - lib/deadlist/models/show.rb
54
42
  - lib/deadlist/models/track.rb
55
- - lib/version.rb
43
+ - lib/deadlist/version.rb
56
44
  homepage: https://github.com/nazwr/deadlist
57
45
  licenses:
58
46
  - MIT
59
47
  metadata: {}
48
+ post_install_message:
60
49
  rdoc_options: []
61
50
  require_paths:
62
51
  - lib
@@ -71,7 +60,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
60
  - !ruby/object:Gem::Version
72
61
  version: '0'
73
62
  requirements: []
74
- rubygems_version: 3.6.7
63
+ rubygems_version: 3.4.19
64
+ signing_key:
75
65
  specification_version: 4
76
66
  summary: Download Grateful Dead shows from archive.org
77
67
  test_files: []
data/lib/version.rb DELETED
@@ -1,3 +0,0 @@
1
- module DeadList
2
- VERSION = '1.0.0'
3
- end