xxx_rename 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.
- checksums.yaml +7 -0
- data/.github/workflows/codeql-analysis.yml +42 -0
- data/.github/workflows/ruby.yml +44 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +174 -0
- data/README.md +319 -0
- data/Rakefile +12 -0
- data/bin/console +23 -0
- data/bin/install +22 -0
- data/bin/setup +8 -0
- data/codecov.yml +2 -0
- data/docs/DEVELOPMENT.md +42 -0
- data/exe/xxx_rename +12 -0
- data/lib/xxx_rename/actions/base_action.rb +20 -0
- data/lib/xxx_rename/actions/log_new_filename.rb +40 -0
- data/lib/xxx_rename/actions/resolver.rb +32 -0
- data/lib/xxx_rename/actions/stash_app_post_movie.rb +62 -0
- data/lib/xxx_rename/actors_helper.rb +117 -0
- data/lib/xxx_rename/cli.rb +211 -0
- data/lib/xxx_rename/client.rb +110 -0
- data/lib/xxx_rename/constants.rb +96 -0
- data/lib/xxx_rename/contract/config_contract.rb +241 -0
- data/lib/xxx_rename/contract/config_generator.rb +207 -0
- data/lib/xxx_rename/contract/file_rename_op_contract.rb +54 -0
- data/lib/xxx_rename/contract/types.rb +10 -0
- data/lib/xxx_rename/core_extensions/string.rb +39 -0
- data/lib/xxx_rename/data/base.rb +34 -0
- data/lib/xxx_rename/data/config.rb +97 -0
- data/lib/xxx_rename/data/file_rename_op.rb +42 -0
- data/lib/xxx_rename/data/file_rename_op_datastore.rb +111 -0
- data/lib/xxx_rename/data/naughty_america_database.rb +22 -0
- data/lib/xxx_rename/data/query_interface.rb +78 -0
- data/lib/xxx_rename/data/scene_data.rb +71 -0
- data/lib/xxx_rename/data/scene_datastore.rb +401 -0
- data/lib/xxx_rename/data/site_config.rb +84 -0
- data/lib/xxx_rename/data/types.rb +13 -0
- data/lib/xxx_rename/errors.rb +28 -0
- data/lib/xxx_rename/file_scanner.rb +49 -0
- data/lib/xxx_rename/file_utilities.rb +38 -0
- data/lib/xxx_rename/filename_generator.rb +173 -0
- data/lib/xxx_rename/integrations/base.rb +20 -0
- data/lib/xxx_rename/integrations/stash_app.rb +316 -0
- data/lib/xxx_rename/log.rb +26 -0
- data/lib/xxx_rename/migration_client.rb +139 -0
- data/lib/xxx_rename/processed_file.rb +203 -0
- data/lib/xxx_rename/search.rb +166 -0
- data/lib/xxx_rename/site_client_matcher.rb +299 -0
- data/lib/xxx_rename/site_clients/adult_time.rb +31 -0
- data/lib/xxx_rename/site_clients/algolia_common.rb +48 -0
- data/lib/xxx_rename/site_clients/algolia_v2.rb +181 -0
- data/lib/xxx_rename/site_clients/babes.rb +15 -0
- data/lib/xxx_rename/site_clients/base.rb +61 -0
- data/lib/xxx_rename/site_clients/blacked.rb +12 -0
- data/lib/xxx_rename/site_clients/blacked_raw.rb +12 -0
- data/lib/xxx_rename/site_clients/brazzers.rb +15 -0
- data/lib/xxx_rename/site_clients/configuration.rb +55 -0
- data/lib/xxx_rename/site_clients/digital_playground.rb +15 -0
- data/lib/xxx_rename/site_clients/elegant_angel.rb +168 -0
- data/lib/xxx_rename/site_clients/errors.rb +103 -0
- data/lib/xxx_rename/site_clients/evil_angel.rb +59 -0
- data/lib/xxx_rename/site_clients/goodporn.rb +109 -0
- data/lib/xxx_rename/site_clients/jules_jordan.rb +22 -0
- data/lib/xxx_rename/site_clients/jules_jordan_media.rb +175 -0
- data/lib/xxx_rename/site_clients/manuel_ferrara.rb +24 -0
- data/lib/xxx_rename/site_clients/mg_premium.rb +247 -0
- data/lib/xxx_rename/site_clients/mofos.rb +15 -0
- data/lib/xxx_rename/site_clients/naughty_america.rb +272 -0
- data/lib/xxx_rename/site_clients/nfbusty.rb +84 -0
- data/lib/xxx_rename/site_clients/query_generator/base.rb +89 -0
- data/lib/xxx_rename/site_clients/query_generator/evil_angel.rb +36 -0
- data/lib/xxx_rename/site_clients/query_generator/goodporn.rb +27 -0
- data/lib/xxx_rename/site_clients/query_generator/mg_premium.rb +26 -0
- data/lib/xxx_rename/site_clients/query_generator/naughty_america.rb +24 -0
- data/lib/xxx_rename/site_clients/query_generator/stash_db.rb +21 -0
- data/lib/xxx_rename/site_clients/query_generator/vixen.rb +27 -0
- data/lib/xxx_rename/site_clients/query_generator/whale.rb +39 -0
- data/lib/xxx_rename/site_clients/reality_kings.rb +14 -0
- data/lib/xxx_rename/site_clients/stash_db.rb +257 -0
- data/lib/xxx_rename/site_clients/tushy.rb +12 -0
- data/lib/xxx_rename/site_clients/tushy_raw.rb +12 -0
- data/lib/xxx_rename/site_clients/twistys.rb +15 -0
- data/lib/xxx_rename/site_clients/vixen.rb +12 -0
- data/lib/xxx_rename/site_clients/vixen_media.rb +130 -0
- data/lib/xxx_rename/site_clients/whale.rb +106 -0
- data/lib/xxx_rename/site_clients/wicked.rb +52 -0
- data/lib/xxx_rename/site_clients/x_empire.rb +51 -0
- data/lib/xxx_rename/site_clients/zero_tolerance.rb +36 -0
- data/lib/xxx_rename/utils.rb +81 -0
- data/lib/xxx_rename/version.rb +5 -0
- data/lib/xxx_rename.rb +60 -0
- data/output.png +0 -0
- data/xxx_rename.gemspec +42 -0
- metadata +411 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xxx_rename/site_clients/query_generator/evil_angel"
|
4
|
+
require "xxx_rename/site_clients/algolia_v2"
|
5
|
+
|
6
|
+
module XxxRename
|
7
|
+
module SiteClients
|
8
|
+
class EvilAngel < AlgoliaV2
|
9
|
+
SCENES_INDEX_NAME = "all_scenes"
|
10
|
+
MOVIES_INDEX_NAME = "all_movies"
|
11
|
+
ACTORS_INDEX_NAME = "all_actors"
|
12
|
+
|
13
|
+
CDN_BASE_URL = "https://transform.gammacdn.com"
|
14
|
+
|
15
|
+
site_client_name :evil_angel
|
16
|
+
|
17
|
+
def initialize(config)
|
18
|
+
@site_url = "https://www.evilangel.com"
|
19
|
+
super(config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def search(filename)
|
23
|
+
match = SiteClients::QueryGenerator::EvilAngel.generate(filename, source_format)
|
24
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) if match.nil?
|
25
|
+
|
26
|
+
if match.processed
|
27
|
+
processed_scene_details(match)
|
28
|
+
else
|
29
|
+
unprocessed_scene_details(match)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def actor_details(actor)
|
34
|
+
response = fetch_actor_from_api(actor)
|
35
|
+
response&.select { |x| match? actor, x[:name] }
|
36
|
+
&.first
|
37
|
+
&.slice(:name, :gender)
|
38
|
+
&.transform_keys(&:to_s) # Backwards compatibility. Keys should be strings.
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def processed_scene_details(match)
|
44
|
+
resp = fetch_scenes_from_api(match.title)
|
45
|
+
|
46
|
+
find_matched_scene!(resp, match)
|
47
|
+
end
|
48
|
+
|
49
|
+
def unprocessed_scene_details(match)
|
50
|
+
combined = [
|
51
|
+
fetch_scenes_from_api(match.title),
|
52
|
+
fetch_scenes_from_api("#{match.title} Scene #{match.index}")
|
53
|
+
].reduce([], :concat)
|
54
|
+
|
55
|
+
find_matched_scene!(combined, match)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "xxx_rename/site_clients/query_generator/goodporn"
|
6
|
+
|
7
|
+
module XxxRename
|
8
|
+
module SiteClients
|
9
|
+
class Goodporn < Base
|
10
|
+
include HTTParty
|
11
|
+
|
12
|
+
base_uri "https://goodporn.to"
|
13
|
+
site_client_name :goodporn
|
14
|
+
|
15
|
+
# rubocop:disable Style/RegexpLiteral
|
16
|
+
HEADLINE_REGEX = /
|
17
|
+
(?<collection>[^-]*) # Collection
|
18
|
+
\s-\s # Separator
|
19
|
+
(?<scene_title>.*) # Scene Title
|
20
|
+
\s-\s # Separator
|
21
|
+
(?<date_released>\d\d\/\d\d\/\d\d\d\d) # Release Date
|
22
|
+
/x.freeze
|
23
|
+
# rubocop:enable Style/RegexpLiteral
|
24
|
+
|
25
|
+
# rubocop:disable Style/RegexpLiteral
|
26
|
+
BRAZZERS_LIVE_HEADLINE_REGEX = /
|
27
|
+
-\s # Prefix only for Brazzers Live
|
28
|
+
(?<collection>[^-]*) # Collection
|
29
|
+
:\s? # Scene Separator
|
30
|
+
(?<scene_title>[^-]*) # Scene Title
|
31
|
+
\s-\s # Separator
|
32
|
+
(?<date_released>\d\d\/\d\d\/\d\d\d\d) # Release Date
|
33
|
+
/x.freeze
|
34
|
+
# rubocop:enable Style/RegexpLiteral
|
35
|
+
|
36
|
+
# @param [String] filename
|
37
|
+
# @return [XxxRename::Data::SceneData]
|
38
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
39
|
+
def search(filename, **_opts)
|
40
|
+
pattern = pattern(filename) || raise(Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename))
|
41
|
+
|
42
|
+
doc = Nokogiri::HTML request_search_html(pattern.gsub("-", " "))
|
43
|
+
link = search_result_links(doc).select { |url| url.include? pattern }.first
|
44
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) if link.nil?
|
45
|
+
|
46
|
+
scene = Nokogiri::HTML request_video_html(link)
|
47
|
+
metadata_tags = scene.css("#tab_video_info").css(".info").children.select { |x| x.children.length > 1 }
|
48
|
+
actors_hash = actors_hash(actors(metadata_tags))
|
49
|
+
match = headline(scene).match(HEADLINE_REGEX) || headline(scene).match(BRAZZERS_LIVE_HEADLINE_REGEX)
|
50
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, filename) if match.nil? || actors_hash.nil?
|
51
|
+
|
52
|
+
Data::SceneData.new(
|
53
|
+
{
|
54
|
+
collection: match[:collection],
|
55
|
+
collection_tag: "GP",
|
56
|
+
title: match[:scene_title],
|
57
|
+
id: nil,
|
58
|
+
date_released: Time.strptime(match[:date_released], "%m/%d/%Y")
|
59
|
+
}.merge(actors_hash)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @param [Hash] actors_hash
|
67
|
+
# @return [Array]
|
68
|
+
def female_actors(actors_hash)
|
69
|
+
actors_hash[:female_actors].nil? ? [] : actors_hash[:female_actors]
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param [Hash] actors_hash
|
73
|
+
# @return [Array]
|
74
|
+
def male_actors(actors_hash)
|
75
|
+
actors_hash[:male_actors].nil? ? [] : actors_hash[:male_actors]
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param [String] filename
|
79
|
+
# @return [nil, String]
|
80
|
+
def pattern(filename)
|
81
|
+
SiteClients::QueryGenerator::Goodporn.generate(filename, source_format)
|
82
|
+
end
|
83
|
+
|
84
|
+
def headline(doc)
|
85
|
+
doc.css(".headline").css("h1").text.strip
|
86
|
+
end
|
87
|
+
|
88
|
+
def search_result_links(doc)
|
89
|
+
search_results = doc.css("#list_videos_videos_list_search_result_items").children
|
90
|
+
search_results = search_results.select { |x| x.children.length == 3 }
|
91
|
+
search_results.map { |child| child.children[1]["href"] }
|
92
|
+
end
|
93
|
+
|
94
|
+
def actors(doc)
|
95
|
+
actors = doc.select { |x| x.text.include? "Models" }.first&.children&.css("a")&.map { |x| x.text.strip }
|
96
|
+
XxxRename.logger.debug "Unable to fetch actor data".colorize(:red) if actors.nil?
|
97
|
+
actors
|
98
|
+
end
|
99
|
+
|
100
|
+
def request_search_html(query)
|
101
|
+
self.class.get("/search/#{CGI.escape(query)}").body
|
102
|
+
end
|
103
|
+
|
104
|
+
def request_video_html(link)
|
105
|
+
HTTParty.get(link).body
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xxx_rename/site_clients/jules_jordan_media"
|
4
|
+
require "xxx_rename/site_clients/query_generator/base"
|
5
|
+
|
6
|
+
module XxxRename
|
7
|
+
module SiteClients
|
8
|
+
class JulesJordan < JulesJordanMedia
|
9
|
+
base_uri "https://www.julesjordan.com"
|
10
|
+
|
11
|
+
site_client_name :jules_jordan
|
12
|
+
|
13
|
+
COLLECTION = "Jules Jordan"
|
14
|
+
|
15
|
+
def search(filename)
|
16
|
+
refresh_datastore(1) if datastore_refresh_required?
|
17
|
+
match = SiteClients::QueryGenerator::Base.generic_generate(filename, source_format)
|
18
|
+
lookup_in_datastore!(match)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "xxx_rename/site_clients/base"
|
6
|
+
require "xxx_rename/file_utilities"
|
7
|
+
require "xxx_rename/constants"
|
8
|
+
|
9
|
+
module XxxRename
|
10
|
+
module SiteClients
|
11
|
+
class JulesJordanMedia < Base
|
12
|
+
include FileUtilities
|
13
|
+
include HTTParty
|
14
|
+
|
15
|
+
headers Constants::DEFAULT_HEADERS
|
16
|
+
|
17
|
+
SCENES_ENDPOINT_TRIAL = "/trial/categories/movies_$page$_d.html"
|
18
|
+
|
19
|
+
def datastore_refresh_required?
|
20
|
+
if config.force_refresh_datastore
|
21
|
+
XxxRename.logger.info "#{"[FORCE REFRESH]".colorize(:green)} #{self.class.name}"
|
22
|
+
true
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def refresh_datastore(page = 1)
|
29
|
+
return if all_scenes_processed?
|
30
|
+
|
31
|
+
scenes = get_scenes_from_page(page)
|
32
|
+
if scenes.empty?
|
33
|
+
XxxRename.logger.info "#{"[DATASTORE REFRESH COMPLETE]".colorize(:green)} #{self.class.site_client_name}"
|
34
|
+
@all_scenes_processed = true
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
|
38
|
+
scenes.map { |scene_data| site_client_datastore.create!(scene_data, force: true) }
|
39
|
+
refresh_datastore(page + 1)
|
40
|
+
end
|
41
|
+
|
42
|
+
def all_scenes_processed?
|
43
|
+
all_scenes_processed
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def lookup_in_datastore!(match)
|
49
|
+
msg = "requires both movie title(%collection) and scene title(%title)"
|
50
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, msg) if match.nil?
|
51
|
+
|
52
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, msg) unless collection_key(match) && match.title.presence
|
53
|
+
|
54
|
+
msg = "collection should be either 'julesjordan' or 'manuelferrara' (case-insensitive, spaces allowed)"
|
55
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, msg) unless valid_collection?(collection_key(match))
|
56
|
+
|
57
|
+
index_key = site_client_datastore.generate_lookup_key(collection_key(match), match.title)
|
58
|
+
scene_data_key = site_client_datastore.find_by_key?(index_key)
|
59
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, index_key) if scene_data_key.nil?
|
60
|
+
|
61
|
+
scene_data = site_client_datastore.find_by_key?(scene_data_key)
|
62
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, scene_data_key) if scene_data.nil?
|
63
|
+
|
64
|
+
scene_data
|
65
|
+
end
|
66
|
+
|
67
|
+
def collection_key(match)
|
68
|
+
config.override_site.presence || match.collection.presence
|
69
|
+
end
|
70
|
+
|
71
|
+
def valid_collection?(collection)
|
72
|
+
match?(collection, self.class::COLLECTION)
|
73
|
+
end
|
74
|
+
|
75
|
+
def all_scenes_processed
|
76
|
+
@all_scenes_processed ||= false
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_scenes_from_page(page)
|
80
|
+
XxxRename.logger.info "#{"[PROCESSING PAGE]".colorize(:green)} #{page}"
|
81
|
+
url = SCENES_ENDPOINT_TRIAL.gsub("$page$", page.to_s)
|
82
|
+
doc = doc(url)
|
83
|
+
|
84
|
+
all_scenes_urls(doc).map do |link|
|
85
|
+
scene_data = make_scene_data(link)
|
86
|
+
XxxRename.logger.info "#{"[PROCESSING SCENE]".colorize(:green)} #{scene_data.title}"
|
87
|
+
scene_data
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def all_scenes_urls(doc)
|
92
|
+
doc.css(".category_listing_wrapper_updates a")
|
93
|
+
.map { |x| x["href"] }
|
94
|
+
.select { |x| x.include?("/trial/scenes/") }
|
95
|
+
.uniq
|
96
|
+
end
|
97
|
+
|
98
|
+
def make_scene_data(link)
|
99
|
+
endpoint = link.gsub(self.class.base_uri, "")
|
100
|
+
doc = doc(endpoint)
|
101
|
+
actors_hash = actors_hash(actors(doc))
|
102
|
+
movie_hash = movie_hash(doc)
|
103
|
+
hash = {}.tap do |h|
|
104
|
+
h[:collection] = self.class::COLLECTION
|
105
|
+
h[:collection_tag] = site_config.collection_tag
|
106
|
+
h[:title] = title(doc)
|
107
|
+
h[:id] = nil
|
108
|
+
h[:date_released] = date_released(doc)
|
109
|
+
h[:movie] = movie_hash unless movie_hash.nil?
|
110
|
+
end.merge(actors_hash)
|
111
|
+
Data::SceneData.new(hash)
|
112
|
+
end
|
113
|
+
|
114
|
+
def title(doc)
|
115
|
+
doc.css(".title_bar_hilite").text.strip
|
116
|
+
end
|
117
|
+
|
118
|
+
def date_released(doc)
|
119
|
+
txt = doc.css(".backgroundcolor_info .update_date").text.strip
|
120
|
+
Time.strptime(txt, "%m/%d/%Y")
|
121
|
+
end
|
122
|
+
|
123
|
+
def female_actors(actors)
|
124
|
+
actors.select { |x| ActorsHelper.instance.female? x }
|
125
|
+
end
|
126
|
+
|
127
|
+
def actors(doc)
|
128
|
+
doc.css(".backgroundcolor_info .update_models a")
|
129
|
+
.map(&:text)
|
130
|
+
.map(&:strip)
|
131
|
+
end
|
132
|
+
|
133
|
+
def movie_hash(doc)
|
134
|
+
movie_url, movie_name = movie_details(doc)
|
135
|
+
return nil if movie_url.nil?
|
136
|
+
|
137
|
+
endpoint = movie_url.gsub(self.class.base_uri, "")
|
138
|
+
if processed_movies.key?(endpoint)
|
139
|
+
XxxRename.logger.debug "[MOVIE ALREADY PROCESSED] #{movie_name}"
|
140
|
+
return processed_movies[endpoint]
|
141
|
+
end
|
142
|
+
|
143
|
+
XxxRename.logger.info "#{"[PROCESSING MOVIE]".colorize(:green)} #{movie_name}"
|
144
|
+
movie_doc = doc(endpoint)
|
145
|
+
h = {
|
146
|
+
name: movie_name,
|
147
|
+
url: movie_url,
|
148
|
+
front_image: movie_doc.at("//div[@class=\"front\"]/a/img/@src0_3x").value,
|
149
|
+
back_image: movie_doc.at("//div[@class=\"back\"]/a/img/@src0_3x").value,
|
150
|
+
studio: self.class::COLLECTION
|
151
|
+
}
|
152
|
+
processed_movies[endpoint] = h
|
153
|
+
h
|
154
|
+
end
|
155
|
+
|
156
|
+
def movie_details(doc)
|
157
|
+
node = doc.css(".backgroundcolor_info .update_dvds a")
|
158
|
+
return [nil, nil] if node.nil?
|
159
|
+
|
160
|
+
url = node.map { |x| x["href"] }.first
|
161
|
+
name = node.map(&:text).map(&:strip)&.first
|
162
|
+
[url, name]
|
163
|
+
end
|
164
|
+
|
165
|
+
def doc(endpoint)
|
166
|
+
res = handle_response!(return_raw: true) { self.class.get(endpoint) }
|
167
|
+
Nokogiri::HTML res.parsed_response
|
168
|
+
end
|
169
|
+
|
170
|
+
def processed_movies
|
171
|
+
@processed_movies ||= {}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xxx_rename/site_clients/jules_jordan_media"
|
4
|
+
require "xxx_rename/site_clients/query_generator/base"
|
5
|
+
|
6
|
+
module XxxRename
|
7
|
+
module SiteClients
|
8
|
+
class ManuelFerrara < JulesJordanMedia
|
9
|
+
base_uri "https://manuelferrara.com/"
|
10
|
+
|
11
|
+
site_client_name :manuel_ferrara
|
12
|
+
|
13
|
+
COLLECTION = "Manuel Ferrara"
|
14
|
+
|
15
|
+
SCENES_ENDPOINT_TRIAL = "/trial/categories/movies_$page$_d.html"
|
16
|
+
|
17
|
+
def search(filename)
|
18
|
+
refresh_datastore(1) if datastore_refresh_required?
|
19
|
+
match = SiteClients::QueryGenerator::Base.generic_generate(filename, source_format)
|
20
|
+
lookup_in_datastore!(match)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xxx_rename/site_clients/query_generator/mg_premium"
|
4
|
+
|
5
|
+
module XxxRename
|
6
|
+
module SiteClients
|
7
|
+
class MGPremium < Base
|
8
|
+
include HTTParty
|
9
|
+
include Utils
|
10
|
+
|
11
|
+
base_uri "https://site-api.project1service.com"
|
12
|
+
|
13
|
+
SEARCH_ENDPOINT = "/v2/releases"
|
14
|
+
ACTOR_ENDPOINT = "/v1/actors"
|
15
|
+
|
16
|
+
# @param [XxxRename::Data::Config] config
|
17
|
+
# @param [String] site_url
|
18
|
+
def initialize(config, site_url:)
|
19
|
+
@site_url = site_url
|
20
|
+
super(config)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [String] filename
|
24
|
+
# @return [Hash{Symbol->Array | String | Time}, nil] A Hash containing details extracted from the file.
|
25
|
+
# @param [Hash] opts
|
26
|
+
# @option opts [Boolean] recursive If needed to search for a file recursively
|
27
|
+
def search(filename, **opts)
|
28
|
+
str = search_scene_title(filename)
|
29
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) if str.nil?
|
30
|
+
|
31
|
+
@normalized_scene_name = str.normalize
|
32
|
+
|
33
|
+
scene_resp = api_search(str)
|
34
|
+
return scene_resp[@normalized_scene_name] unless scene_resp[@normalized_scene_name].nil?
|
35
|
+
|
36
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, str) unless opts[:recursive]
|
37
|
+
|
38
|
+
scene_resp = recursive_search(str)
|
39
|
+
scene_resp || raise(Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, str))
|
40
|
+
end
|
41
|
+
|
42
|
+
def actor_details(actor)
|
43
|
+
opts = opt_search_actor(actor)
|
44
|
+
response = self.class.get(ACTOR_ENDPOINT, opts)
|
45
|
+
return unless response.code == 200
|
46
|
+
|
47
|
+
response["result"]&.select { |x| match? actor, x["name"] }&.first&.slice("name", "gender")
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# @param [String] search_string Search parameter
|
53
|
+
# @return [Hash{Symbol->Array | String | Time}, nil]
|
54
|
+
def recursive_search(search_string)
|
55
|
+
while search_string.length >= 3
|
56
|
+
search_string = reduce_search_string(search_string)
|
57
|
+
XxxRename.logger.debug "Trying with #{search_string.to_s.colorize(:blue)}"
|
58
|
+
scene = api_search(search_string)
|
59
|
+
return scene[@normalized_scene_name] unless scene[@normalized_scene_name].nil?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def api_search(search_string)
|
64
|
+
opts = opt_search_scene(search_string)
|
65
|
+
response = self.class.get(SEARCH_ENDPOINT, opts)
|
66
|
+
|
67
|
+
case response.code
|
68
|
+
when 200
|
69
|
+
parsed_response = JSON.parse(response.body)
|
70
|
+
fetch_scene_details(parsed_response["result"])
|
71
|
+
when 404
|
72
|
+
parsed_response = JSON.parse(response.body)
|
73
|
+
if parsed_response.is_a?(Array) && banned_search?(parsed_response.first["errors"])
|
74
|
+
reduced_search_string = reduce_search_string(search_string)
|
75
|
+
XxxRename.logger.debug "Got banned search error with search string" \
|
76
|
+
"#{search_string.to_s.colorize(:red)}. Trying with" \
|
77
|
+
"#{reduced_search_string.to_s.colorize(:red)}\n"
|
78
|
+
api_search(reduced_search_string)
|
79
|
+
else
|
80
|
+
obj = {
|
81
|
+
request_options: opts,
|
82
|
+
response_code: response.code,
|
83
|
+
response_body: response.body
|
84
|
+
}
|
85
|
+
raise Errors::SearchError.new(search_string, obj)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
handle_response! { response }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param [String] search_string
|
93
|
+
# @return [String]
|
94
|
+
def reduce_search_string(search_string)
|
95
|
+
# Remove last 5 characters
|
96
|
+
search_string.chop.chop.chop.chop.chop
|
97
|
+
end
|
98
|
+
|
99
|
+
def banned_search?(errors)
|
100
|
+
errors.each do |err|
|
101
|
+
return true if err["code"] == 1750
|
102
|
+
end
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
def opt_search_actor(actor)
|
107
|
+
{
|
108
|
+
query: {
|
109
|
+
limit: 10,
|
110
|
+
search: actor
|
111
|
+
},
|
112
|
+
headers: {
|
113
|
+
"Instance" => instance_token,
|
114
|
+
"User-Agent" => "Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion"
|
115
|
+
},
|
116
|
+
timeout: 5
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def opt_search_scene(scene_name)
|
121
|
+
{
|
122
|
+
query: {
|
123
|
+
type: "scene",
|
124
|
+
limit: 10,
|
125
|
+
search: scene_name
|
126
|
+
},
|
127
|
+
headers: {
|
128
|
+
"Instance" => instance_token,
|
129
|
+
"User-Agent" => "Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion"
|
130
|
+
},
|
131
|
+
timeout: 5
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
def instance_token(refresh = false)
|
136
|
+
@instance_token = refresh_token_mg(@site_url) if @instance_token.nil?
|
137
|
+
@instance_token = refresh_token_mg(@site_url) if refresh
|
138
|
+
|
139
|
+
@instance_token
|
140
|
+
end
|
141
|
+
|
142
|
+
def refresh_token_mg(site_url)
|
143
|
+
XxxRename.logger.debug "Refreshing Instance Token...".colorize(:blue)
|
144
|
+
response = HTTParty.get(site_url)
|
145
|
+
case response.code
|
146
|
+
when 429
|
147
|
+
raise "Failed to refresh instance token. Too many requests made..."
|
148
|
+
when 200
|
149
|
+
cookie_header = response.headers["set-cookie"].to_s
|
150
|
+
instance_token_param = cookie_header.split(";").first
|
151
|
+
instance_token_param.sub("instance_token=", "")
|
152
|
+
else
|
153
|
+
raise "Error in refreshing instance token: #{response.code}"
|
154
|
+
end
|
155
|
+
rescue OpenSSL::SSL::SSLError
|
156
|
+
raise "Unable to open connection to #{site_url}. Check your internet connection and try again."
|
157
|
+
end
|
158
|
+
|
159
|
+
def fetch_scene_details(results)
|
160
|
+
resp = {}
|
161
|
+
return resp if results.empty?
|
162
|
+
|
163
|
+
results.each do |scene|
|
164
|
+
normalized_title = title(scene).normalize
|
165
|
+
|
166
|
+
hash = {}.tap do |h|
|
167
|
+
h[:collection] = collection(scene)
|
168
|
+
h[:collection_tag] = site_config.collection_tag
|
169
|
+
h[:title] = title(scene)
|
170
|
+
h[:id] = scene_id(scene)
|
171
|
+
h[:date_released] = date_released(scene)
|
172
|
+
h[:movie] = movie_hash(scene) unless movie_hash(scene).nil?
|
173
|
+
h[:female_actors] = female_actors(scene)
|
174
|
+
h[:male_actors] = male_actors(scene)
|
175
|
+
h[:actors] = female_actors(scene) + male_actors(scene)
|
176
|
+
end
|
177
|
+
resp[normalized_title] = Data::SceneData.new(hash)
|
178
|
+
end
|
179
|
+
|
180
|
+
resp
|
181
|
+
end
|
182
|
+
|
183
|
+
def movie_hash(scene)
|
184
|
+
return nil if scene["parent"].nil?
|
185
|
+
|
186
|
+
parent = scene["parent"]
|
187
|
+
type = parent["type"] == "serie" ? "series" : parent["type"]
|
188
|
+
{
|
189
|
+
name: parent["title"],
|
190
|
+
date: Time.parse(parent["dateReleased"]),
|
191
|
+
url: URI.join(@site_url, "/#{type}/", "#{parent["id"]}/", parent["title"].downcase.gsub(" ", "-")).to_s,
|
192
|
+
front_image: extract_image_url(parent),
|
193
|
+
studio: parent.dig("brandMeta", "displayName"),
|
194
|
+
synopsis: parent["description"]
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
def extract_image_url(hash)
|
199
|
+
cover_hash = hash.dig("images", "cover", "0") || hash.dig("images", "poster", "0")
|
200
|
+
return if cover_hash.nil?
|
201
|
+
|
202
|
+
res = %w[xx xl lg md sm].find { |x| cover_hash.key?(x) }
|
203
|
+
cover_hash.dig(res, "url") || cover_hash.dig(res, "urls", "default")
|
204
|
+
end
|
205
|
+
|
206
|
+
def female_actors(scene)
|
207
|
+
scene["actors"]
|
208
|
+
.select { |actor| actor["gender"] == "female" }
|
209
|
+
.map { |actor| actor["name"] }
|
210
|
+
.sort
|
211
|
+
end
|
212
|
+
|
213
|
+
def male_actors(scene)
|
214
|
+
scene["actors"]
|
215
|
+
.select { |actor| actor["gender"] == "male" }
|
216
|
+
.map { |actor| actor["name"] }
|
217
|
+
.sort
|
218
|
+
end
|
219
|
+
|
220
|
+
def collection(scene)
|
221
|
+
collections = scene["collections"]
|
222
|
+
return "" if collections.empty?
|
223
|
+
|
224
|
+
collections.first["name"]
|
225
|
+
end
|
226
|
+
|
227
|
+
def title(scene)
|
228
|
+
scene["title"]
|
229
|
+
end
|
230
|
+
|
231
|
+
def scene_id(scene)
|
232
|
+
scene["id"]
|
233
|
+
end
|
234
|
+
|
235
|
+
def date_released(scene)
|
236
|
+
Time.parse(scene["dateReleased"])
|
237
|
+
end
|
238
|
+
|
239
|
+
def search_scene_title(filename)
|
240
|
+
# TODO: Allow client to use section of parameters rather than just the scene title
|
241
|
+
resp = SiteClients::QueryGenerator::MgPremium.generate(filename, source_format)
|
242
|
+
|
243
|
+
resp&.title&.downcase
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xxx_rename/site_clients/mg_premium"
|
4
|
+
|
5
|
+
module XxxRename
|
6
|
+
module SiteClients
|
7
|
+
class Mofos < MGPremium
|
8
|
+
site_client_name :mofos
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
super(config, site_url: "https://www.mofos.com")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|