gamerom 0.1.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
- # 'frozen_string_literal' => true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
4
  require 'logger'
@@ -6,7 +6,7 @@ require 'rest-client'
6
6
  require 'mechanize'
7
7
 
8
8
  module Gamerom
9
- ROM_ROOT = ENV['ROM_ROOT'] || File.expand_path("~/.gamerom")
9
+ ROM_ROOT = ENV['ROM_ROOT'] || File.expand_path('~/.gamerom')
10
10
  CACHE_DIR = ENV['CACHE_DIR'] || "#{ROM_ROOT}/cache"
11
11
  GAME_DIR = ENV['GAME_DIR'] || "#{ROM_ROOT}/games"
12
12
  LOG_DIR = ENV['LOG_DIR'] || "#{ROM_ROOT}/logs"
data/lib/gamerom/game.rb CHANGED
@@ -1,47 +1,56 @@
1
- # 'frozen_string_literal' => true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
4
  require 'ostruct'
5
+ require 'yaml'
5
6
 
6
7
  module Gamerom
8
+ # Game - Represents a game ROM
7
9
  class Game < OpenStruct
8
- def filename
9
- "#{self.filepath}/#{File.read(self.state_filename)}"
10
+ def filenames
11
+ YAML.load_file(state_filename).map do |filename|
12
+ "#{filepath}/#{filename}"
13
+ end
10
14
  end
11
15
 
12
16
  def filepath
13
- "#{Gamerom::GAME_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}"
17
+ "#{Gamerom::GAME_DIR}/#{repo.name}/#{platform}/#{region}"
14
18
  end
15
19
 
16
20
  def install
17
- self.repo.install self do |filename|
18
- self.update_state filename
21
+ repo.install self do |filenames|
22
+ update_state filenames
19
23
  end
20
24
  end
21
25
 
22
26
  def installed?
23
- File.exists? self.state_filename
27
+ File.exist? state_filename
24
28
  end
25
29
 
26
30
  def state_filename
27
- "#{Gamerom::STATE_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}/#{self.id}"
31
+ "#{Gamerom::STATE_DIR}/#{repo.name}/#{platform}/#{region}/#{id}"
28
32
  end
29
33
 
30
34
  def to_s
31
- "#{self.id} - #{self.name} - #{self.region}#{self.installed? ? " (#{shell.set_color 'installed', :green})" : ''}"
35
+ install_status = ''
36
+ install_status = " (#{shell.set_color "installed", :green})" if installed?
37
+ tags = ''
38
+ tags = " - tags: #{tags.join(", ")}" if respond_to?(:tags) && !tags.empty?
39
+ "#{id} - #{name} - #{region}#{install_status}#{tags}"
32
40
  end
33
41
 
34
42
  def uninstall
35
- FileUtils.rm_rf self.filename
36
- FileUtils.rm_rf self.state_filename
43
+ FileUtils.rm_rf filenames
44
+ FileUtils.rm_rf state_filename
37
45
  end
38
46
 
39
- def update_state filename
40
- FileUtils.mkdir_p("#{Gamerom::STATE_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}")
41
- File.write(self.state_filename, filename)
47
+ def update_state(filenames)
48
+ FileUtils.mkdir_p("#{Gamerom::STATE_DIR}/#{repo.name}/#{platform}/#{region}")
49
+ File.write(state_filename, filenames.to_yaml)
42
50
  end
43
51
 
44
52
  private
53
+
45
54
  def shell
46
55
  @shell ||= Thor::Shell::Color.new
47
56
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamerom
4
+ # GameInfo - Extracts region and tags from game name
5
+ class GameInfo
6
+ REGIONS = {
7
+ '1' => 'Japan & Korea',
8
+ '4' => 'USA & Brazil - NTSC',
9
+ '5' => 'NTSC',
10
+ '8' => 'PAL',
11
+ 'A' => 'Australia',
12
+ 'As' => 'Asia',
13
+ 'B' => 'Brazil',
14
+ 'C' => 'Canada',
15
+ 'Ch' => 'China',
16
+ 'D' => 'Netherlands (Dutch)',
17
+ 'E' => 'Europe',
18
+ 'F' => 'France',
19
+ 'FC' => 'French Canadian',
20
+ 'FN' => 'Finland',
21
+ 'G' => 'Germany',
22
+ 'GR' => 'Greece',
23
+ 'H' => 'Holland',
24
+ 'HK' => 'Hong Kong',
25
+ 'I' => 'Italy',
26
+ 'J' => 'Japan',
27
+ 'JUE' => 'Japan & USA & Europe',
28
+ 'K' => 'Korea',
29
+ 'Nl' => 'Netherlands',
30
+ 'NL' => 'Netherlands',
31
+ 'No' => 'Norway',
32
+ 'PD' => 'Public Domain',
33
+ 'R' => 'Russia',
34
+ 'S' => 'Spain',
35
+ 'Sw' => 'Sweden',
36
+ 'SW' => 'Sweden',
37
+ 'U' => 'USA',
38
+ 'UK' => 'England',
39
+ 'Unk' => 'Unknown Country',
40
+ 'Unl' => 'Unlicensed',
41
+ 'PAL' => 'PAL regions (Australia, Europe)',
42
+ 'NTSC' => 'NTSC regions (Japan, USA, Latin America)',
43
+ }.freeze
44
+
45
+ TAGS = {
46
+ '!' => :good,
47
+ '!p' => :pending,
48
+ 'a' => :alternate,
49
+ 'b' => :bad,
50
+ 'BF' => :bung,
51
+ 'c' => :checksum,
52
+ 'C' => :color,
53
+ 'f' => :fixed,
54
+ 'h' => :hack,
55
+ 'J' => :japanese_translation,
56
+ 'o' => :overdump,
57
+ 'p' => :pirate,
58
+ 'PC10' => :pc10,
59
+ 'S' => :super,
60
+ 'T-' => :old_translation,
61
+ 't' => :trained,
62
+ 'T+' => :newer_translation,
63
+ 'VS' => :vs,
64
+ 'x' => :bad_checksum,
65
+ }.freeze
66
+
67
+ attr_reader :name
68
+
69
+ def initialize(name)
70
+ @name = name
71
+ end
72
+
73
+ def region
74
+ identifiers = @name.scan(/\((?<region>[A-Za-z0-9]+)\)/).flatten
75
+ region_id = identifiers.find { |i| REGIONS.include? i }
76
+ if region_id
77
+ REGIONS[region_id]
78
+ else
79
+ 'USA'
80
+ end
81
+ end
82
+
83
+ def tags
84
+ tags = []
85
+ codes = @name.scan(/\[(?<code>[^\]]+)\]/).flatten
86
+ codes.each do |code|
87
+ code = Regexp.last_match(1) if code.match /^(?<code>[abcfhop])[0-9]*/
88
+ tags << TAGS[code] if TAGS.include?(code)
89
+ end
90
+ tags
91
+ end
92
+ end
93
+ end
data/lib/gamerom/repo.rb CHANGED
@@ -1,4 +1,4 @@
1
- # 'frozen_string_literal' => true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
4
 
