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,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,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,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
|