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,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/site_clients/query_generator/stash_db"
4
+
5
+ module XxxRename
6
+ module SiteClients
7
+ class StashDb < Base
8
+ include HTTParty
9
+ include Utils
10
+
11
+ base_uri "https://stashdb.org"
12
+ headers "Content-Type" => "application/json"
13
+
14
+ site_client_name :stash
15
+
16
+ LOGIN_ENDPOINT = "/login"
17
+ GRAPHQL_ENDPOINT = "/graphql"
18
+
19
+ attr_reader :username, :password
20
+
21
+ def initialize(config)
22
+ @cookie_set = false
23
+ @api_key_set = false
24
+ super(config)
25
+ end
26
+
27
+ # @param [String] filename
28
+ # @return [XxxRename::Data::SceneData, nil]
29
+ def search(filename)
30
+ setup_credentials! if login_required?
31
+
32
+ match = search_query(filename)
33
+ search_string = if match.female_actors.empty?
34
+ match.title
35
+ else
36
+ "#{match.female_actors.join(", ")} - #{match.title}"
37
+ end
38
+ search_results = graphql_search(search_string, 10)
39
+ find_result_in_search_resp(search_results, match)
40
+ end
41
+
42
+ def setup_credentials!
43
+ if username_password_provided?
44
+ login_using_credentials(site_config.username, site_config.password)
45
+ elsif api_key_provided?
46
+ register_api_key(site_config.api_token)
47
+ validate_credentials!
48
+ else
49
+ raise Errors::InvalidCredentialsError, self.class.name
50
+ end
51
+ end
52
+
53
+ def actor_details(actor)
54
+ return unless credentials_provided?
55
+
56
+ setup_credentials! if login_required?
57
+ response = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: actor_search_query(actor)) }
58
+ response.dig("data", "searchPerformer")&.select { |x| match?(x["name"], actor) }&.first
59
+ end
60
+
61
+ private
62
+
63
+ def login_using_credentials(username, password)
64
+ body = { username: username, password: password }
65
+ resp = handle_response!(return_raw: true) { self.class.post(LOGIN_ENDPOINT, body: body, multipart: true) }
66
+ cookie_hash = HTTParty::CookieHash.new
67
+ resp.get_fields("Set-Cookie").each { |c| cookie_hash.add_cookies(c) }
68
+ @cookie_set = true
69
+ cookie_s = cookie_hash.to_cookie_string
70
+ self.class.headers "cookie" => cookie_s
71
+ cookie_s
72
+ end
73
+
74
+ def login_required?
75
+ true unless @cookie_set || @api_key_set
76
+ end
77
+
78
+ def register_api_key(api_key)
79
+ @api_key_set = true
80
+ self.class.headers "ApiKey" => api_key
81
+ end
82
+
83
+ def credentials_provided?
84
+ username_password_provided? || api_key_provided?
85
+ end
86
+
87
+ def username_password_provided?
88
+ [site_config.username, site_config.password].map(&:to_s).map(&:presence).all?(String)
89
+ end
90
+
91
+ def api_key_provided?
92
+ site_config.api_token.to_s.presence.is_a?(String)
93
+ end
94
+
95
+ def validate_credentials!
96
+ body = {
97
+ operationName: "Version",
98
+ query: <<~GRAPHQL
99
+ query Version {
100
+ version {
101
+ version
102
+ }
103
+ }
104
+ GRAPHQL
105
+ }.to_json
106
+ resp = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: body) }
107
+ resp.dig("data", "version", "version")
108
+ end
109
+
110
+ # @param [String] filename
111
+ # @return [XxxRename::SiteClients::QueryGenerator::Base::SearchParameters]
112
+ # @raise XxxRename::Errors::NoMatchError
113
+ def search_query(filename)
114
+ resp = SiteClients::QueryGenerator::StashDb.generate(filename, source_format)
115
+
116
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) if resp.nil?
117
+
118
+ resp
119
+ end
120
+
121
+ # @param [String] term
122
+ # @param [Integer] limit
123
+ # @return [Array[Hash]]
124
+ # @raise XxxRename::SearchError
125
+ def graphql_search(term, limit)
126
+ resp = self.class.post(GRAPHQL_ENDPOINT, body: grapql_body(term, limit))
127
+ if resp.code != 200
128
+ opts = { request_options: nil, response_code: resp.code, response_body: resp.body }
129
+ raise Errors::SearchError.new(term, opts)
130
+ end
131
+
132
+ resp.parsed_response.dig("data", "searchScene") || []
133
+ end
134
+
135
+ def find_result_in_search_resp(api_resp, match_data)
136
+ matched_scene = api_resp.select do |x|
137
+ condition1 = match?(x["title"], match_data.title) || match?(x.dig("studio", "name"), match_data.title)
138
+ condition2 = female_actors_included?(x, match_data.female_actors)
139
+ condition1 && condition2
140
+ end.first
141
+
142
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, match_data.title) if matched_scene.nil?
143
+
144
+ Data::SceneData.new(
145
+ female_actors: female_actors(matched_scene),
146
+ male_actors: male_actors(matched_scene),
147
+ actors: female_actors(matched_scene) + male_actors(matched_scene),
148
+ id: matched_scene["id"],
149
+ collection_tag: site_config.collection_tag,
150
+ collection: matched_scene.dig("studio", "name"),
151
+ title: matched_scene["title"],
152
+ date_released: Time.strptime(matched_scene["date"], "%Y-%m-%d")
153
+ )
154
+ end
155
+
156
+ # @param [Hash] scene
157
+ # @return [Array[String]]
158
+ def female_actors(scene)
159
+ actors(scene)
160
+ .select { |x| x["gender"] == "FEMALE" }
161
+ .map { |x| x["name"] }
162
+ end
163
+
164
+ # @param [Hash] scene
165
+ # @return [Array[String]]
166
+ def male_actors(scene)
167
+ actors(scene)
168
+ .select { |x| x["gender"] == "MALE" }
169
+ .map { |x| x["name"] }
170
+ end
171
+
172
+ # Return an Array of Array[Actor]. The first element of the array main actor name and the others are aliases
173
+ # @param [Hash] scene
174
+ # @return [Array[Array[String]]]
175
+ def female_actors_arr(scene)
176
+ actors(scene)
177
+ .select { |x| x["gender"] == "FEMALE" }
178
+ .map { |x| [x["name"]] + x["aliases"] }
179
+ end
180
+
181
+ # Return an Array of Array[Actor]. The first element of the array main actor name and the others are aliases
182
+ # @param [Hash] scene
183
+ # @return [Array[Array[String]]]
184
+ def male_actors_hash(scene)
185
+ actors(scene)
186
+ .select { |x| x["gender"] == "FEMALE" }
187
+ .map { |x| [x["name"]] + x["aliases"] }
188
+ end
189
+
190
+ # @param [Hash] scene
191
+ # @return [Hash]
192
+ def actors(scene)
193
+ scene["performers"]
194
+ .map { |x| x["performer"] }
195
+ .map { |x| x.slice("name", "gender", "aliases") }
196
+ end
197
+
198
+ # @param [Hash] scene
199
+ # @param [Array[String]] female_actors
200
+ def female_actors_included?(scene, female_actors)
201
+ female_actors_search = female_actors_arr(scene).flatten.map(&:normalize).to_set
202
+ rej = female_actors.reject do |actor|
203
+ female_actors_search.member? actor.normalize
204
+ end
205
+ rej.empty?
206
+ end
207
+
208
+ # @return [Hash{Symbol->Unknown}]
209
+ # @param [String] term
210
+ # @param [Integer] limit
211
+ def grapql_body(term, limit)
212
+ {
213
+ operationName: "SearchAll",
214
+ variables:
215
+ { limit: limit,
216
+ term: term },
217
+ query: <<~GRAPHQL
218
+ query SearchAll($term: String!, $limit: Int = 5) {
219
+ searchScene(term: $term, limit: $limit) {
220
+ id
221
+ date
222
+ title
223
+ studio {
224
+ name
225
+ }
226
+ performers {
227
+ as
228
+ performer {
229
+ id
230
+ name
231
+ gender
232
+ aliases
233
+ }
234
+ }
235
+ }
236
+ }
237
+ GRAPHQL
238
+ }.to_json
239
+ end
240
+
241
+ def actor_search_query(actor)
242
+ {
243
+ operationName: "SearchPerformers",
244
+ variables: { term: actor },
245
+ query: <<~GRAPHQL
246
+ query SearchPerformers($term: String!, $limit: Int = 5) {
247
+ searchPerformer(term: $term, limit: $limit) {
248
+ name
249
+ gender
250
+ }
251
+ }
252
+ GRAPHQL
253
+ }.to_json
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/site_clients/vixen_media"
4
+
5
+ module XxxRename
6
+ module SiteClients
7
+ class Tushy < VixenMedia
8
+ base_uri "https://www.tushy.com"
9
+ site_client_name :tushy
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/site_clients/vixen_media"
4
+
5
+ module XxxRename
6
+ module SiteClients
7
+ class TushyRaw < VixenMedia
8
+ base_uri "https://www.tushyraw.com"
9
+ site_client_name :tushy_raw
10
+ end
11
+ end
12
+ 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 Twistys < MGPremium
8
+ site_client_name :twistys
9
+
10
+ def initialize(config)
11
+ super(config, site_url: "https://www.twistys.com")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/site_clients/vixen_media"
4
+
5
+ module XxxRename
6
+ module SiteClients
7
+ class Vixen < VixenMedia
8
+ base_uri "https://www.vixen.com"
9
+ site_client_name :vixen
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/site_clients/query_generator/vixen"
4
+
5
+ module XxxRename
6
+ module SiteClients
7
+ class VixenMedia < Base
8
+ include HTTParty
9
+ include Utils
10
+
11
+ headers "content-type" => "application/json"
12
+
13
+ GRAPHQL_ENDPOINT = "/graphql"
14
+
15
+ def search(filename)
16
+ match = SiteClients::QueryGenerator::Vixen.generate(filename, source_format)
17
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) if match.nil?
18
+
19
+ if match.id && match.collection
20
+ get_video_by_id(match.id, match.collection)
21
+ elsif match.title && match.actors
22
+ get_video_by_metadata(match.title, match.actors)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def get_video_by_id(video_id, site)
29
+ api_resp = self.class.post(GRAPHQL_ENDPOINT, body: body(video_id, site))
30
+ resp = handle_response! { api_resp }
31
+
32
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, "#{site}-#{video_id}") if resp.dig("data", "findOneVideo").nil?
33
+
34
+ Data::SceneData.new(
35
+ {
36
+ collection: site,
37
+ collection_tag: site_config.collection_tag,
38
+ title: resp.dig("data", "findOneVideo", "title"),
39
+ id: resp.dig("data", "findOneVideo", "videoId").to_s,
40
+ date_released: Time.parse(resp.dig("data", "findOneVideo", "releaseDate"))
41
+ }.merge(actors_hash(resp.dig("data", "findOneVideo", "modelsSlugged").map { |m| m["name"] }))
42
+ )
43
+ end
44
+
45
+ def get_video_by_metadata(title, actors)
46
+ req_body = search_results_body("#{title} #{actors.join(", ")}")
47
+ api_resp = handle_response! { self.class.post(GRAPHQL_ENDPOINT, body: req_body) }
48
+
49
+ search_results = api_resp.dig("data", "searchVideos", "edges")
50
+ search_results.map do |search_result_node|
51
+ search_result = search_result_node["node"]
52
+ next if search_result.nil? || !scene_match?(search_result, title, actors)
53
+
54
+ return Data::SceneData.new(
55
+ {
56
+ collection: site,
57
+ collection_tag: site_config.collection_tag,
58
+ title: search_result["title"],
59
+ id: search_result["videoId"].to_s,
60
+ date_released: Time.parse(search_result["releaseDate"])
61
+ }.merge(actors_hash(search_result["modelsSlugged"].map { |m| m["name"] }))
62
+ )
63
+ end
64
+ end
65
+
66
+ def scene_match?(search_result, title, actors)
67
+ actors_n = actors.map(&:normalize).to_set
68
+ res_actors_n = search_result["modelsSlugged"].map { |x| x["name"].normalize }.to_set
69
+ match?(search_result["title"], title) && actors_n.subset?(res_actors_n)
70
+ end
71
+
72
+ # Generate the POST body
73
+ #
74
+ # @return [JSON]
75
+ def body(video_id, site)
76
+ {
77
+ "operationName": "getVideo",
78
+ "variables": {
79
+ "videoId": video_id,
80
+ "site": site
81
+ },
82
+ "query": <<~GRAPHQL
83
+ query getVideo($videoId: ID, $site: Site) {
84
+ findOneVideo(input: { videoId: $videoId, site: $site }) {
85
+ id: uuid
86
+ videoId
87
+ title
88
+ releaseDate
89
+ modelsSlugged: models {
90
+ name
91
+ }
92
+ }
93
+ }
94
+ GRAPHQL
95
+ }.to_json
96
+ end
97
+
98
+ def search_results_body(query)
99
+ {
100
+ "operationName": "getSearchResults",
101
+ "variables": {
102
+ "query": query,
103
+ "site": site,
104
+ "first": 5
105
+ },
106
+ "query": <<~GRAPHQL
107
+ query getSearchResults($query: String!, $site: Site!, $first: Int) {
108
+ searchVideos(input: { query: $query, site: $site, first: $first }) {
109
+ edges {
110
+ node {
111
+ videoId
112
+ title
113
+ releaseDate
114
+ modelsSlugged: models {
115
+ name
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ GRAPHQL
122
+ }.to_json
123
+ end
124
+
125
+ def site
126
+ self.class.site_client_name.to_s.upcase.gsub("_", "")
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ require "xxx_rename/site_clients/query_generator/whale"
6
+
7
+ module XxxRename
8
+ module SiteClients
9
+ class Whale < Base
10
+ include HTTParty
11
+
12
+ URL_MAPPER = {
13
+ nannyspy: "https://nannyspy.com",
14
+ spyfam: "https://spyfam.com",
15
+ holed: "https://holed.com",
16
+ lubed: "https://lubed.com",
17
+ myveryfirsttime: "https://myveryfirsttime.com",
18
+ tiny4k: "https://tiny4k.com",
19
+ povd: "https://povd.com",
20
+ fantasyhd: "https://fantasyhd.com",
21
+ castingcouchx: "https://castingcouch-x.com",
22
+ puremature: "https://puremature.com",
23
+ passionhd: "https://passion-hd.com",
24
+ exotic4k: "https://exotic4k.com"
25
+ }.freeze
26
+
27
+ site_client_name :whale_media
28
+
29
+ # @param [String] filename
30
+ # @return [Hash, NilClass]
31
+ def search(filename)
32
+ match = SiteClients::QueryGenerator::Whale.generate(filename, source_format)
33
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) if match.nil?
34
+
35
+ unless URL_MAPPER.key?(match[:collection])
36
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_CUSTOM,
37
+ "Unknown site collection #{match[:collection]}")
38
+ end
39
+
40
+ url = make_url(match)
41
+ fetch_details_from_html(url)
42
+ end
43
+
44
+ private
45
+
46
+ # @param [String] url
47
+ # @return [Hash, NilClass]
48
+ def fetch_details_from_html(url)
49
+ XxxRename.logger.debug "Scraping data from #{url.to_s.colorize(:blue)}"
50
+ web_resp = HTTParty.get(url, follow_redirects: false)
51
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, url) unless web_resp.code == 200
52
+
53
+ search_string = url.split("/").last
54
+ parse_scene_details(web_resp.body, search_string)
55
+ end
56
+
57
+ # @param [Nokogiri::HTML::Document] body
58
+ # @param [String] search_string
59
+ # @return [Hash, NilClass]
60
+ def parse_scene_details(body, search_string)
61
+ doc = Nokogiri::HTML(body)
62
+ normalised_title = title(doc).downcase.gsub("-", "").gsub(/['"“”‘’„]/, "-").gsub(/[^\s\w-]/, "").gsub(/\s{2,}/, " ").gsub(
63
+ /\s/, "-"
64
+ )
65
+ Data::SceneData.new(
66
+ female_actors: female_actors(doc),
67
+ male_actors: [],
68
+ actors: female_actors(doc),
69
+ collection: collection(doc),
70
+ collection_tag: site_config.collection_tag,
71
+ title: title(doc),
72
+ id: search_string.gsub(normalised_title, ""), # TODO: This doesn't work some times
73
+ date_released: nil
74
+ )
75
+ end
76
+
77
+ # @param [Nokogiri::XML::NodeSet] doc
78
+ # @return [String]
79
+ def title(doc)
80
+ doc.css(".t2019-stitle").text.strip
81
+ end
82
+
83
+ # @param [Nokogiri::XML::NodeSet] doc
84
+ # @return [Array[String]]
85
+ def female_actors(doc)
86
+ doc.css("#t2019-models").css(".badge").map { |x| x.text.strip }.sort
87
+ end
88
+
89
+ # @param [Nokogiri::XML::NodeSet] doc
90
+ # @return [String]
91
+ def collection(doc)
92
+ doc.css("#navigation").css(".my-0").css("a").map { |x| x["alt"] }.first
93
+ end
94
+
95
+ # @param [XxxRename::SiteClients::QueryGenerator::Base::SearchParameters] match
96
+ # @return [String]
97
+ def make_url(match)
98
+ File.join(
99
+ URL_MAPPER[match[:collection]],
100
+ "video",
101
+ match[:title] + match[:id].to_s
102
+ )
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,52 @@
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 Wicked < 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 :wicked
16
+
17
+ def initialize(config)
18
+ @site_url = "https://www.wicked.com"
19
+ super(config)
20
+ end
21
+
22
+ def search(filename)
23
+ match = QueryGenerator::Base.generic_generate(filename, source_format)
24
+ title = match&.title.presence || filename.split("_").first&.titleize_custom
25
+
26
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) unless title.presence
27
+
28
+ resp = fetch_scenes_from_api(title)
29
+ find_matched_scene!(resp, title)
30
+ end
31
+
32
+ def actor_details(actor)
33
+ response = fetch_actor_from_api(actor)
34
+ response&.select { |x| match? actor, x[:name] }
35
+ &.first
36
+ &.slice(:name, :gender)
37
+ &.transform_keys(&:to_s) # Backwards compatibility. Keys should be strings.
38
+ end
39
+
40
+ private
41
+
42
+ def find_matched_scene!(search_results, title)
43
+ scenes = search_results.reject { |x| x[:release_date].nil? }
44
+ .select { |x| match?(x[:title], title) }
45
+
46
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_RESULT, title) if scenes.length != 1
47
+
48
+ make_scene_data(scenes.first)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/site_clients/algolia_v2"
4
+ require "xxx_rename/site_clients/query_generator/evil_angel"
5
+
6
+ module XxxRename
7
+ module SiteClients
8
+ class XEmpire < 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 :x_empire
16
+
17
+ def initialize(config)
18
+ @site_url = "https://www.hardx.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
+ private
34
+
35
+ def processed_scene_details(match)
36
+ resp = fetch_scenes_from_api(match.title)
37
+
38
+ find_matched_scene!(resp, match)
39
+ end
40
+
41
+ def unprocessed_scene_details(match)
42
+ combined = [
43
+ fetch_scenes_from_api(match.title),
44
+ fetch_scenes_from_api("#{match.title} Scene #{match.index}")
45
+ ].reduce([], :concat)
46
+
47
+ find_matched_scene!(combined, match)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
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 ZeroTolerance < AlgoliaV2
9
+ SCENES_INDEX_NAME = "all_scenes"
10
+ MOVIES_INDEX_NAME = "all_movies"
11
+
12
+ site_client_name :zero_tolerance
13
+
14
+ CDN_BASE_URL = "https://transform.gammacdn.com"
15
+
16
+ def initialize(config)
17
+ @site_url = "https://www.zerotolerancefilms.com"
18
+ super(config)
19
+ end
20
+
21
+ def search(filename)
22
+ match = SiteClients::QueryGenerator::Base.generic_generate(filename, source_format)
23
+ raise Errors::NoMatchError.new(Errors::NoMatchError::ERR_NO_METADATA, filename) unless match&.title&.presence
24
+
25
+ search_results = search_scene_using_title(match.title)
26
+ find_matched_scene!(search_results, match)
27
+ end
28
+
29
+ private
30
+
31
+ def search_scene_using_title(title)
32
+ with_retry { scenes_index.search(title, default_query)&.[](:hits) }
33
+ end
34
+ end
35
+ end
36
+ end