@@ -6,30 +6,34 @@ REPOSITORIES = Dir["#{File.dirname(__FILE__)}/repo_adapters/*"].map do |file|
6
6
  File.basename(file, '.rb')
7
7
  end
8
8
 
9
+ require_relative 'repo_adapter'
9
10
  REPOSITORIES.each do |repo|
10
11
  require_relative "repo_adapters/#{repo}"
11
12
  end
12
13
 
13
14
  module Gamerom
15
+ # Repo - Represents a game ROM repository
14
16
  class Repo
17
+ attr_reader :name
18
+
15
19
  def self.list
16
20
  REPOSITORIES.map do |repo|
17
- self.new repo
21
+ new repo
18
22
  end
19
23
  end
20
24
 
21
- def initialize name
25
+ def initialize(name)
22
26
  @name = name
23
27
  @repo = Gamerom::RepoAdapters.const_get(name.capitalize)
24
28
  end
25
29
 
26
- def install game, &block
30
+ def install(game, &block)
27
31
  @repo.install game, &block
28
32
  end
29
33
 
30
- def find platform, game_identifier
34
+ def find(platform, game_identifier)
31
35
  games(platform).find do |game|
32
- if Float(game_identifier, exception: false)
36
+ if Integer(game_identifier, exception: false)
33
37
  game.id == game_identifier.to_i
34
38
  else
35
39
  game.name.downcase == game_identifier.downcase
@@ -37,47 +41,42 @@ module Gamerom
37
41
  end
38
42
  end
39
43
 
40
- def games platform, options={}
44
+ def games(platform, options = {})
41
45
  platform_database = "#{Gamerom::CACHE_DIR}/#{@name}/#{platform}.yml"
42
- update_database platform unless File.exists? platform_database
43
- games = YAML.load_file(platform_database).map { |game|
46
+ update_database platform unless File.exist? platform_database
47
+ games = YAML.load_file(platform_database).map do |game|
44
48
  Game.new(game.merge(platform: platform, repo: self))
45
- }
49
+ end
46
50
 
47
- if !options[:region].nil?
48
- games = games.select { |game|
49
- options[:region].nil? || game.region == options[:region]
50
- }
51
+ unless options[:region].nil?
52
+ games.select! do |game|
53
+ game.region == options[:region]
54
+ end
51
55
  end
52
56
 
