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