web-novel-scraper 1.1.1__py3-none-any.whl → 2.0.1__py3-none-any.whl

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.
@@ -1,37 +1,32 @@
1
- import json
2
1
  from pathlib import Path
3
- import sys
4
2
  from datetime import datetime
3
+ from typing import Optional
5
4
 
6
5
  import click
7
6
 
8
- from .file_manager import FileManager
7
+ from .config_manager import ScraperConfig
9
8
  from .novel_scraper import Novel
10
9
  from .version import __version__
11
10
 
12
11
  CURRENT_DIR = Path(__file__).resolve().parent
13
12
 
14
- def obtain_novel(novel_title: str, novel_base_dir: str = None, allow_not_exists: bool = False) -> Novel:
15
- """Obtain a novel instance from the file system."""
16
- file_manager = FileManager(
17
- novel_title=novel_title, novel_base_dir=novel_base_dir, read_only=True)
18
- novel_json = file_manager.load_novel_json()
19
- if novel_json:
20
- try:
21
- novel = Novel.from_json(novel_json)
22
- return novel
23
- except KeyError:
24
- click.echo(
25
- 'JSON file seems to be manipulated, please check it.', err=True)
26
- except json.decoder.JSONDecodeError:
27
- click.echo(
28
- 'JSON file seems to be corrupted, please check it.', err=True)
29
- elif allow_not_exists:
30
- return None
31
- else:
32
- click.echo(
33
- 'Novel with that title does not exist or the main data file was deleted.', err=True)
34
- sys.exit(1)
13
+ def global_options(f):
14
+ f = click.option('-nb', '--novel-base-dir', type=click.Path(), required=False, help="Alternative directory for this novel.")(f)
15
+ f = click.option('--config-file', type=click.Path(), required=False, help="Path to config file.")(f)
16
+ f = click.option('--base-novels-dir', type=click.Path(), required=False, help="Alternative base directory for all novels.")(f)
17
+ f = click.option('--decode-guide-file', type=click.Path(), required=False, help="Path to alternative decode guide file.")(f)
18
+ return f
19
+
20
+ def obtain_novel(title, ctx_opts, allow_missing=False):
21
+ cfg = ScraperConfig(ctx_opts.get("CONFIG_FILE"), ctx_opts.get("BASE_NOVELS_DIR"))
22
+ try:
23
+ return Novel.load(title, cfg, ctx_opts.get("NOVEL_BASE_DIR"))
24
+ except ValueError:
25
+ if allow_missing:
26
+ return None
27
+ click.echo("Novel not found.", err=True)
28
+ exit(1)
29
+
35
30
 
36
31
  def validate_date(ctx, param, value):
37
32
  """Validate the date format."""
@@ -57,8 +52,15 @@ novel_base_dir_option = click.option(
57
52
  '-nb', '--novel-base-dir', type=str, help='Alternative base directory for the novel files.')
58
53
 
59
54
  @click.group()
60
- def cli():
55
+ @global_options
56
+ @click.pass_context
57
+ def cli(ctx, novel_base_dir, config_file, base_novels_dir, decode_guide_file):
61
58
  """CLI Tool for web novel scraping."""
59
+ ctx.ensure_object(dict)
60
+ ctx.obj['NOVEL_BASE_DIR'] = novel_base_dir
61
+ ctx.obj['CONFIG_FILE'] = config_file
62
+ ctx.obj['BASE_NOVELS_DIR'] = base_novels_dir
63
+ ctx.obj['DECODE_GUIDE_FILE'] = decode_guide_file
62
64
 
63
65
  # Metadata:
