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,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/contract/file_rename_op_contract"
4
+ require "xxx_rename/data/file_rename_op_datastore"
5
+ require "xxx_rename/site_client_matcher"
6
+ require "xxx_rename/actions/resolver"
7
+
8
+ module XxxRename
9
+ class Client
10
+ #
11
+ # Rename file(s) in the current directory
12
+ #
13
+ # @param [Data::Config] config
14
+ # @param [Boolean] verbose
15
+ # @param [Symbol] override_site
16
+ # @param [Boolean] nested
17
+ # @param [String, Nil] checkpoint
18
+ def initialize(config, verbose:, override_site: nil, nested: false, checkpoint: nil)
19
+ @config = config
20
+ @override_site = override_site
21
+ @nested = nested
22
+ @checkpoint = checkpoint
23
+ @checkpoint_reached = false
24
+ XxxRename.logger(verbose: verbose)
25
+ end
26
+
27
+ def generate(object, &block)
28
+ @object = object
29
+ @custom_action = block
30
+ if File.directory?(object)
31
+ process_directory
32
+ return
33
+ end
34
+
35
+ if File.file?(object)
36
+ process_file(object)
37
+ return
38
+ end
39
+
40
+ raise Errors::FatalError, "[UNKNOWN OBJECT #{object}] pass a valid file or directory"
41
+ end
42
+
43
+ def matcher
44
+ @matcher ||=
45
+ begin
46
+ m = SiteClientMatcher.new(config, override_site: override_site)
47
+ ActorsHelper.instance.matcher(m)
48
+ m
49
+ end
50
+ end
51
+
52
+ def resolver
53
+ @resolver ||= Actions::Resolver.new(config)
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :config, :override_site, :nested, :object
59
+
60
+ def process_directory
61
+ scanner.each do |file|
62
+ if @checkpoint
63
+ if @checkpoint != file && !@checkpoint_reached
64
+ XxxRename.logger.debug "[SKIP CHECKPOINT] #{file}"
65
+ next
66
+ elsif @checkpoint == file
67
+ XxxRename.logger.info "[REACHED CHECKPOINT] #{file}"
68
+ @checkpoint_reached = true
69
+ end
70
+ end
71
+ process_file(file)
72
+ end
73
+ end
74
+
75
+ def process_file(file)
76
+ path = relative_path(file)
77
+ file = File.basename(file)
78
+
79
+ Dir.chdir(path) do
80
+ XxxRename.logger.info "#{"[FILE SCAN]".colorize(:blue)} #{file}"
81
+ search_engine.search(file) do |search_result|
82
+ next if search_result&.empty?
83
+
84
+ config.actions.each do |action_str|
85
+ action = resolver.resolve!(action_str)
86
+ action.perform(Dir.pwd, file, search_result)
87
+ end
88
+
89
+ next unless @custom_action
90
+
91
+ @custom_action.call(Dir.pwd, file, search_result)
92
+ end
93
+ end
94
+ end
95
+
96
+ def search_engine
97
+ @search_engine ||= Search.new(matcher, config.scene_datastore, config.force_refresh)
98
+ end
99
+
100
+ def scanner
101
+ @scanner ||= FileScanner.new(@object, nested: @nested)
102
+ end
103
+
104
+ def relative_path(file)
105
+ return Dir.pwd if File.basename(file) == file
106
+
107
+ file.gsub(File.basename(file), "")
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ module Constants
5
+ VIDEO_EXTENSIONS = %w[m4v mp4 mov wmv avi mpg mpeg rmvb rm flv asf mkv webm].to_set.freeze
6
+
7
+ DEFAULT_HEADERS = {
8
+ "User-Agent" => "Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion"
9
+ }.freeze
10
+
11
+ EVIL_ANGEL_ORIGINAL_FILE_PATTERN = /
12
+ ^ # Start of filename
13
+ (?<title>[a-zA-z0-9-]+) # Name of movie
14
+ _s(?<index>\d{1,2}) # Scene index e.g. s01
15
+ _ # Underscore
16
+ (?<actors>[a-zA-Z_]+) # Actors in format ActorName,ActorName
17
+ _\d{3,4}p # Scene Resolution
18
+ \.\w+ # File extension
19
+ $ # End of filename
20
+ /x.freeze
21
+
22
+ GOODPORN_ORIGINAL_FILE_FORMAT =
23
+ /
24
+ ^ # Start of filename
25
+ [a-z0-9-]+ # Collection + Title
26
+ - # Hyphen
27
+ \d{2}-\d{2}-\d{4} # Release Date
28
+ (_(480|720|1080)p)? # Scene Resolution
29
+ \.\w+ # File extension
30
+ $ # End of filename
31
+ /x.freeze
32
+
33
+ MG_PREMIUM_ORIGINAL_FILE_FORMAT =
34
+ /
35
+ ^ # Start of filename
36
+ (?<title>[a-z\-\d]+).*(?<!\d{2}-\d{2}-\d{4}) # Scene Title (should not contain a date format \d{2}-\d{2}-\d{4})
37
+ _ # Underscore
38
+ \d{3,4}p # Resolution
39
+ \.\w{3,4} # File extension
40
+ $ # End of filename
41
+ /x.freeze
42
+
43
+ NAUGHTY_AMERICA_ORIGINAL_FILE_REGEX =
44
+ /
45
+ ^ # Start of filename
46
+ (?<compressed_scene>[a-z1]+) # Scene Title
47
+ _ # Underscore
48
+ \d+\w+? # Resolution
49
+ \.\w{0,4} # File extension
50
+ $ # End of filename
51
+ /x.freeze
52
+
53
+ NF_BUSTY_ORIGINAL_FILE_REGEX =
54
+ /
55
+ ^ # Start of filename
56
+ nfbusty_ # Prefix
57
+ (?<title>[a-z0-9_]+) # Scene Title
58
+ _ # Underscore
59
+ \d{3,4} # Resolution
60
+ \.\w{0,4} # File extension
61
+ $ # End of filename
62
+ /x.freeze
63
+
64
+ # These regexes match the files downloaded originally from Vixen Media sites
65
+ VIXEN_MEDIA_SITES = %w[DEEPER VIXEN BLACKEDRAW BLACKED TUSHYRAW TUSHY CHANNELS SLAYED].join("|")
66
+ VIXEN_MEDIA_ORIGINAL_FILE_REGEX_1 = /(?<collection>(#{VIXEN_MEDIA_SITES}))_(?<id>\d*)_\d{3,4}P/x.freeze
67
+ VIXEN_MEDIA_ORIGINAL_FILE_REGEX_2 = /(?<collection>(#{VIXEN_MEDIA_SITES}))_(?<id>\d{6})-.*_\d{3,4}P/x.freeze
68
+
69
+ # rubocop:disable Lint/MixedRegexpCaptureTypes
70
+ WHALE_ORIGINAL_FILE_PATTERN =
71
+ /
72
+ ^ # Start of filename
73
+ (?<site>(nannyspy|spyfam|holed|lubed|myveryfirsttime|tiny4k|povd|fantasyhd|castingcouchx|puremature|passionhd|exotic4k)) # Sitename
74
+ - # Hyphen
75
+ (?<title>[a-z0-9-]+) # Scene Title (might contain random id strings)
76
+ - # Hyphen separator for resolution
77
+ \d{3,4} # Resolution
78
+ \.\w+ # File extension
79
+ $ # End of filename
80
+ /x.freeze
81
+ # rubocop:enable Lint/MixedRegexpCaptureTypes
82
+ end
83
+
84
+ module SystemConstants
85
+ def home_dir
86
+ ENV["HOME"]
87
+ end
88
+
89
+ def config_file_lookup_dirs
90
+ [
91
+ File.join(home_dir, ".config", "xxx_rename"),
92
+ File.join(home_dir, "xxx_rename")
93
+ ]
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XxxRename
4
+ module Contract
5
+ class ConfigContract < Dry::Validation::Contract
6
+ include FileUtilities
7
+ option :filename_generator
8
+
9
+ SITE_CONFIG = Dry::Schema.JSON do
10
+ required(:collection_tag).value(Types::SanitizedString)
11
+ required(:output_format).value(array[Types::SanitizedString])
12
+ required(:file_source_format).value(array[Types::SanitizedString])
13
+ end
14
+
15
+ JULES_JORDAN_MEDIA_CONFIG = Dry::Schema.JSON do
16
+ required(:collection_tag).value(Types::SanitizedString)
17
+ required(:output_format).value(array[Types::SanitizedString])
18
+ required(:file_source_format).value(array[Types::SanitizedString])
19
+ optional(:cookie_file).maybe(Types::SanitizedString)
20
+ end
21
+
22
+ STASH_CONFIG = Dry::Schema.JSON do
23
+ required(:collection_tag).value(Types::SanitizedString)
24
+ optional(:username).maybe(Types::SanitizedString)
25
+ optional(:password).maybe(Types::SanitizedString)
26
+ optional(:api_token).maybe(Types::SanitizedString)
27
+ required(:output_format).value(array[Types::SanitizedString])
28
+ required(:file_source_format).value(array[Types::SanitizedString])
29
+ end
30
+
31
+ NAUGHTY_AMERICA_CONFIG = Dry::Schema.JSON do
32
+ # if database is not provided, it will be created automatically
33
+ optional(:database).filled(Types::SanitizedString)
34
+ required(:collection_tag).value(Types::SanitizedString)
35
+ required(:output_format).value(array[Types::SanitizedString])
36
+ required(:file_source_format).value(array[Types::SanitizedString])
37
+ end
38
+
39
+ NF_BUSTY_CONFIG = Dry::Schema.JSON do
40
+ # if database is not provided, it will be created automatically
41
+ optional(:database).filled(Types::SanitizedString)
42
+ required(:collection_tag).value(Types::SanitizedString)
43
+ required(:output_format).value(array[Types::SanitizedString])
44
+ required(:file_source_format).value(array[Types::SanitizedString])
45
+ end
46
+
47
+ # rubocop:disable Metrics/BlockLength
48
+ json do
49
+ required(:generated_files_dir).filled(:string)
50
+ optional(:force_refresh_datastore).filled(Types::Bool)
51
+ optional(:force_refresh).filled(Types::Bool)
52
+ optional(:actions).value(Types::Array.of(Types::String))
53
+ optional(:override_site).value(Types::String)
54
+
55
+ optional(:stash_app).hash do
56
+ optional(:url).value(Types::SanitizedString)
57
+ optional(:api_token).maybe(Types::SanitizedString)
58
+ end
59
+
60
+ required(:global).hash do
61
+ optional(:female_actors_prefix).filled(Types::SanitizedString)
62
+ optional(:male_actors_prefix).filled(Types::SanitizedString)
63
+ optional(:actors_prefix).filled(Types::SanitizedString)
64
+ optional(:title_prefix).filled(Types::SanitizedString)
65
+ optional(:id_prefix).filled(Types::SanitizedString)
66
+ optional(:output_format).value(array[Types::SanitizedString])
67
+ end
68
+
69
+ required(:site).hash do
70
+ optional(:adult_time).hash(SITE_CONFIG)
71
+ optional(:babes).hash(SITE_CONFIG)
72
+ optional(:blacked).hash(SITE_CONFIG)
73
+ optional(:blacked_raw).hash(SITE_CONFIG)
74
+ optional(:brazzers).hash(SITE_CONFIG)
75
+ optional(:digital_playground).hash(SITE_CONFIG)
76
+ optional(:elegant_angel).hash(SITE_CONFIG)
77
+ optional(:evil_angel).hash(SITE_CONFIG)
78
+ optional(:goodporn).hash(SITE_CONFIG)
79
+ optional(:jules_jordan).hash(JULES_JORDAN_MEDIA_CONFIG)
80
+ optional(:manuel_ferrara).hash(JULES_JORDAN_MEDIA_CONFIG)
81
+ optional(:mofos).hash(SITE_CONFIG)
82
+ optional(:naughty_america).hash(NAUGHTY_AMERICA_CONFIG)
83
+ optional(:nf_busty).hash(NF_BUSTY_CONFIG)
84
+ optional(:reality_kings).hash(SITE_CONFIG)
85
+ optional(:stash).hash(STASH_CONFIG)
86
+ optional(:tushy).hash(SITE_CONFIG)
87
+ optional(:tushy_raw).hash(SITE_CONFIG)
88
+ optional(:twistys).hash(SITE_CONFIG)
89
+ optional(:vixen).hash(SITE_CONFIG)
90
+ optional(:whale_media).hash(SITE_CONFIG)
91
+ optional(:wicked).hash(SITE_CONFIG)
92
+ optional(:x_empire).hash(SITE_CONFIG)
93
+ optional(:zero_tolerance).hash(SITE_CONFIG)
94
+ end
95
+ end
96
+ # rubocop:enable Metrics/BlockLength
97
+
98
+ rule(:generated_files_dir) do
99
+ key.failure("does not exist or is not readable") unless valid_dir?(value)
100
+ end
101
+
102
+ rule("global.female_actors_prefix") { key.failure(INVALID_PREFIX_MSG) unless prefix_valid?(value) }
103
+ rule("global.male_actors_prefix") { key.failure(INVALID_PREFIX_MSG) unless prefix_valid?(value) }
104
+ rule("global.actors_prefix") { key.failure(INVALID_PREFIX_MSG) unless prefix_valid?(value) }
105
+ rule("global.title_prefix") { key.failure(INVALID_PREFIX_MSG) unless prefix_valid?(value) }
106
+ rule("global.id_prefix") { key.failure(INVALID_PREFIX_MSG) unless prefix_valid?(value) }
107
+
108
+ # Validate output format
109
+ rule("global.output_format") { validate_format!(key, value) }
110
+ rule("site.adult_time.output_format") { validate_format!(key, value) }
111
+ rule("site.babes.output_format") { validate_format!(key, value) }
112
+ rule("site.blacked.output_format") { validate_format!(key, value) }
113
+ rule("site.blacked_raw.output_format") { validate_format!(key, value) }
114
+ rule("site.brazzers.output_format") { validate_format!(key, value) }
115
+ rule("site.digital_playground.output_format") { validate_format!(key, value) }
116
+ rule("site.elegant_angel.output_format") { validate_format!(key, value) }
117
+ rule("site.evil_angel.output_format") { validate_format!(key, value) }
118
+ rule("site.jules_jordan.output_format") { validate_format!(key, value) }
119
+ rule("site.manuel_ferrara.output_format") { validate_format!(key, value) }
120
+ rule("site.mofos.output_format") { validate_format!(key, value) }
121
+ rule("site.naughty_america.output_format") { validate_format!(key, value) }
122
+ rule("site.nf_busty.output_format") { validate_format!(key, value) }
123
+ rule("site.reality_kings.output_format") { validate_format!(key, value) }
124
+ rule("site.stash.output_format") { validate_format!(key, value) }
125
+ rule("site.tushy.output_format") { validate_format!(key, value) }
126
+ rule("site.tushy_raw.output_format") { validate_format!(key, value) }
127
+ rule("site.twistys.output_format") { validate_format!(key, value) }
128
+ rule("site.vixen.output_format") { validate_format!(key, value) }
129
+ rule("site.whale_media.output_format") { validate_format!(key, value) }
130
+ rule("site.wicked.output_format") { validate_format!(key, value) }
131
+ rule("site.x_empire.output_format") { validate_format!(key, value) }
132
+ rule("site.zero_tolerance.output_format") { validate_format!(key, value) }
133
+
134
+ rule("site.adult_time.file_source_format") { validate_source_format!(key, value) }
135
+ rule("site.babes.file_source_format") { validate_source_format!(key, value) }
136
+ rule("site.blacked.file_source_format") { validate_source_format!(key, value) }
137
+ rule("site.blacked_raw.file_source_format") { validate_source_format!(key, value) }
138
+ rule("site.brazzers.file_source_format") { validate_source_format!(key, value) }
139
+ rule("site.digital_playground.file_source_format") { validate_source_format!(key, value) }
140
+ rule("site.elegant_angel.file_source_format") { validate_source_format!(key, value) }
141
+ rule("site.evil_angel.file_source_format") { validate_source_format!(key, value) }
142
+ rule("site.mofos.file_source_format") { validate_source_format!(key, value) }
143
+ rule("site.jules_jordan.file_source_format") { validate_source_format!(key, value) }
144
+ rule("site.manuel_ferrara.file_source_format") { validate_source_format!(key, value) }
145
+ rule("site.naughty_america.file_source_format") { validate_source_format!(key, value) }
146
+ rule("site.nf_busty.file_source_format") { validate_source_format!(key, value) }
147
+ rule("site.reality_kings.file_source_format") { validate_source_format!(key, value) }
148
+ rule("site.stash.file_source_format") { validate_source_format!(key, value) }
149
+ rule("site.tushy.file_source_format") { validate_source_format!(key, value) }
150
+ rule("site.tushy_raw.file_source_format") { validate_source_format!(key, value) }
151
+ rule("site.twistys.file_source_format") { validate_source_format!(key, value) }
152
+ rule("site.vixen.file_source_format") { validate_source_format!(key, value) }
153
+ rule("site.whale_media.file_source_format") { validate_source_format!(key, value) }
154
+ rule("site.wicked.file_source_format") { validate_source_format!(key, value) }
155
+ rule("site.x_empire.file_source_format") { validate_source_format!(key, value) }
156
+ rule("site.zero_tolerance.file_source_format") { validate_source_format!(key, value) }
157
+ rule("site.adult_time.file_source_format",
158
+ "site.babes.file_source_format",
159
+ "site.blacked.file_source_format",
160
+ "site.blacked_raw.file_source_format",
161
+ "site.brazzers.file_source_format",
162
+ "site.digital_playground.file_source_format",
163
+ "site.elegant_angel.file_source_format",
164
+ "site.evil_angel.file_source_format",
165
+ "site.jules_jordan.file_source_format",
166
+ "site.manuel_ferrara.file_source_format",
167
+ "site.mofos.file_source_format",
168
+ "site.naughty_america.file_source_format",
169
+ "site.nf_busty.file_source_format",
170
+ "site.reality_kings.file_source_format",
171
+ "site.stash.file_source_format",
172
+ "site.tushy.file_source_format",
173
+ "site.tushy_raw.file_source_format",
174
+ "site.twistys.file_source_format",
175
+ "site.vixen.file_source_format",
176
+ "site.whale_media.file_source_format",
177
+ "site.wicked.file_source_format",
178
+ "site.x_empire.file_source_format",
179
+ "site.zero_tolerance.file_source_format") { validate_uniqueness!(key(:duplicate_source_file_format), values) }
180
+
181
+ rule("site.stash.username", "site.stash.password") do
182
+ next unless key?("site.stash.username") && key?("site.stash.password")
183
+
184
+ presence_arr = [values["site.stash.username"], values["site.stash.password"]].map(&:to_s).map(&:presence)
185
+ if presence_arr.all?(NIL)
186
+ true
187
+ elsif presence_arr.none?(NIL)
188
+ true
189
+ else
190
+ key(:stash_credentials).failure("provide both username and password if you want to use login credentials")
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ INVALID_PREFIX_MSG = "should only contain A-Z a-z 0-9 [ ] _ -"
197
+ INVALID_PREFIX_CHARACTERS = /[^\w\[\]-]/.freeze
198
+
199
+ def prefix_valid?(prefix)
200
+ !INVALID_PREFIX_CHARACTERS.match?(prefix)
201
+ end
202
+
203
+ def validate_format!(key, value)
204
+ value.each { |format| filename_generator.validate_format!(format) }
205
+ true
206
+ rescue InvalidFormatError => e
207
+ key.failure(e.message)
208
+ end
209
+
210
+ def validate_source_format!(key, value)
211
+ value.each { |format| filename_generator.validate_input_format!(format) }
212
+ true
213
+ rescue InvalidFormatError => e
214
+ key.failure(e.message)
215
+ end
216
+
217
+ def validate_uniqueness!(key, values)
218
+ source_file_formats = []
219
+ values[:site].each_value { |x| source_file_formats.push(*x[:file_source_format]) }
220
+ duplicates = find_duplicates(source_file_formats)
221
+ return true if duplicates.empty?
222
+
223
+ key.failure(duplicates.map { |x| "'#{x}'" }.join(", "))
224
+ end
225
+
226
+ def find_duplicates(ary)
227
+ frequency = {}
228
+ ary.map do |x|
229
+ frequency[x] = if frequency.key?(x)
230
+ frequency[x] + 1
231
+ else
232
+ 1
233
+ end
234
+ end
235
+ resp = []
236
+ frequency.each_pair { |key, value| resp << key if value > 1 }
237
+ resp
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xxx_rename/constants"
4
+ require "fileutils"
5
+
6
+ module XxxRename
7
+ module Contract
8
+ class ConfigGenerator
9
+ include XxxRename::FileUtilities
10
+ include SystemConstants
11
+
12
+ DEFAULT_CONFIG_FILE = "config.yml"
13
+
14
+ def initialize(options)
15
+ @options = options
16
+ generated_files_dir
17
+ end
18
+
19
+ # @return [Data::Config]
20
+ def generate!
21
+ config_hash = make_config_hash
22
+ valid_config = validate_download_filters!(config_hash)
23
+ Data::Config.new(valid_config)
24
+ end
25
+
26
+ # Step 1: Get the base configuration hash with default values
27
+ def default_config
28
+ {
29
+ "generated_files_dir" => generated_files_dir,
30
+ "stash_app" => {
31
+ "url" => "",
32
+ "api_token" => nil
33
+ },
34
+ "global" => {
35
+ "female_actors_prefix" => "[F]",
36
+ "male_actors_prefix" => "[M]",
37
+ "actors_prefix" => "[A]",
38
+ "title_prefix" => "[T]",
39
+ "id_prefix" => "[ID]",
40
+ "output_format" => [
41
+ # disable line length check for readability
42
+ # rubocop:disable Layout/LineLength
43
+ "%yyyy_mm_dd %id_prefix %id %title_prefix %title %collection_tag %collection %female_actors_prefix %female_actors %male_actors_prefix %male_actors",
44
+ "%yyyy_mm_dd %id_prefix %id %title_prefix %collection_tag %collection %actors_prefix %actors",
45
+ "%yyyy_mm_dd %title_prefix %title %collection_tag %collection %female_actors_prefix %female_actors %male_actors_prefix %male_actors",
46
+ "%yyyy_mm_dd %title_prefix %collection_tag %collection %actors_prefix %actors"
47
+ # rubocop:enable Layout/LineLength
48
+ ]
49
+ },
50
+ "site" =>
51
+ { "adult_time" =>
52
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "AT" },
53
+ "babes" =>
54
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "BA" },
55
+ "blacked" =>
56
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "BL" },
57
+ "blacked_raw" =>
58
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "BLR" },
59
+ "brazzers" =>
60
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "BZ" },
61
+ "digital_playground" =>
62
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "DP" },
63
+ "elegant_angel" =>
64
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "EL" },
65
+ "evil_angel" =>
66
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "EA" },
67
+ "goodporn" =>
68
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "GP" },
69
+ "jules_jordan" =>
70
+ { "output_format" => [],
71
+ "file_source_format" => [],
72
+ "collection_tag" => "JJ",
73
+ "cookie_file" => nil },
74
+ "manuel_ferrara" =>
75
+ { "output_format" => [],
76
+ "file_source_format" => [],
77
+ "collection_tag" => "MNF",
78
+ "cookie_file" => nil },
79
+ "mofos" =>
80
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "MF" },
81
+ "naughty_america" =>
82
+ { "output_format" => [],
83
+ "file_source_format" => [],
84
+ "collection_tag" => "NA",
85
+ "database" => File.join(generated_files_dir, "naughtyamerica.store") },
86
+ "nf_busty" =>
87
+ { "output_format" => [],
88
+ "file_source_format" => [],
89
+ "collection_tag" => "NF",
90
+ "database" => File.join(generated_files_dir, "nf_busty.store") },
91
+ "reality_kings" =>
92
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "RK" },
93
+ "stash" =>
94
+ { "output_format" => [],
95
+ "file_source_format" => [],
96
+ "username" => nil,
97
+ "password" => nil,
98
+ "api_token" => nil,
99
+ "collection_tag" => "ST" },
100
+ "tushy" =>
101
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "TU" },
102
+ "tushy_raw" =>
103
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "TUR" },
104
+ "twistys" =>
105
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "TW" },
106
+ "vixen" =>
107
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "VX" },
108
+ "whale_media" =>
109
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "WH" },
110
+ "wicked" =>
111
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "WI" },
112
+ "x_empire" =>
113
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "XM" },
114
+ "zero_tolerance" =>
115
+ { "output_format" => [], "file_source_format" => [], "collection_tag" => "WI" } }
116
+ }
117
+ end
118
+
119
+ private
120
+
121
+ attr_reader :options, :yaml
122
+
123
+ def make_config_hash
124
+ config_file = read_config_file_or_abort!
125
+ override_from_file = config_file.deeper_merge(default_config)
126
+ override_from_options = override_flags_from_options(override_from_file)
127
+ override_from_options.deeper_merge(override_from_file)
128
+ end
129
+
130
+ def generated_files_dir
131
+ @generated_files_dir ||=
132
+ begin
133
+ dir = File.join(home_dir, ".config", "xxx_rename", "generated")
134
+ FileUtils.mkpath(dir)
135
+ File.expand_path(dir)
136
+ end
137
+ end
138
+
139
+ def save_default_config
140
+ file = File.join(config_file_lookup_dirs[0], DEFAULT_CONFIG_FILE)
141
+
142
+ XxxRename.logger.info "-" * 100
143
+ XxxRename.logger.info "Config option not passed to app and no config file detected in the current directory."
144
+ XxxRename.logger.info "Generating a blank configuration file to #{file} This app will now exit."
145
+ XxxRename.logger.info "Check the contents of the file and run the app again to start downloading."
146
+ XxxRename.logger.info "-" * 100
147
+
148
+ raise Errors::FatalError, "config file already exists" if valid_file?(file)
149
+
150
+ File.open(file, "w") do |f|
151
+ f.write default_config.to_yaml
152
+ end
153
+ end
154
+
155
+ # Step 2: Get the configuration hash from the config file, if present
156
+ # This method will raise an error if no config file is present,
157
+ # which is intentional, to allow users to create a config file automatically
158
+ def read_config_file_or_abort!
159
+ return read_yaml!(options["config"], nil) if options["config"]
160
+
161
+ config_file_lookup_dirs.each do |dir|
162
+ file = File.join(dir, DEFAULT_CONFIG_FILE)
163
+ next unless valid_file?(file)
164
+
165
+ contents = read_yaml!(file, nil) || {}
166
+ return contents
167
+ end
168
+
169
+ save_default_config
170
+ raise XxxRename::Errors::SafeExit, "DEFAULT_FILE_GENERATION"
171
+ end
172
+
173
+ # Step 3: Override any configuration with the options passed to the CLI
174
+ def override_flags_from_options(config)
175
+ {}.tap do |h|
176
+ h["override_site"] = options["override_site"] if options["override_site"]
177
+ h["global"] = config["global"].merge(**overridden_globals(config["global"]))
178
+ h["force_refresh_datastore"] = options["force_refresh_datastore"] || false
179
+ h["actions"] = options["actions"] || []
180
+ h["force_refresh"] = options["force_refresh"] || false
181
+ end
182
+ end
183
+
184
+ def overridden_globals(hash)
185
+ {
186
+ "female_actors_prefix" => override_value(hash["female_actors_prefix"], options["female_actors_prefix"]),
187
+ "male_actors_prefix" => override_value(hash["male_actors_prefix"], options["male_actors_prefix"]),
188
+ "actors_prefix" => override_value(hash["actors_prefix"], options["actors_prefix"]),
189
+ "title_prefix" => override_value(hash["title_prefix"], options["title_prefix"]),
190
+ "id_prefix" => override_value(hash["id_prefix"], options["id_prefix"]),
191
+ "collection_prefix" => override_value(hash["collection_prefix"], options["collection_prefix"])
192
+ }.compact
193
+ end
194
+
195
+ def validate_download_filters!(hash)
196
+ contract = Contract::ConfigContract.new(filename_generator: FilenameGenerator).call(hash)
197
+ raise XxxRename::Errors::ConfigValidationError, contract.errors unless contract.errors.empty?
198
+
199
+ contract.to_h.transform_keys(&:to_s)
200
+ end
201
+
202
+ def override_value(original, override)
203
+ override.nil? ? original : override
204
+ end
205
+ end
206
+ end
207
+ end