53
- if !options[:keyword].nil?
54
- games = games.select { |game|
57
+ unless options[:keyword].nil?
58
+ games.select! do |game|
55
59
  game.name =~ /#{options[:keyword]}/i
56
- }
60
+ end
57
61
  end
58
62
 
59
63
  games
60
64
  end
61
65
 
62
- def name
63
- @name
64
- end
65
-
66
66
  def platforms
67
67
  @repo.platforms
68
68
  end
69
69
 
70
- def regions platform
71
- games(platform).map { |game| game.region }.sort.uniq
70
+ def regions(platform)
71
+ games(platform).map(&:region).sort.uniq
72
72
  end
73
73
 
74
74
  def to_s
75
75
  @name
76
76
  end
77
77
 
78
- def update_database platform
78
+ def update_database(platform)
79
79
  games = @repo.games platform
80
- puts
81
80
  FileUtils.mkdir_p("#{Gamerom::CACHE_DIR}/#{@name}")
82
81
  File.write("#{Gamerom::CACHE_DIR}/#{@name}/#{platform}.yml", games.to_yaml)
83
82
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'rest-client'
5
+
6
+ module Gamerom
7
+ # RepoAdapter - Common adapter methods
8
+ module RepoAdapter
9
+ def nokogiri_get(url)
10
+ Nokogiri::HTML(RestClient.get(url))
11
+ end
12
+
13
+ def games(platform)
14
+ games = []
15
+ progress_bar = ProgressBar.new(platform, sections.count)
16
+
17
+ extract_games(platform) do |section_games, section_index|
18
+ games.append(*section_games)
19
+ progress_bar.set(section_index + 1)
20
+ end
21
+
22
+ progress_bar.finish
23
+ games
24
+ end
25
+ end
26
+ end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'nokogiri'
4
- require 'rest-client'
3
+ require 'mechanize'
4
+ require 'mechanize/progressbar'
5
+ require 'mechanizeprogress'
5
6
 
6
7
  module Gamerom
7
8
  module RepoAdapters
9
+ # Coolrom - An adapter for the CoolROM repository website
8
10
  class Coolrom
11
+ extend Gamerom::RepoAdapter
12
+
9
13
  PLATFORM = {
10
14
  'atari2600' => 'Atari 2600',
11
15
  'atari5200' => 'Atari 5200',
@@ -30,48 +34,51 @@ module Gamerom
30
34
  'psx' => 'Sony Playstation',
31
35
  'ps2' => 'Sony Playstation 2',
32
36
  'psp' => 'Sony Playstation Portable',
33
- }
37
+ }.freeze
34
38
 
35
39
  def self.platforms
36
40
  PLATFORM
37
41
  end
38
42
 
39
- def self.games(platform)
40
- games = []
41
- sections = ('a'..'z').to_a.unshift("0")
43
+ def self.sections
44
+ ('a'..'z').to_a.unshift('0')
45
+ end
42
46
 
43
- sections.each do |section|
44
- print "#{section} "
45
- page = Nokogiri::HTML(RestClient.get("https://coolrom.com.au/roms/#{platform}/#{section}/"))
46
- regions = page.css('input.region').map { |i| i["name"] }
47
+ def self.extract_games(platform)
48
+ sections.each_with_index do |section, index|
49
+ page = nokogiri_get("https://coolrom.com.au/roms/#{platform}/#{section}/")
50
+ regions = page.css('input.region').map { |i| i['name'] }
47
51
  regions.each do |region|
48
- games.append *page.css("div.#{region} a").map { |game|
49
- {
50
- id: game['href'].split('/')[3].to_i,
51
- name: game.text,
52
- region: region,
53
- }
54
- }
52
+ game_links = page.css("div.#{region} a")
53
+ yield game_links.map { |game_link| game(game_link, region) }, index
55
54
  end
56
55
  end
57
- games
56
+ end
57
+
58
+ def self.game(game_link, region)
59
+ {
60
+ id: game_link['href'].split('/')[3].to_i,
61
+ name: game_link.text,
62
+ region: region,
63
+ }
58
64
  end
59
65
 
60
66
  def self.install(game)
61
- response = RestClient::Request.execute(
62
- method: :get,
63
- url: "https://coolrom.com.au/downloader.php?id=#{game.id}",
64
- headers: {
65
- 'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
66
- },
67
- raw_response: true,
68
- )
69
- if response.code == 200
70
- filename = response.headers[:content_disposition].split('; ')[1].split('"')[1]
71
- FileUtils.mkdir_p(game.filepath)
72
- FileUtils.cp(response.file.path, "#{game.filepath}/#{filename}")
73
- yield filename
67
+ agent = Mechanize.new
68
+ agent.pluggable_parser.default = Mechanize::Download
69
+ agent.user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'
70
+
71
+ response = nil
72
+ agent.progressbar do
73
+ response = agent.get("https://coolrom.com.au/downloader.php?id=#{game.id}")
74
74
  end