64
66
  metadata_author_option = click.option(
@@ -100,8 +102,8 @@ force_flaresolver_option = click.option('--force-flaresolver', is_flag=True, sho
100
102
  # Novel creation and data management commands
101
103
 
102
104
  @cli.command()
105
+ @click.pass_context
103
106
  @title_option
104
- @novel_base_dir_option
105
107
  @toc_main_url_option
106
108
  @create_toc_html_option()
107
109
  @host_option
@@ -115,9 +117,9 @@ force_flaresolver_option = click.option('--force-flaresolver', is_flag=True, sho
115
117
  @save_title_to_content_option
116
118
  @auto_add_host_option
117
119
  @force_flaresolver_option
118
- def create_novel(title, novel_base_dir, toc_main_url, toc_html, host, author, start_date, end_date, language, description, tags, cover, save_title_to_content, auto_add_host, force_flaresolver):
120
+ def create_novel(ctx, title, toc_main_url, toc_html, host, author, start_date, end_date, language, description, tags, cover, save_title_to_content, auto_add_host, force_flaresolver):
119
121
  """Creates a new novel and saves it."""
120
- novel = obtain_novel(title, novel_base_dir, allow_not_exists=True)
122
+ novel = obtain_novel(title, ctx.obj, allow_missing=True)
121
123
  if novel:
122
124
  click.confirm(f'A novel with the title {title} already exists, do you want to replace it?', abort=True)
123
125
  novel.delete_toc()
@@ -138,9 +140,16 @@ def create_novel(title, novel_base_dir, toc_main_url, toc_html, host, author, st
138
140
  toc_html_content = None
139
141
  if toc_html:
140
142
  toc_html_content = toc_html.read()
141
-
142
- novel = Novel(title, toc_main_url=toc_main_url,
143
- toc_html=toc_html_content, host=host, novel_base_dir=novel_base_dir)
143
+ novel = Novel(title=title,
144
+ toc_main_url=toc_main_url,
145
+ toc_html=toc_html_content,
146
+ host=host
147
+ )
148
+ novel.set_config(config_file=ctx.obj.get('CONFIG_FILE'),
149
+ base_novels_dir=ctx.obj.get('BASE_NOVELS_DIR'),
150
+ novel_base_dir=ctx.obj.get('NOVEL_BASE_DIR'),
151
+ decode_guide_file=ctx.obj.get('DECODE_GUIDE_FILE')
152
+ )
144
153
  novel.set_metadata(author=author, start_date=start_date,
145
154
  end_date=end_date, language=language, description=description)
146
155
  novel.set_scraper_behavior(save_title_to_content=save_title_to_content,
@@ -151,189 +160,197 @@ def create_novel(title, novel_base_dir, toc_main_url, toc_html, host, author, st
151
160
  if cover:
152
161
  if not novel.set_cover_image(cover):
153
162
  click.echo('Error saving the novel cover image.', err=True)
163
+ novel.save_novel()
154
164
  click.echo('Novel saved successfully.')
155
165
 
156
166
  @cli.command()
167
+ @click.pass_context
157
168
  @title_option
158
- @novel_base_dir_option
159
- def show_novel_info(title, novel_base_dir):
169
+ def show_novel_info(ctx, title):
160
170
  """Show information about a novel."""
161
- novel = obtain_novel(title, novel_base_dir)
171
+ novel = obtain_novel(title, ctx.obj)
162
172
  click.echo(novel)
163
173
 
164
174
  @cli.command()
175
+ @click.pass_context
165
176
  @title_option
166
- @novel_base_dir_option
167
177
  @metadata_author_option
168
178
  @metadata_start_date_option
169
179
  @metadata_end_date_option
170
180
  @metadata_language_option
171
181
  @metadata_description_option
172
- def set_metadata(title, novel_base_dir, author, start_date, end_date, language, description):
182
+ def set_metadata(ctx, title, author, start_date, end_date, language, description):
173
183
  """Set metadata for a novel."""
174
- novel = obtain_novel(title, novel_base_dir)
184
+ novel = obtain_novel(title, ctx.obj)
175
185
  novel.set_metadata(author=author, start_date=start_date,
176
186
  end_date=end_date, language=language, description=description)
187
+ novel.save_novel()
177
188
  click.echo('Novel metadata saved successfully.')
178
189
  click.echo(novel.metadata)
179
190
 
180
191
  @cli.command()
192
+ @click.pass_context
181
193
  @title_option
182
- @novel_base_dir_option
183
- def show_metadata(title, novel_base_dir):
194
+ def show_metadata(ctx, title):
184
195
  """Show metadata of a novel."""
185
- novel = obtain_novel(title, novel_base_dir)
196
+ novel = obtain_novel(title, ctx.obj)
186
197
  click.echo(novel.metadata)
187
198
 
188
199
  @cli.command()
200
+ @click.pass_context
189
201
  @title_option
190
- @novel_base_dir_option
191
202
  @click.option('--tag', 'tags', type=str, help='Tag to be added', multiple=True)
192
- def add_tags(title, novel_base_dir, tags):
203
+ def add_tags(ctx, title, tags):
193
204
  """Add tags to a novel."""
194
- novel = obtain_novel(title, novel_base_dir)
205
+ novel = obtain_novel(title, ctx.obj)
195
206
  for tag in tags:
196
207
  if not novel.add_tag(tag):
197
208
  click.echo(f'Tag {tag} already exists', err=True)
209
+ novel.save_novel()
198
210
  click.echo(f'Tags: {", ".join(novel.metadata.tags)}')
199
211
 
200
212
  @cli.command()
213
+ @click.pass_context
201
214
  @title_option
202
- @novel_base_dir_option
203
215
  @click.option('--tag', 'tags', type=str, help='Tag to be removed.', multiple=True)
204
- def remove_tags(title, novel_base_dir, tags):
216
+ def remove_tags(ctx, title, tags):
205
217
  """Remove tags from a novel."""
206
- novel = obtain_novel(title, novel_base_dir)
218
+ novel = obtain_novel(title, ctx.obj)
207
219
  for tag in tags:
208
220
  if not novel.remove_tag(tag):
209
221
  click.echo(f'Tag {tag} does not exist.', err=True)
222
+ novel.save_novel()
210
223
  click.echo(f'Tags: {", ".join(novel.metadata.tags)}')
211
224
 
212
225
  @cli.command()
226
+ @click.pass_context
213
227
  @title_option
214
- @novel_base_dir_option
215
- def show_tags(title, novel_base_dir):
228
+ def show_tags(ctx, title):
216
229
  """Show tags of a novel."""
217
- novel = obtain_novel(title, novel_base_dir)
230
+ novel = obtain_novel(title, ctx.obj)
218
231
  click.echo(f'Tags: {", ".join(novel.metadata.tags)}')
219
232
 
220
233
  @cli.command()
234
+ @click.pass_context
221
235
  @title_option
222
- @novel_base_dir_option
223
236
  @click.option('--cover-image', type=str, required=True, help='Filepath of the cover image.')
224
- def set_cover_image(title, novel_base_dir, cover_image):
237
+ def set_cover_image(ctx, title, cover_image):
225
238
  """Set the cover image for a novel."""
226
- novel = obtain_novel(title, novel_base_dir)
227
- if not novel.set_cover_image(cover_image):
228
- click.echo('Error saving the cover image.', err=True)
229
- else:
230
- click.echo('New cover image set successfully.')
239
+ novel = obtain_novel(title, ctx.obj)
240
+ novel.set_cover_image(cover_image)
241
+ click.echo(f'Cover image saved successfully.')
231
242
 
232
243
  @cli.command()
244
+ @click.pass_context
233
245
  @title_option
234
- @novel_base_dir_option
235
246
  @click.option('--save-title-to-content', type=bool, help='Toggle the title of the chapter being added to the content (use true or false).')
236
247
  @click.option('--auto-add-host', type=bool, help='Toggle automatic addition of the host to chapter URLs (use true or false).')
237
248
  @click.option('--force-flaresolver', type=bool, help='Toggle forcing the use of FlareSolver (use true or false).')
238
249
  @click.option('--hard-clean', type=bool, help='Toggle using a hard clean when cleaning HTML files (use true or false).')
239
- def set_scraper_behavior(title, novel_base_dir, save_title_to_content, auto_add_host, force_flaresolver, hard_clean):
250
+ def set_scraper_behavior(ctx, title, save_title_to_content, auto_add_host, force_flaresolver, hard_clean):
240
251
  """Set scraper behavior for a novel."""
241
- novel = obtain_novel(title, novel_base_dir)
252
+ novel = obtain_novel(title, ctx.obj)
242
253
  novel.set_scraper_behavior(
243
254
  save_title_to_content=save_title_to_content,
244
255
  auto_add_host=auto_add_host,
245
256
  force_flaresolver=force_flaresolver,
246
257
  hard_clean=hard_clean
247
258
  )
259
+ novel.save_novel()
248
260
  click.echo('New scraper behavior added successfully.')
249
261
 
250
262
  @cli.command()
263
+ @click.pass_context
251
264
  @title_option
252
- @novel_base_dir_option
253
- def show_scraper_behavior(title, novel_base_dir):
265
+ def show_scraper_behavior(ctx, title):
254
266
  """Show scraper behavior of a novel."""
255
- novel = obtain_novel(title, novel_base_dir)
267
+ novel = obtain_novel(title, ctx.obj)
256
268
  click.echo(novel.scraper_behavior)
257
269
 
258
270
  @cli.command()
271
+ @click.pass_context
259
272
  @title_option
260
- @novel_base_dir_option
261
273
  @host_option
262
- def set_host(title, novel_base_dir, host):
274
+ def set_host(ctx, title, host):
263
275
  """Set the host for a novel."""
264
- novel = obtain_novel(title, novel_base_dir)
276
+ novel = obtain_novel(title, ctx.obj)
265
277
  novel.set_host(host)
278
+ novel.save_novel()
266
279
  click.echo('New host set successfully.')
267
280
 
268
281
  # TOC MANAGEMENT COMMANDS
269
282
 
270
283
  @cli.command()
284
+ @click.pass_context
271
285
  @title_option
272
- @novel_base_dir_option
273
286
  @click.option('--toc-main-url', type=str, required=True, help='New TOC main URL (Previous links will be deleted).')
274
- def set_toc_main_url(title, novel_base_dir, toc_main_url):
287
+ def set_toc_main_url(ctx, title, toc_main_url):
275
288
  """Set the main URL for the TOC of a novel."""
276
- novel = obtain_novel(title, novel_base_dir)
289
+ novel = obtain_novel(title, ctx.obj)
277
290
  novel.set_toc_main_url(toc_main_url)
291
+ novel.save_novel()
278
292
 
279
293
  @cli.command()
294
+ @click.pass_context
280
295
  @title_option
281
- @novel_base_dir_option
282
296
  @create_toc_html_option(required=True)
283
297
  @host_option
284
- def add_toc_html(title, novel_base_dir, toc_html, host):
298
+ def add_toc_html(ctx, title, toc_html, host):
285
299
  """Add TOC HTML to a novel."""
286
- novel = obtain_novel(title, novel_base_dir)
300
+ novel = obtain_novel(title, ctx.obj)
287
301
  html_content = toc_html.read()
288
302
  novel.add_toc_html(html_content, host)
303
+ novel.save_novel()
289
304
 
290
305
  @cli.command()
306
+ @click.pass_context
291
307
  @title_option
292
- @novel_base_dir_option
293
308
  @click.option('--reload-files', is_flag=True, required=False, default=False, show_default=True, help='Reload the TOC files before sync (only works if using a TOC URL).')
294
- def sync_toc(title, novel_base_dir, reload_files):
309
+ def sync_toc(ctx, title, reload_files):
295
310
  """Sync the TOC of a novel."""
296
- novel = obtain_novel(title, novel_base_dir)
311
+ novel = obtain_novel(title, ctx.obj)
297
312
  if novel.sync_toc(reload_files):
298
313
  click.echo(
299
314
  'Table of Contents synced with files, to see the new TOC use the command show-toc.')
300
315
  else:
301
316
  click.echo(
302
317
  'Error with the TOC syncing, please check the TOC files and decoding options.', err=True)
318
+ novel.save_novel()
303
319
 
304
320
  @cli.command()
321
+ @click.pass_context
305
322
  @title_option
306
- @novel_base_dir_option
307
323
  @click.option('--auto-approve', is_flag=True, required=False, default=False, show_default=True, help='Auto approve.')
308
- def delete_toc(title, novel_base_dir, auto_approve):
324
+ def delete_toc(ctx, title, auto_approve):
309
325
  """Delete the TOC of a novel."""
310
- novel = obtain_novel(title, novel_base_dir)
326
+ novel = obtain_novel(title, ctx.obj)
311
327
  if not auto_approve:
312
328
  click.confirm(f'Are you sure you want to delete the TOC for {title}?', abort=True)
313
329
  novel.delete_toc()
330
+ novel.save_novel()
314
331
 
315
332
  @cli.command()
333
+ @click.pass_context
316
334
  @title_option
317
- @novel_base_dir_option
318
- def show_toc(title, novel_base_dir):
335
+ def show_toc(ctx, title):
319
336
  """Show the TOC of a novel."""
320
- novel = obtain_novel(title, novel_base_dir)
337
+ novel = obtain_novel(title, ctx.obj)
321
338
  click.echo(novel.show_toc())
322
339
 
323
340
  # CHAPTER MANAGEMENT COMMANDS
324
341
 
325
342
  @cli.command()
343
+ @click.pass_context
326
344
  @title_option
327
- @novel_base_dir_option
328
345
  @click.option('--chapter-url', type=str, required=False, help='Chapter URL to be scrapped.')
329
346
  @click.option('--chapter-num', type=int, required=False, help='Chapter number to be scrapped.')
330
347
  @click.option('--update-html', is_flag=True, default=False, show_default=True, help='If the chapter HTML is saved, it will be updated.')
331
- def scrap_chapter(title, novel_base_dir, chapter_url, chapter_num, update_html):
348
+ def scrap_chapter(ctx, title, chapter_url, chapter_num, update_html):
332
349
  """Scrap a chapter of a novel."""
333
350
  if (chapter_url is None and chapter_num is None) or (chapter_url and chapter_num):
334
351
  raise click.UsageError("You must set exactly one: --chapter-url o --chapter-num.")
335
352
 
336
- novel = obtain_novel(title, novel_base_dir)
353
+ novel = obtain_novel(title, ctx.obj)
337
354
 
338
355
  if chapter_num is not None:
339
356
  if chapter_num <= 0 or chapter_num > len(novel.chapters):
@@ -354,34 +371,37 @@ def scrap_chapter(title, novel_base_dir, chapter_url, chapter_num, update_html):
354
371
  click.echo(chapter.chapter_content)
355
372
 
356
373
  @cli.command()
374
+ @click.pass_context
357
375
  @title_option
358
- @novel_base_dir_option
359
376
  @sync_toc_option
360
377
  @click.option('--update-html', is_flag=True, default=False, show_default=True, help='If the chapter HTML is saved, it will be updated.')
361
378
  @click.option('--clean-chapters', is_flag=True, default=False, show_default=True, help='If the chapter HTML should be cleaned upon saving.')
362
- def request_all_chapters(title, novel_base_dir, sync_toc, update_html, clean_chapters):
379
+ def request_all_chapters(ctx, title, sync_toc, update_html, clean_chapters):
363
380
  """Request all chapters of a novel."""
364
- novel = obtain_novel(title, novel_base_dir)
381
+ novel = obtain_novel(title, ctx.obj)
365
382
  novel.request_all_chapters(
366
383
  sync_toc=sync_toc, update_html=update_html, clean_chapters=clean_chapters)
384
+ novel.save_novel()
367
385
  click.echo('All chapters requested and saved.')
368
386
 
369
387
  @cli.command()
388
+ @click.pass_context
370
389
  @title_option
371
- @novel_base_dir_option
372
- def show_chapters(title, novel_base_dir):
390
+ def show_chapters(ctx, title):
373
391
  """Show chapters of a novel."""
374
- novel = obtain_novel(title, novel_base_dir)
392
+ novel = obtain_novel(title, ctx.obj)
375
393
  click.echo(novel.show_chapters())
394
+ click.echo(f'Config file: {ctx.obj["CONFIG_FILE"]}')
395
+
376
396
 
377
397
  @cli.command()
398
+ @click.pass_context
378
399
  @title_option
379
- @novel_base_dir_option
380
400
  @sync_toc_option
381
401
  @click.option('--start-chapter', type=int, default=1, show_default=True, help='The start chapter for the books (position in the TOC, may differ from the actual number).')
382
402
  @click.option('--end-chapter', type=int, default=None, show_default=True, help='The end chapter for the books (if not defined, every chapter will be saved).')
383
403
  @click.option('--chapters-by-book', type=int, default=100, show_default=True, help='The number of chapters each book will have.')
384
- def save_novel_to_epub(title, novel_base_dir, sync_toc, start_chapter, end_chapter, chapters_by_book):
404
+ def save_novel_to_epub(ctx, title, sync_toc, start_chapter, end_chapter, chapters_by_book):
385
405
  """Save the novel to EPUB format."""
386
406
  if start_chapter <= 0:
387
407
  raise click.BadParameter(
@@ -395,7 +415,7 @@ def save_novel_to_epub(title, novel_base_dir, sync_toc, start_chapter, end_chapt
395
415
  raise click.BadParameter(
396
416
  'Should be a positive number.', param_hint='--chapters-by-book')
397
417
 
398
- novel = obtain_novel(title, novel_base_dir)
418
+ novel = obtain_novel(title, ctx.obj)
399
419
  if novel.save_novel_to_epub(sync_toc=sync_toc, start_chapter=start_chapter, end_chapter=end_chapter, chapters_by_book=chapters_by_book):
400
420
  click.echo('All books saved.')
401
421
  else:
@@ -404,27 +424,27 @@ def save_novel_to_epub(title, novel_base_dir, sync_toc, start_chapter, end_chapt
404
424
  # UTILS
405
425
 
406
426
  @cli.command()
427
+ @click.pass_context
407
428
  @title_option
408
- @novel_base_dir_option
409
429
  @click.option('--clean-chapters', is_flag=True, default=False, show_default=True, help='If the chapters HTML files are cleaned.')
410
430
  @click.option('--clean-toc', is_flag=True, default=False, show_default=True, help='If the TOC files are cleaned.')
411
431
  @click.option('--hard-clean', is_flag=True, default=False, show_default=True, help='If the files are more deeply cleaned.')
412
- def clean_files(title, novel_base_dir, clean_chapters, clean_toc, hard_clean):
432
+ def clean_files(ctx, title, clean_chapters, clean_toc, hard_clean):
413
433
  """Clean files of a novel."""
414
434
  if not clean_chapters and not clean_toc:
415
435
  click.echo(
416
436
  'You must choose at least one of the options: --clean-chapters, --clean-toc.', err=True)
417
437
  return
418
- novel = obtain_novel(title, novel_base_dir)
438
+ novel = obtain_novel(title, ctx.obj)
419
439
  novel.clean_files(clean_chapters=clean_chapters,
420
440
  clean_toc=clean_toc, hard_clean=hard_clean)
421
441
 
422
442
  @cli.command()
443
+ @click.pass_context
423
444
  @title_option
424
- @novel_base_dir_option
425
- def show_novel_dir(title, novel_base_dir):
445
+ def show_novel_dir(ctx, title):
426
446
  """Show the directory where the novel is saved."""
427
- novel = obtain_novel(title, novel_base_dir)
447
+ novel = obtain_novel(title, ctx.obj)
428
448
  click.echo(novel.show_novel_dir())
429
449
 
430
450
  @cli.command()
@@ -0,0 +1,84 @@
1
+ import os
2
+ import json
3
+
4
+ import platformdirs
5
+ from dotenv import load_dotenv
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from .logger_manager import create_logger
10
+ from .utils import FileOps
11
+
12
+ load_dotenv()
13
+
14
+ CURRENT_DIR = Path(__file__).resolve().parent
15
+
16
+ app_author = "web-novel-scraper"
17
+ app_name = "web-novel-scraper"
18
+
19
+ # DEFAULT VALUES
20
+ SCRAPER_CONFIG_FILE = str(Path(platformdirs.user_config_dir(app_name, app_author)) / "config.json")
21
+ SCRAPER_BASE_NOVELS_DIR = platformdirs.user_data_dir(app_name, app_author)
22
+ SCRAPER_DECODE_GUIDE_FILE = str(CURRENT_DIR / 'decode_guide/decode_guide.json')
23
+
24
+ logger = create_logger("CONFIG MANAGER")
25
+
26
+
27
+ ## ORDER PRIORITY
28
+ ## 1. PARAMETER TO THE INIT FUNCTION
29
+ ## 2. ENVIRONMENT VARIABLE
30
+ ## 3. CONFIG FILE VALUE
31
+ ## 4. DEFAULT VALUE
32
+ class ScraperConfig:
33
+ base_novels_dir: str
34
+ decode_guide_file: str
35
+
36
+ def __init__(self,
37
+ config_file: str = None,
38
+ base_novels_dir: str = None,
39
+ decode_guide_file: str = None):
40
+ ## LOADING CONFIGURATION
41
+ config_file = self._get_config(default_value=SCRAPER_CONFIG_FILE,
42
+ config_file_value=None,
43
+ env_variable="SCRAPER_CONFIG_FILE",
44
+ parameter_value=config_file)
45
+
46
+ config_file = Path(config_file)
47
+ logger.debug(f'Obtaining configuration from file "{config_file}"')
48
+ config = self._load_config(config_file)
49
+
50
+ if config is None:
51
+ logger.debug('No configuration found on config file.')
52
+ logger.debug('If no other config option was set, the default configuration will be used.')
53
+ config = {}
54
+
55
+ ## SETTING CONFIGURATION VALUES
56
+
57
+ self.base_novels_dir = self._get_config(default_value=SCRAPER_BASE_NOVELS_DIR,
58
+ config_file_value=config.get("base_novels_dir"),
59
+ env_variable="SCRAPER_BASE_NOVELS_DIR",
60
+ parameter_value=base_novels_dir)
61
+
62
+ self.decode_guide_file = self._get_config(default_value=SCRAPER_DECODE_GUIDE_FILE,
63
+ config_file_value=config.get("decode_guide_file"),
64
+ env_variable="SCRAPER_DECODE_GUIDE_FILE",
65
+ parameter_value=decode_guide_file)
66
+
67
+ @staticmethod
68
+ def _get_config(default_value: str,
69
+ config_file_value: Optional[str],
70
+ env_variable: str,
71
+ parameter_value: Optional[str]) -> str:
72
+ return (
73
+ parameter_value
74
+ or os.getenv(env_variable)
75
+ or config_file_value
76
+ or default_value
77
+ )
78
+
79
+ @staticmethod
80
+ def _load_config(config_file: Path) -> Optional[dict]:
81
+ config = FileOps.read_json(config_file)
82
+ if config is None:
83
+ logger.debug(f'Could not load configuration from file "{config_file}". Skipping...')
84
+ return config