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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/codeql-analysis.yml +42 -0
  3. data/.github/workflows/ruby.yml +44 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +41 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +174 -0
  10. data/README.md +319 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +23 -0
  13. data/bin/install +22 -0
  14. data/bin/setup +8 -0
  15. data/codecov.yml +2 -0
  16. data/docs/DEVELOPMENT.md +42 -0
  17. data/exe/xxx_rename +12 -0
  18. data/lib/xxx_rename/actions/base_action.rb +20 -0
  19. data/lib/xxx_rename/actions/log_new_filename.rb +40 -0
  20. data/lib/xxx_rename/actions/resolver.rb +32 -0
  21. data/lib/xxx_rename/actions/stash_app_post_movie.rb +62 -0
  22. data/lib/xxx_rename/actors_helper.rb +117 -0
  23. data/lib/xxx_rename/cli.rb +211 -0
  24. data/lib/xxx_rename/client.rb +110 -0
  25. data/lib/xxx_rename/constants.rb +96 -0
  26. data/lib/xxx_rename/contract/config_contract.rb +241 -0
  27. data/lib/xxx_rename/contract/config_generator.rb +207 -0
  28. data/lib/xxx_rename/contract/file_rename_op_contract.rb +54 -0
  29. data/lib/xxx_rename/contract/types.rb +10 -0
  30. data/lib/xxx_rename/core_extensions/string.rb +39 -0
  31. data/lib/xxx_rename/data/base.rb +34 -0
  32. data/lib/xxx_rename/data/config.rb +97 -0
  33. data/lib/xxx_rename/data/file_rename_op.rb +42 -0
  34. data/lib/xxx_rename/data/file_rename_op_datastore.rb +111 -0
  35. data/lib/xxx_rename/data/naughty_america_database.rb +22 -0
  36. data/lib/xxx_rename/data/query_interface.rb +78 -0
  37. data/lib/xxx_rename/data/scene_data.rb +71 -0
  38. data/lib/xxx_rename/data/scene_datastore.rb +401 -0
  39. data/lib/xxx_rename/data/site_config.rb +84 -0
  40. data/lib/xxx_rename/data/types.rb +13 -0
  41. data/lib/xxx_rename/errors.rb +28 -0
  42. data/lib/xxx_rename/file_scanner.rb +49 -0
  43. data/lib/xxx_rename/file_utilities.rb +38 -0
  44. data/lib/xxx_rename/filename_generator.rb +173 -0
  45. data/lib/xxx_rename/integrations/base.rb +20 -0
  46. data/lib/xxx_rename/integrations/stash_app.rb +316 -0
  47. data/lib/xxx_rename/log.rb +26 -0
  48. data/lib/xxx_rename/migration_client.rb +139 -0
  49. data/lib/xxx_rename/processed_file.rb +203 -0
  50. data/lib/xxx_rename/search.rb +166 -0
  51. data/lib/xxx_rename/site_client_matcher.rb +299 -0
  52. data/lib/xxx_rename/site_clients/adult_time.rb +31 -0
  53. data/lib/xxx_rename/site_clients/algolia_common.rb +48 -0
  54. data/lib/xxx_rename/site_clients/algolia_v2.rb +181 -0
  55. data/lib/xxx_rename/site_clients/babes.rb +15 -0
  56. data/lib/xxx_rename/site_clients/base.rb +61 -0
  57. data/lib/xxx_rename/site_clients/blacked.rb +12 -0
  58. data/lib/xxx_rename/site_clients/blacked_raw.rb +12 -0
  59. data/lib/xxx_rename/site_clients/brazzers.rb +15 -0
  60. data/lib/xxx_rename/site_clients/configuration.rb +55 -0
  61. data/lib/xxx_rename/site_clients/digital_playground.rb +15 -0
  62. data/lib/xxx_rename/site_clients/elegant_angel.rb +168 -0
  63. data/lib/xxx_rename/site_clients/errors.rb +103 -0
  64. data/lib/xxx_rename/site_clients/evil_angel.rb +59 -0
  65. data/lib/xxx_rename/site_clients/goodporn.rb +109 -0
  66. data/lib/xxx_rename/site_clients/jules_jordan.rb +22 -0
  67. data/lib/xxx_rename/site_clients/jules_jordan_media.rb +175 -0
  68. data/lib/xxx_rename/site_clients/manuel_ferrara.rb +24 -0
  69. data/lib/xxx_rename/site_clients/mg_premium.rb +247 -0
  70. data/lib/xxx_rename/site_clients/mofos.rb +15 -0
  71. data/lib/xxx_rename/site_clients/naughty_america.rb +272 -0
  72. data/lib/xxx_rename/site_clients/nfbusty.rb +84 -0
  73. data/lib/xxx_rename/site_clients/query_generator/base.rb +89 -0
  74. data/lib/xxx_rename/site_clients/query_generator/evil_angel.rb +36 -0
  75. data/lib/xxx_rename/site_clients/query_generator/goodporn.rb +27 -0
  76. data/lib/xxx_rename/site_clients/query_generator/mg_premium.rb +26 -0
  77. data/lib/xxx_rename/site_clients/query_generator/naughty_america.rb +24 -0
  78. data/lib/xxx_rename/site_clients/query_generator/stash_db.rb +21 -0
  79. data/lib/xxx_rename/site_clients/query_generator/vixen.rb +27 -0
  80. data/lib/xxx_rename/site_clients/query_generator/whale.rb +39 -0
  81. data/lib/xxx_rename/site_clients/reality_kings.rb +14 -0
  82. data/lib/xxx_rename/site_clients/stash_db.rb +257 -0
  83. data/lib/xxx_rename/site_clients/tushy.rb +12 -0
  84. data/lib/xxx_rename/site_clients/tushy_raw.rb +12 -0
  85. data/lib/xxx_rename/site_clients/twistys.rb +15 -0
  86. data/lib/xxx_rename/site_clients/vixen.rb +12 -0
  87. data/lib/xxx_rename/site_clients/vixen_media.rb +130 -0
  88. data/lib/xxx_rename/site_clients/whale.rb +106 -0
  89. data/lib/xxx_rename/site_clients/wicked.rb +52 -0
  90. data/lib/xxx_rename/site_clients/x_empire.rb +51 -0
  91. data/lib/xxx_rename/site_clients/zero_tolerance.rb +36 -0
  92. data/lib/xxx_rename/utils.rb +81 -0
  93. data/lib/xxx_rename/version.rb +5 -0
  94. data/lib/xxx_rename.rb +60 -0
  95. data/output.png +0 -0
  96. data/xxx_rename.gemspec +42 -0
  97. 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