75
+
76
+ return unless response.code.to_i == 200
77
+
78
+ filename = response.filename
79
+ FileUtils.mkdir_p(game.filepath)
80
+ response.save!("#{game.filepath}/#{filename}")
81
+ yield [filename]
75
82
  end
76
83
  end
77
84
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'mechanize'
5
+ require 'mechanize/progressbar'
6
+ require 'mechanizeprogress'
7
+
8
+ module Gamerom
9
+ module RepoAdapters
10
+ # Romnation - An adapter for the ROMNation repository website
11
+ class Romnation
12
+ extend Gamerom::RepoAdapter
13
+
14
+ PLATFORM = {
15
+ 'amstrad' => 'Amstrad',
16
+ 'atari2600' => 'Atari 2600',
17
+ 'atari5200' => 'Atari 5200',
18
+ 'atari7800' => 'Atari 7800',
19
+ 'atarijaguar' => 'Atari Jaguar',
20
+ 'atarilynx' => 'Atari Lynx',
21
+ 'colecovision' => 'ColecoVision',
22
+ 'commodore64' => 'Commodore 64',
23
+ 'gamegear' => 'Game Gear',
24
+ 'gb' => 'Game Boy',
25
+ 'gbc' => 'Game Boy Color',
26
+ 'gcdvectrex' => 'Vectrex',
27
+ 'genesis' => 'Genesis',
28
+ 'intellivision' => 'Intellivision',
29
+ 'mame' => 'MAME',
30
+ 'msx1' => 'MSX',
31
+ 'msx2' => 'MSX2',
32
+ 'mtx' => 'MTX',
33
+ 'n64' => 'N64',
34
+ 'neogeocd' => 'Neo Geo CD',
35
+ 'neogeopocket' => 'Neo Geo Pocket',
36
+ 'nes' => 'NES',
37
+ 'oric' => 'Oric',
38
+ 'pce' => 'PC Engine',
39
+ 'radioshackcolorcomputer' => 'TRS-80',
40
+ 'samcoupe' => 'SAM Coupé',
41
+ 'segacd' => 'Sega CD',
42
+ 'segamastersystem' => 'Master System',
43
+ 'snes' => 'SNES',
44
+ 'thompsonmo5' => 'Thomson MO5',
45
+ 'virtualboy' => 'Virtual Boy',
46
+ 'watara' => 'Watara Supervision',
47
+ 'wonderswan' => 'WonderSwan',
48
+ }.freeze
49
+
50
+ def self.platforms
51
+ PLATFORM
52
+ end
53
+
54
+ def self.sections
55
+ ('a'..'z').to_a.unshift('0')
56
+ end
57
+
58
+ def self.extract_games(platform)
59
+ sections.each_with_index do |section, index|
60
+ pages = extract_pages(platform, section)
61
+ yield extract_games_from_section_pages(platform, section, pages), index
62
+ end
63
+ end
64
+
65
+ def self.extract_pages(platform, section)
66
+ page = nokogiri_get("https://www.romnation.net/srv/roms/#{platform}/#{section}/sort-title.html")
67
+ pages = ['1']
68
+ pages = page.css('.pagination').first.css('a').map(&:text).map(&:strip).reject(&:empty?) unless page.css('.pagination').empty?
69
+ pages
70
+ end
71
+
72
+ def self.extract_games_from_section_pages(platform, section, pages)
73
+ pages.reduce([]) do |section_games, p|
74
+ page = nokogiri_get("https://www.romnation.net/srv/roms/#{platform}/#{section}/page-#{p}_sort-title.html")
75
+ games_links = page.css('table.listings td.title a')
76
+ section_games.append(*games_links.map { |game_link| game(game_link) })
77
+ end
78
+ end
79
+
80
+ def self.game(game_link)
81
+ game_info = GameInfo.new(game_link.text)
82
+ {
83
+ id: game_link['href'].split('/')[3].to_i,
84
+ name: game_link.text,
85
+ region: game_info.region,
86
+ tags: game_info.tags,
87
+ }
88
+ end
89
+
90
+ def self.install(game)
91
+ agent = Mechanize.new
92
+ agent.pluggable_parser.default = Mechanize::Download
93
+ agent.user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'
94
+ page = agent.get("https://www.romnation.net/download/rom/#{game.id}")
95
+
96
+ response = nil
97
+ agent.progressbar do
98
+ response = page.link_with(text: 'Download This Rom').click
99
+ end
100
+
101
+ return unless response.code.to_i == 200
102
+
103
+ filename = CGI.unescape(response.filename.split('_host=').first)
104
+ FileUtils.mkdir_p(game.filepath)
105
+ response.save!("#{game.filepath}/#{filename}")
106
+ yield [filename]
107
+ end
108
+ end
109
+ end
110
+ end