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,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module XxxRename
|
4
|
+
class ProcessedFile
|
5
|
+
@@prefix_regex_maps = {}
|
6
|
+
|
7
|
+
CHARS = "[\\w\\s\\-_,'\"\\.]+"
|
8
|
+
OPTIONAL_CHARS = "[\\w\\s\\-_,'\"]*"
|
9
|
+
IDENTIFIER = "(\\d+|[a-z\\-\\_]+)"
|
10
|
+
OPTIONAL_IDENTIFIER = "(\\d*|[a-z\\-\\_]*)"
|
11
|
+
|
12
|
+
PROCESSED_FILE_REGEX = /
|
13
|
+
(?<title>#{CHARS}) # Scene Title
|
14
|
+
((\[(?<collection_tag>\w+)\])(?<collection>#{CHARS}))? # [CL] Collection (Optional)
|
15
|
+
(\[F\](?<female_actors>#{CHARS})) # Female Actors
|
16
|
+
(\[M\](?<male_actors>#{CHARS}))? # Male Actors (Optional)
|
17
|
+
/x.freeze
|
18
|
+
|
19
|
+
FEMALE_FIRST_ID_FILE_REGEX = /
|
20
|
+
(?<female_actors>#{CHARS}) # Female Actors
|
21
|
+
(\[T\](?<title>#{CHARS})) # [T] Title
|
22
|
+
(\[M\](?<male_actors>#{CHARS}))? # Male Actors (Optional)
|
23
|
+
((\[(?<collection_tag>\w+)\])(?<collection>#{CHARS})) # [CL] Collection
|
24
|
+
(\[ID\](?<id>#{CHARS}))? # [ID] ID (Optional)
|
25
|
+
/x.freeze
|
26
|
+
|
27
|
+
# rubocop:disable Layout/HashAlignment
|
28
|
+
TOKEN_REGEX_MAP = {
|
29
|
+
# Scene Title
|
30
|
+
"%title" => "(?<title>#{CHARS})",
|
31
|
+
|
32
|
+
# Scene Collection
|
33
|
+
"%collection" => "(?<collection>#{CHARS})",
|
34
|
+
"%collection_op" => "(?<collection>#{OPTIONAL_CHARS}?)",
|
35
|
+
"%collection_tag_1" => "(?<collection_tag>\\w)",
|
36
|
+
"%collection_tag_2" => "(?<collection_tag>\\w{,2})",
|
37
|
+
"%collection_tag_3" => "(?<collection_tag>\\w{,3})",
|
38
|
+
|
39
|
+
# Actors (Words separated by spaces, hyphen(-), underscore(_))
|
40
|
+
# Multiple actors(female, male or actors) can be parsed with a single tag
|
41
|
+
# Do not mix `actors` with any other tags
|
42
|
+
"%female_actors" => "(?<female_actors>#{CHARS})",
|
43
|
+
"%male_actors" => "(?<male_actors>#{CHARS})",
|
44
|
+
"%male_actors_op" => "(?<male_actors>#{OPTIONAL_CHARS})?",
|
45
|
+
"%actors" => "(?<actors>#{CHARS})",
|
46
|
+
|
47
|
+
# Scene ID
|
48
|
+
"%id" => "(?<id>#{IDENTIFIER})",
|
49
|
+
"%id_op" => "(?<id>#{OPTIONAL_IDENTIFIER})?",
|
50
|
+
|
51
|
+
# Ignore n words (space separated)
|
52
|
+
"%ignore_1_word" => "(\\w\\s){1}",
|
53
|
+
"%ignore_2_words" => "(\\w\\s){2}",
|
54
|
+
"%ignore_3_words" => "(\\w\\s){3}",
|
55
|
+
"%ignore_4_words" => "(\\w\\s){4}",
|
56
|
+
"%ignore_5_words" => "(\\w\\s){5}",
|
57
|
+
"%ignore_6_words" => "(\\w\\s){6}",
|
58
|
+
"%ignore_7_words" => "(\\w\\s){7}",
|
59
|
+
|
60
|
+
"%ignore_all" => ".*",
|
61
|
+
|
62
|
+
# Date
|
63
|
+
"%yyyy_mm_dd" => "(?<year>\\d{4})_(?<month>\\d{1,2})_(?<day>\\d{1,2})",
|
64
|
+
"%dd" => "(?<day>\\d{1,2})",
|
65
|
+
"%mm" => "(?<month>\\d{1,2})",
|
66
|
+
"%yyyy" => "(?<year>\\d{4})",
|
67
|
+
"%yy" => "(?<year>\\d{2,4})"
|
68
|
+
}.freeze
|
69
|
+
# rubocop:enable Layout/HashAlignment
|
70
|
+
|
71
|
+
# @param [XxxRename::Data::Config::Global] hash
|
72
|
+
def self.prefix_hash_set(hash)
|
73
|
+
return unless @@prefix_regex_maps.empty?
|
74
|
+
|
75
|
+
# rubocop:disable Layout/HashAlignment
|
76
|
+
@@prefix_regex_maps = {
|
77
|
+
"%female_actors_prefix" => "(?<female_actors_prefix>#{Regexp.quote(hash[:female_actors_prefix])})",
|
78
|
+
"%male_actors_prefix" => "(?<male_actors_prefix>#{Regexp.quote(hash[:male_actors_prefix])})",
|
79
|
+
"%actors_prefix" => "(?<actors_prefix>#{Regexp.quote(hash[:actors_prefix])})",
|
80
|
+
"%title_prefix" => "(?<title_prefix>#{Regexp.quote(hash[:title_prefix])})",
|
81
|
+
"%id_prefix" => "(?<id_prefix>#{Regexp.quote(hash[:id_prefix])})"
|
82
|
+
}
|
83
|
+
# rubocop:enable Layout/HashAlignment
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.prefix_regex_maps
|
87
|
+
@@prefix_regex_maps
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :file
|
91
|
+
|
92
|
+
# @param [String] file File to match
|
93
|
+
# @return [Data::SceneData]
|
94
|
+
def self.parse(file)
|
95
|
+
resp = new(file).match_file(PROCESSED_FILE_REGEX, FEMALE_FIRST_ID_FILE_REGEX)
|
96
|
+
raise Errors::ParsingError, "does not match any registered patterns" if resp.nil?
|
97
|
+
|
98
|
+
resp
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param [String] file File to match
|
102
|
+
# @param [String] pattern A regular expression to match the file
|
103
|
+
# @return [Data::SceneData]
|
104
|
+
def self.strpfile(file, pattern)
|
105
|
+
object = new(file)
|
106
|
+
regex = object.make_regex!(pattern)
|
107
|
+
|
108
|
+
resp = object.match_file(regex)
|
109
|
+
raise Errors::ParsingError, "does not match given pattern" if resp.nil?
|
110
|
+
|
111
|
+
resp
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param [String] file Name of file
|
115
|
+
def initialize(file)
|
116
|
+
@file = file
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param [String] pattern
|
120
|
+
def make_regex!(pattern)
|
121
|
+
token_regex = /%\w+\b/
|
122
|
+
tokens = pattern.scan(token_regex)
|
123
|
+
validate_tokens!(tokens)
|
124
|
+
|
125
|
+
regex = "^#{Regexp.quote(pattern)}"
|
126
|
+
tokens.each do |m|
|
127
|
+
regex = regex.gsub(m, TOKEN_REGEX_MAP[m]) if TOKEN_REGEX_MAP.key?(m)
|
128
|
+
regex = regex.gsub(m, @@prefix_regex_maps[m]) if @@prefix_regex_maps.key?(m)
|
129
|
+
end
|
130
|
+
regex += "$"
|
131
|
+
Regexp.new regex
|
132
|
+
end
|
133
|
+
|
134
|
+
# @param [Array[Regex]] regexes
|
135
|
+
def match_file(*regexes)
|
136
|
+
regexes.map { |regex| process_regex!(regex) }.compact.first
|
137
|
+
end
|
138
|
+
|
139
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
140
|
+
# @return [Data::SceneData]
|
141
|
+
def process_regex!(regex)
|
142
|
+
file_without_ext = File.basename(file, File.extname(file))
|
143
|
+
match = file_without_ext.match(regex)
|
144
|
+
return if match.nil?
|
145
|
+
|
146
|
+
match_hash = match.named_captures.tap do |h|
|
147
|
+
h["female_actors"] = clean_actor_str(h["female_actors"]) || []
|
148
|
+
h["male_actors"] = clean_actor_str(h["male_actors"]) || []
|
149
|
+
h["actors"] = begin
|
150
|
+
if h["actors"]
|
151
|
+
clean_actor_str(h["actors"])
|
152
|
+
else
|
153
|
+
h["female_actors"] + h["male_actors"]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
h["collection"] = clean_s(h["collection"])
|
157
|
+
h["collection_tag"] = clean_s(h["collection_tag"])
|
158
|
+
h["title"] = clean_s(h["title"])
|
159
|
+
h["id"] = clean_s(h["id"])
|
160
|
+
h["title"] = clean_s(h["title"])
|
161
|
+
h["date_released"] = if (dr = date_released(h["year"], h["month"], h["day"]))
|
162
|
+
dr
|
163
|
+
elsif File.exist?(@file)
|
164
|
+
File.mtime(@file)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
Data::SceneData.new(match_hash)
|
169
|
+
end
|
170
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
# For a set of tokens to be valid, all of the tokens
|
175
|
+
# should have a respective rule AKA 'value' in the combined
|
176
|
+
# hash of TOKEN_REGEX_MAP and @@prefix_regex_maps
|
177
|
+
# @param [Array[String]] tokens
|
178
|
+
def validate_tokens!(tokens)
|
179
|
+
combined_token_map = TOKEN_REGEX_MAP.merge(@@prefix_regex_maps)
|
180
|
+
invalid_tokens = tokens.select { |x| combined_token_map[x].nil? }
|
181
|
+
raise ArgumentError, "Invalid tokens #{invalid_tokens.join(", ")} in pattern." unless invalid_tokens.empty?
|
182
|
+
end
|
183
|
+
|
184
|
+
# @return [Nil, Time]
|
185
|
+
def date_released(year, month, day)
|
186
|
+
Time.new(year, month, day) if year && month && day
|
187
|
+
end
|
188
|
+
|
189
|
+
# @param [String] str A String extracted from the regex match
|
190
|
+
# @return [Array[String]]
|
191
|
+
def clean_actor_str(str)
|
192
|
+
return nil if str.nil?
|
193
|
+
|
194
|
+
str.split(",").map(&:strip).map(&:presence).sort.compact
|
195
|
+
end
|
196
|
+
|
197
|
+
def clean_s(str)
|
198
|
+
return nil if str.nil? || !str.presence
|
199
|
+
|
200
|
+
str.strip
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xxx_rename/site_clients/errors"
|
4
|
+
require "xxx_rename/constants"
|
5
|
+
require "awesome_print"
|
6
|
+
require "algolia/error"
|
7
|
+
|
8
|
+
module XxxRename
|
9
|
+
class Search
|
10
|
+
include FileUtilities
|
11
|
+
|
12
|
+
SearchResult = Struct.new(:scene_data, :site_client, keyword_init: true) do
|
13
|
+
def success?
|
14
|
+
scene_data && site_client
|
15
|
+
end
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
scene_data.nil? && site_client.nil?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
NON_FATAL_ERRORS = [
|
23
|
+
SiteClients::Errors::NoMatchError,
|
24
|
+
SiteClients::Errors::SearchError,
|
25
|
+
SiteClients::Errors::NotFoundError,
|
26
|
+
SiteClients::Errors::RedirectedError,
|
27
|
+
SiteClients::Errors::BadRequestError
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
SITE_CLIENT_FATAL_ERRORS = [
|
31
|
+
SiteClients::Errors::SiteClientUnavailableError,
|
32
|
+
SiteClients::Errors::InvalidCredentialsError,
|
33
|
+
SiteClients::Errors::ForbiddenError,
|
34
|
+
SiteClients::Errors::BadGatewayError,
|
35
|
+
SiteClients::Errors::UnhandledError,
|
36
|
+
SiteClients::Errors::UnauthorizedError,
|
37
|
+
SiteClients::Errors::TooManyRequestsError,
|
38
|
+
Algolia::AlgoliaUnreachableHostError,
|
39
|
+
Algolia::AlgoliaHttpError
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
# @param [XxxRename::SiteClientMatcher] matcher
|
43
|
+
# @param [XxxRename::Data::SceneDatastoreQuery] scene_datastore
|
44
|
+
def initialize(matcher, scene_datastore, force_refresh)
|
45
|
+
@matcher = matcher
|
46
|
+
@scene_datastore = scene_datastore
|
47
|
+
@force_refresh = force_refresh
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Search for a file using datastore or public APIs
|
52
|
+
# Optionally, if a block is given, it will be called
|
53
|
+
# with the file's matched XxxRename::Data::SceneData
|
54
|
+
#
|
55
|
+
# @param [String] file
|
56
|
+
# @return [Nil|[XxxRename::Search::SearchResult]]
|
57
|
+
def search(file)
|
58
|
+
@file = File.basename(file)
|
59
|
+
match_responses = matcher.match(@file)
|
60
|
+
search_result = nil
|
61
|
+
|
62
|
+
match_responses.each do |match_response|
|
63
|
+
scene_data = find_by_site_or_datastore(match_response)
|
64
|
+
unless scene_data.nil?
|
65
|
+
search_result = SearchResult.new(scene_data: scene_data, site_client: match_response.site_client)
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if search_result.nil?
|
71
|
+
block_given? ? yield(nil_search_result) : nil_search_result
|
72
|
+
else
|
73
|
+
XxxRename.logger.ap search_result.scene_data
|
74
|
+
block_given? ? yield(search_result) : search_result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
attr_reader :matcher, :scene_datastore, :file
|
81
|
+
|
82
|
+
def nil_search_result
|
83
|
+
@nil_search_result ||= SearchResult.new(scene_data: nil, site_client: nil)
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_by_site_or_datastore(match_response)
|
87
|
+
return site_client_search(match_response.site_client, file) if match_response.unprocessed == true || @force_refresh
|
88
|
+
|
89
|
+
if (resp = scene_datastore.find_by_abs_path?(abs_path!(@file)))
|
90
|
+
XxxRename.logger.info "#{"[FILENAME LOOKUP SUCCESS]".colorize(:green)} #{resp.title}"
|
91
|
+
return resp
|
92
|
+
end
|
93
|
+
|
94
|
+
if (resp = find_in_datastore(match_response.parsed_info))
|
95
|
+
XxxRename.logger.info "#{"[MATCH SUCCESS DATASTORE]".colorize(:green)} #{resp.title}"
|
96
|
+
register_file(resp, file)
|
97
|
+
return resp
|
98
|
+
end
|
99
|
+
|
100
|
+
site_client_search(match_response.site_client, file)
|
101
|
+
end
|
102
|
+
|
103
|
+
def site_client_search(site_client, file)
|
104
|
+
result = site_client.search(file)
|
105
|
+
if result.nil?
|
106
|
+
XxxRename.logger.info "#{"[NO MATCH SITE CLIENT]".colorize(:yellow)} #{site_client.class.site_client_name}"
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
save_scene_in_datastore(result, file)
|
111
|
+
XxxRename.logger.info "#{"[MATCH SUCCESS SITE CLIENT]".colorize(:green)} #{site_client.class.site_client_name}"
|
112
|
+
result
|
113
|
+
rescue *NON_FATAL_ERRORS => e
|
114
|
+
XxxRename.logger.info "#{"[NO MATCH SITE CLIENT]".colorize(:yellow)} #{site_client.class.site_client_name}"
|
115
|
+
XxxRename.logger.debug e.message
|
116
|
+
nil
|
117
|
+
rescue *SITE_CLIENT_FATAL_ERRORS => e
|
118
|
+
XxxRename.logger.error e.message
|
119
|
+
matcher.disable_site(site_client)
|
120
|
+
nil
|
121
|
+
rescue Errors::FatalError => e
|
122
|
+
raise e
|
123
|
+
end
|
124
|
+
|
125
|
+
def save_scene_in_datastore(scene_data, file)
|
126
|
+
scene_datastore.create!(scene_data, force: true)
|
127
|
+
register_file(scene_data, file)
|
128
|
+
end
|
129
|
+
|
130
|
+
def register_file(scene_data, file)
|
131
|
+
XxxRename.logger.debug "[FILENAME REGISTER] #{file} (SCENE) #{scene_data.title}"
|
132
|
+
path = abs_path!(file)
|
133
|
+
scene_datastore.register_file(scene_data, path)
|
134
|
+
end
|
135
|
+
|
136
|
+
def find_in_datastore(parsed_info)
|
137
|
+
find_by_metadata?(parsed_info) || find_by_key?(parsed_info)
|
138
|
+
end
|
139
|
+
|
140
|
+
def abs_path!(file)
|
141
|
+
path = File.expand_path(file)
|
142
|
+
raise "expanded path #{path} invalid" unless valid_file?(path)
|
143
|
+
|
144
|
+
path
|
145
|
+
end
|
146
|
+
|
147
|
+
def find_by_key?(parsed_info)
|
148
|
+
key = parsed_info&.key
|
149
|
+
key.nil? ? nil : scene_datastore.find_by_key?(key)
|
150
|
+
end
|
151
|
+
|
152
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
153
|
+
def find_by_metadata?(parsed_info)
|
154
|
+
collection_tag = parsed_info.collection_tag.presence
|
155
|
+
if collection_tag && (id = parsed_info.id&.presence)
|
156
|
+
scene_datastore.find(collection_tag: collection_tag, id: id)&.first
|
157
|
+
elsif collection_tag && parsed_info.title
|
158
|
+
scene_datastore.find(collection_tag: collection_tag, title: parsed_info.title)&.first
|
159
|
+
elsif parsed_info.title && parsed_info.actors
|
160
|
+
resp = scene_datastore.find(title: parsed_info.title, actors: parsed_info.actors)
|
161
|
+
return resp.first if resp && resp.length == 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,299 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
require "xxx_rename/site_clients/base"
|
6
|
+
require "xxx_rename/site_clients/errors"
|
7
|
+
|
8
|
+
module XxxRename
|
9
|
+
class SiteClientMatcher
|
10
|
+
MatchResponse = Struct.new(:parsed_info, :site_client, :unprocessed, keyword_init: true)
|
11
|
+
|
12
|
+
# @param [XxxRename::Data::Config] config
|
13
|
+
def initialize(config, override_site: nil)
|
14
|
+
@config = config
|
15
|
+
@override_site = override_site
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String] file
|
19
|
+
# @return [Array[MatchResponse]]
|
20
|
+
def match(file)
|
21
|
+
@match_response = []
|
22
|
+
find_matches(file)
|
23
|
+
@match_response
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch(key)
|
27
|
+
site_clients.fetch(key.to_sym, nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Disable a site client permanently for the duration of the
|
32
|
+
# program runtime. This is helpful when we are rate limited,
|
33
|
+
# or fail auth for sites that require one, and protects
|
34
|
+
# the app from calling the API unnecessarily. Once a site
|
35
|
+
# is disabled, the matcher will never return the site client
|
36
|
+
# even after a successful match
|
37
|
+
#
|
38
|
+
# @param [XxxRename::SiteClients::Base] client
|
39
|
+
# @return [Set[String]]
|
40
|
+
def disable_site(client)
|
41
|
+
site_client_sym = client.class.site_client_name
|
42
|
+
site_clients[site_client_sym] = nil
|
43
|
+
disabled_sites.add(site_client_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
def site_disabled?(site)
|
47
|
+
disabled_sites.member?(site)
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Initialise a site client and store it in memory
|
52
|
+
# Consecutive calls to this method with the same site
|
53
|
+
# client name will not cause it to reinitialise the
|
54
|
+
# site client. This can provide performance benefits
|
55
|
+
# if the site client needs to do some work during
|
56
|
+
# initialisation, e.g. login, fetch tokens, etc
|
57
|
+
#
|
58
|
+
# Usually, you won't need to call this method manually,
|
59
|
+
# as the matcher will do it for you. But it can be used
|
60
|
+
# by certain functions like ActorsHelper.
|
61
|
+
#
|
62
|
+
# @param [Symbol] site
|
63
|
+
# @return [XxxRename::SiteClients::Base]
|
64
|
+
def initialise_site_client(site)
|
65
|
+
return site_clients[site] if site_clients.key?(site) && !site_clients[site].nil?
|
66
|
+
|
67
|
+
if disabled_sites.member?(site)
|
68
|
+
XxxRename.logger.debug "#{site} is disabled"
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
|
72
|
+
site_clients[site] = generate_class(site)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
attr_reader :config, :override_site
|
78
|
+
|
79
|
+
#
|
80
|
+
# Find a match for a file using three strategies
|
81
|
+
# 1. match a file by original file regex.
|
82
|
+
# this will work for files downloaded from websites
|
83
|
+
# 2. match a file with the legacy format used by
|
84
|
+
# the app. this is defined in ProcessedFile by
|
85
|
+
# PROCESSED_FILE_REGEX and FEMALE_FIRST_ID_FILE_REGEX
|
86
|
+
# 3. match a file by formats provided in the config
|
87
|
+
#
|
88
|
+
# @param [String] file
|
89
|
+
# @return [Boolean]
|
90
|
+
def find_matches(file)
|
91
|
+
match_unprocessed?(file) ||
|
92
|
+
match_with_legacy_formats?(file) ||
|
93
|
+
match_with_source_file_format?(file)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Nil, String]
|
97
|
+
def override_sc_original_pattern
|
98
|
+
@override_sc_original_pattern ||=
|
99
|
+
begin
|
100
|
+
pattern = nil
|
101
|
+
original_file_regexes.map do |i_pattern, i_site_clients|
|
102
|
+
i_site_clients.each do |sc|
|
103
|
+
if override_site == sc
|
104
|
+
pattern = i_pattern
|
105
|
+
break
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
pattern
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def register_response(site_client_sym, match, unprocessed:)
|
114
|
+
return if site_disabled?(site_client_sym)
|
115
|
+
|
116
|
+
site_client = initialise_site_client(site_client_sym)
|
117
|
+
@match_response << MatchResponse.new(parsed_info: match, site_client: site_client, unprocessed: unprocessed)
|
118
|
+
end
|
119
|
+
|
120
|
+
def disabled_sites
|
121
|
+
@disabled_sites ||= Set.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def site_clients
|
125
|
+
@site_clients ||= {}
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Generate a reverse lookup of
|
130
|
+
# site.{site}.file_source_format -> site
|
131
|
+
#
|
132
|
+
# This will be used to decide which site client should be used for each file
|
133
|
+
#
|
134
|
+
# @return [Hash[String->Symbol]]
|
135
|
+
def source_file_patterns_to_site
|
136
|
+
@source_file_patterns_to_site ||=
|
137
|
+
begin
|
138
|
+
file_patterns = {}
|
139
|
+
site_client_configs = if override_site.nil?
|
140
|
+
config.site.attributes
|
141
|
+
else
|
142
|
+
config.site.attributes.select { |site, _| site.to_sym == override_site }
|
143
|
+
end
|
144
|
+
site_client_configs.each_pair do |site, site_config|
|
145
|
+
site_config.file_source_format.map { |format| file_patterns[format] = site }
|
146
|
+
end
|
147
|
+
file_patterns
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# @param [String] file
|
152
|
+
# @return [Boolean]
|
153
|
+
def match_unprocessed?(file)
|
154
|
+
unless override_site.nil?
|
155
|
+
register_response(override_site, nil, unprocessed: true)
|
156
|
+
return true
|
157
|
+
end
|
158
|
+
|
159
|
+
original_file_regexes.each_pair do |regex, site_clients|
|
160
|
+
next unless (match = file.match(regex))
|
161
|
+
|
162
|
+
site_clients.map do |sc|
|
163
|
+
register_response(sc, match, unprocessed: true)
|
164
|
+
end
|
165
|
+
return true
|
166
|
+
end
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
# @param [String] file
|
171
|
+
# @return [Boolean] List of matching site clients
|
172
|
+
def match_with_legacy_formats?(file)
|
173
|
+
match = ProcessedFile.parse(file)
|
174
|
+
return false unless match.collection_tag.presence
|
175
|
+
|
176
|
+
site_client_sym = config.collection_tag_to_site_client[match.collection_tag]
|
177
|
+
return false if site_client_sym.nil?
|
178
|
+
|
179
|
+
register_response(site_client_sym, match, unprocessed: false) if override_site.nil? || override_site == client
|
180
|
+
rescue Errors::ParsingError
|
181
|
+
false
|
182
|
+
end
|
183
|
+
|
184
|
+
# @param [String] file
|
185
|
+
# @return [Boolean]
|
186
|
+
def match_with_source_file_format?(file)
|
187
|
+
matches = []
|
188
|
+
source_file_patterns_to_site.each_pair do |format, site_client_sym|
|
189
|
+
match = ProcessedFile.strpfile(file, format)
|
190
|
+
register_response(site_client_sym, match, unprocessed: false)
|
191
|
+
matches << true
|
192
|
+
rescue Errors::ParsingError
|
193
|
+
matches << false
|
194
|
+
end
|
195
|
+
matches.any?(TRUE)
|
196
|
+
end
|
197
|
+
|
198
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
199
|
+
def generate_class(site)
|
200
|
+
case site.to_sym
|
201
|
+
when :adult_time
|
202
|
+
require "xxx_rename/site_clients/adult_time"
|
203
|
+
XxxRename::SiteClients::AdultTime.new(config)
|
204
|
+
when :babes
|
205
|
+
require "xxx_rename/site_clients/babes"
|
206
|
+
XxxRename::SiteClients::Babes.new(config)
|
207
|
+
when :blacked
|
208
|
+
require "xxx_rename/site_clients/blacked"
|
209
|
+
XxxRename::SiteClients::Blacked.new(config)
|
210
|
+
when :blacked_raw
|
211
|
+
require "xxx_rename/site_clients/blacked_raw"
|
212
|
+
XxxRename::SiteClients::BlackedRaw.new(config)
|
213
|
+
when :brazzers
|
214
|
+
require "xxx_rename/site_clients/brazzers"
|
215
|
+
XxxRename::SiteClients::Brazzers.new(config)
|
216
|
+
when :digital_playground
|
217
|
+
require "xxx_rename/site_clients/digital_playground"
|
218
|
+
XxxRename::SiteClients::DigitalPlayground.new(config)
|
219
|
+
when :elegant_angel
|
220
|
+
require "xxx_rename/site_clients/elegant_angel"
|
221
|
+
XxxRename::SiteClients::ElegantAngel.new(config)
|
222
|
+
when :evil_angel
|
223
|
+
require "xxx_rename/site_clients/evil_angel"
|
224
|
+
XxxRename::SiteClients::EvilAngel.new(config)
|
225
|
+
when :goodporn
|
226
|
+
require "xxx_rename/site_clients/goodporn"
|
227
|
+
XxxRename::SiteClients::Goodporn.new(config)
|
228
|
+
when :jules_jordan
|
229
|
+
require "xxx_rename/site_clients/jules_jordan"
|
230
|
+
XxxRename::SiteClients::JulesJordan.new(config)
|
231
|
+
when :manuel_ferrara
|
232
|
+
require "xxx_rename/site_clients/manuel_ferrara"
|
233
|
+
XxxRename::SiteClients::ManuelFerrara.new(config)
|
234
|
+
when :mofos
|
235
|
+
require "xxx_rename/site_clients/mofos"
|
236
|
+
XxxRename::SiteClients::Mofos.new(config)
|
237
|
+
when :naughty_america
|
238
|
+
require "xxx_rename/site_clients/naughty_america"
|
239
|
+
XxxRename::SiteClients::NaughtyAmerica.new(config)
|
240
|
+
when :nf_busty
|
241
|
+
require "xxx_rename/site_clients/nfbusty"
|
242
|
+
XxxRename::SiteClients::Nfbusty.new(config)
|
243
|
+
when :reality_kings
|
244
|
+
require "xxx_rename/site_clients/reality_kings"
|
245
|
+
XxxRename::SiteClients::RealityKings.new(config)
|
246
|
+
when :stash
|
247
|
+
require "xxx_rename/site_clients/stash_db"
|
248
|
+
XxxRename::SiteClients::StashDb.new(config)
|
249
|
+
when :tushy
|
250
|
+
require "xxx_rename/site_clients/tushy"
|
251
|
+
XxxRename::SiteClients::Tushy.new(config)
|
252
|
+
when :tushy_raw
|
253
|
+
require "xxx_rename/site_clients/tushy_raw"
|
254
|
+
XxxRename::SiteClients::TushyRaw.new(config)
|
255
|
+
when :twistys
|
256
|
+
require "xxx_rename/site_clients/twistys"
|
257
|
+
XxxRename::SiteClients::Twistys.new(config)
|
258
|
+
when :vixen
|
259
|
+
require "xxx_rename/site_clients/vixen"
|
260
|
+
XxxRename::SiteClients::Vixen.new(config)
|
261
|
+
when :whale_media
|
262
|
+
require "xxx_rename/site_clients/whale"
|
263
|
+
XxxRename::SiteClients::Whale.new(config)
|
264
|
+
when :wicked
|
265
|
+
require "xxx_rename/site_clients/wicked"
|
266
|
+
XxxRename::SiteClients::Wicked.new(config)
|
267
|
+
when :x_empire
|
268
|
+
require "xxx_rename/site_clients/x_empire"
|
269
|
+
XxxRename::SiteClients::XEmpire.new(config)
|
270
|
+
when :zero_tolerance
|
271
|
+
require "xxx_rename/site_clients/zero_tolerance"
|
272
|
+
XxxRename::SiteClients::ZeroTolerance.new(config)
|
273
|
+
else
|
274
|
+
raise "undefined site: #{site}"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
278
|
+
|
279
|
+
def original_file_regexes
|
280
|
+
@original_file_regexes ||= {}.tap do |h|
|
281
|
+
h[Constants::MG_PREMIUM_ORIGINAL_FILE_FORMAT] = %i[babes brazzers digital_playground mofos reality_kings twistys] # babes
|
282
|
+
# brazzers; mg premium
|
283
|
+
# digital playground; mg premium
|
284
|
+
h[Constants::EVIL_ANGEL_ORIGINAL_FILE_PATTERN] = [:evil_angel] # evil angel
|
285
|
+
h[Constants::GOODPORN_ORIGINAL_FILE_FORMAT] = [:goodporn] # goodporn
|
286
|
+
# mofos; mg premium
|
287
|
+
h[Constants::NAUGHTY_AMERICA_ORIGINAL_FILE_REGEX] = [:naughty_america] # naughty america
|
288
|
+
h[Constants::NF_BUSTY_ORIGINAL_FILE_REGEX] = [:nf_busty] # nf busty
|
289
|
+
# reality kings; mg premium
|
290
|
+
# stash; ALWAYS OVERRIDE
|
291
|
+
# twistys; mg premium
|
292
|
+
h[Constants::VIXEN_MEDIA_ORIGINAL_FILE_REGEX_1] = [:vixen] # vixen
|
293
|
+
h[Constants::VIXEN_MEDIA_ORIGINAL_FILE_REGEX_2] = [:vixen]
|
294
|
+
h[Constants::WHALE_ORIGINAL_FILE_PATTERN] = [:whale_media] # whale
|
295
|
+
# wicked # ALWAYS OVERRIDE
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xxx_rename/site_clients/algolia_v2"
|
4
|
+
require "xxx_rename/site_clients/query_generator/base"
|
5
|
+
|
6
|
+
module XxxRename
|
7
|
+
module SiteClients
|
8
|
+
class AdultTime < 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 :adult_time
|
16
|
+
|
17
|
+
def initialize(config)
|
18
|
+
@site_url = "https://www.milkingtable.com"
|
19
|
+
super(config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def search(filename)
|
23
|
+
match = SiteClients::QueryGenerator::Base.generic_generate(filename, source_format)
|
24
|
+
raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) if match.nil? || match.title.blank?
|
25
|
+
|
26
|
+
resp = fetch_scenes_from_api(match.title)
|
27
|
+
find_matched_scene!(resp, match)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|