From 771b8461aa58a9fd74137a122427bb14b69e8f72 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 2 Mar 2017 23:29:46 +0100 Subject: [PATCH] V : 0.11.0 --- Classes/DB/manga_db.rb | 224 ---------------- Classes/arguments/argument_manager.rb | 4 - Classes/arguments/query.rb | 4 - Classes/html/html.rb | 230 ---------------- MangaScrap.rb | 118 ++++----- README.md | 18 +- migration/0.10.x_to_0.11.x.txt | 8 + migration/0.9.X_to_1.0.x.txt | 2 +- pictures/logos/MangaScrap_logo.png | Bin 0 -> 37713 bytes sources/Classes/DB/Manga_data.rb | 198 ++++++++++++++ sources/Classes/DB/Manga_database.rb | 161 ++++++++++++ .../Classes/DB/Params.rb | 10 +- .../Classes}/Download/mangafox.rb | 79 +++--- .../Classes}/DownloadDisplay.rb | 0 sources/Classes/html/html.rb | 248 ++++++++++++++++++ .../Classes}/html/html_buffer.rb | 1 - .../Classes/instructions/Instructions_exec.rb | 184 +++++++++++++ .../Classes/instructions/Manga_data_filter.rb | 56 ++++ .../instructions/Parsers/Data_parser.rb | 69 +++++ .../instructions/Parsers/File_parser.rb | 55 ++++ .../Parsers/Instruction_parser.rb | 109 ++++++++ .../instructions/Parsers/Query_Parser.rb | 19 ++ sources/Classes/instructions/query.rb | 46 ++++ sources/add.rb | 58 ---- sources/clear.rb | 30 --- sources/delete.rb | 82 ------ sources/download.rb | 69 ----- sources/help.rb | 10 - sources/html_manager.rb | 25 -- sources/init.rb | 39 +-- sources/instructions/basic_instructions.rb | 178 +++++++++++++ sources/{ => instructions}/delete_diff.rb | 25 +- sources/{ => instructions}/params.rb | 74 +++--- sources/{ => instructions}/redl.rb | 147 +++++------ sources/list.rb | 11 - sources/scan/scan.rb | 39 ++- sources/scan/scan_utils.rb | 30 +-- sources/update.rb | 66 ----- sources/utils/utils_args.rb | 22 ++ sources/utils/utils_co.rb | 41 +-- sources/utils/utils_db.rb | 18 -- sources/utils/utils_file.rb | 14 +- sources/utils/utils_manga.rb | 35 +++ sources/utils_co.rb | 124 --------- sources/utils_db.rb | 16 -- sources/utils_manga.rb | 168 ------------ utils/exemple.txt | 40 +-- utils/help.txt | 177 ++++++++----- utils/version.txt | 1 + version.txt | 1 - 50 files changed, 1800 insertions(+), 1583 deletions(-) delete mode 100644 Classes/DB/manga_db.rb delete mode 100644 Classes/arguments/argument_manager.rb delete mode 100644 Classes/arguments/query.rb delete mode 100644 Classes/html/html.rb create mode 100644 migration/0.10.x_to_0.11.x.txt create mode 100644 pictures/logos/MangaScrap_logo.png create mode 100644 sources/Classes/DB/Manga_data.rb create mode 100644 sources/Classes/DB/Manga_database.rb rename Classes/DB/params_db.rb => sources/Classes/DB/Params.rb (90%) rename {Classes => sources/Classes}/Download/mangafox.rb (69%) rename {Classes => sources/Classes}/DownloadDisplay.rb (100%) create mode 100644 sources/Classes/html/html.rb rename {Classes => sources/Classes}/html/html_buffer.rb (90%) create mode 100644 sources/Classes/instructions/Instructions_exec.rb create mode 100644 sources/Classes/instructions/Manga_data_filter.rb create mode 100644 sources/Classes/instructions/Parsers/Data_parser.rb create mode 100644 sources/Classes/instructions/Parsers/File_parser.rb create mode 100644 sources/Classes/instructions/Parsers/Instruction_parser.rb create mode 100644 sources/Classes/instructions/Parsers/Query_Parser.rb create mode 100644 sources/Classes/instructions/query.rb delete mode 100644 sources/add.rb delete mode 100644 sources/clear.rb delete mode 100644 sources/delete.rb delete mode 100644 sources/download.rb delete mode 100644 sources/help.rb delete mode 100644 sources/html_manager.rb create mode 100644 sources/instructions/basic_instructions.rb rename sources/{ => instructions}/delete_diff.rb (60%) rename sources/{ => instructions}/params.rb (69%) rename sources/{ => instructions}/redl.rb (52%) delete mode 100644 sources/list.rb delete mode 100644 sources/update.rb create mode 100644 sources/utils/utils_args.rb delete mode 100644 sources/utils/utils_db.rb delete mode 100644 sources/utils_co.rb delete mode 100644 sources/utils_db.rb delete mode 100644 sources/utils_manga.rb create mode 100644 utils/version.txt delete mode 100644 version.txt diff --git a/Classes/DB/manga_db.rb b/Classes/DB/manga_db.rb deleted file mode 100644 index 7815a3f..0000000 --- a/Classes/DB/manga_db.rb +++ /dev/null @@ -1,224 +0,0 @@ -#manga database -class Manga_database - def add_manga(manga_name, description, site, link, author, artist, type, status, genres, release, html_name, alternative_names, rank, rating, rating_max) - begin - prep = @db.prepare 'INSERT INTO manga_list VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' - prep.bind_param 1, manga_name - prep.bind_param 2, description - prep.bind_param 3, site - prep.bind_param 4, link - prep.bind_param 5, author - prep.bind_param 6, artist - prep.bind_param 7, type - prep.bind_param 8, status - prep.bind_param 9, genres - prep.bind_param 10, release - prep.bind_param 11, html_name - prep.bind_param 12, alternative_names - prep.bind_param 13, rank - prep.bind_param 14, rating - prep.bind_param 15, rating_max - prep.execute - rescue SQLite3::Exception => e - db_error_exit('Exception while adding manga to database', e) - end - end - - def update_manga(manga_name, description, author, artist, genres, html_name, alternative_names, rank, rating, rating_max) - begin - prep = @db.prepare 'UPDATE manga_list SET description=?, author=?, artist=?, html_name=?, genres=?, alternative_names=?, rank=?, rating=?, rating_max=? WHERE name=?' - prep.bind_param 1, description - prep.bind_param 2, author - prep.bind_param 3, artist - prep.bind_param 4, html_name - prep.bind_param 5, genres - prep.bind_param 6, alternative_names - prep.bind_param 7, rank - prep.bind_param 8, rating - prep.bind_param 9, rating_max - # manga_name variable must always be last ( note for future updates ) - prep.bind_param 10, manga_name - prep.execute - rescue SQLite3::Exception => e - db_error_exit("Error while trying to update data of #{manga_name}", e) - end - end - - def delete_manga(manga_name) - manga_id = get_manga(manga_name)[0] - delete_todo(manga_id) - begin - @db.execute "DELETE FROM manga_trace WHERE mangaId=#{manga_id}" - rescue SQLite3::Exception => e - db_error_exit("Exception while deleting #{manga_name} from trace database", e) - end - begin - @db.execute "DELETE FROM manga_list WHERE name = '#{manga_name}'" - rescue SQLite3::Exception => e - db_error_exit("Exception while deleting #{manga_name} from database", e) - end - end - - def get_manga(manga_name) - if manga_name == nil - puts 'error while trying to get manga in database => name is nil' - exit 2 - end - begin - ret = @db.execute "SELECT * FROM manga_list WHERE name='#{manga_name}'" - rescue SQLite3::Exception => e - db_error_exit("Exception while getting #{manga_name} in database", e) - end - ret[0] - end - - def manga_in_data?(manga_name) - manga = get_manga(manga_name) - if manga == nil - return false - end - true - end - - def get_manga_list(data = false) - begin - ret = @db.execute 'SELECT ' + ((!data) ? 'name' : '*') + ' FROM manga_list ORDER BY name COLLATE NOCASE' - rescue SQLite3::Exception => e - db_error_exit('Exception while getting manga list', e) - end - ret - end - - #todo database - def add_todo(manga_name, volume_value, chapter_value, page_nb) - manga_id = get_manga(manga_name)[0] - todo = get_todo(manga_name) - insert = [manga_id, volume_value, chapter_value, page_nb] - if manga_name == nil || volume_value == nil || chapter_value == nil || page_nb == nil - puts 'Error while trying to insert element in todo database => nil' - p insert - puts '(manga_name, volume, chapter, page)' - exit 2 - end - todo.each do |elem| - elem.shift - if elem == insert - return false - end - end - begin - prep = @db.prepare "INSERT INTO manga_todo VALUES (NULL, #{manga_id}, ?, ?, ?)" - prep.bind_param 1, volume_value - prep.bind_param 2, chapter_value - prep.bind_param 3, page_nb - prep.execute - rescue SQLite3::Exception => e - db_error_exit('could not insert page into todo database', e) - end - true - end - - def get_todo(manga_name) - manga_id = get_manga(manga_name)[0] - begin - ret = @db.execute "SELECT * FROM manga_todo WHERE mangaId=#{manga_id}" - rescue SQLite3::Exception => e - db_error_exit('exception on database while getting todo of ' + manga_name, e) - end - ret - end - - def delete_todo(id) - begin - @db.execute "DELETE FROM manga_todo WHERE Id = #{id}" - rescue SQLite3::Exception => e - db_error_exit('exception on database while deleting todo element', e) - end - end - - def clear_todo(manga_name) - manga_id = get_manga(manga_name)[0] - begin - @db.execute "DELETE FROM manga_todo WHERE mangaId=#{manga_id}" - rescue SQLite3::Exception => e - db_error_exit("exception on database while deleting todo of #{manga_name}", e) - end - end - - #trace database - def add_trace(manga_name, volume_value, chapter_value) - manga_id = get_manga(manga_name)[0] - trace = get_trace(manga_name) - insert = [manga_id, volume_value, chapter_value] - if manga_name == nil || volume_value == nil || chapter_value == nil - puts 'Error while trying to insert element in trace database => nil' - p insert - puts '(manga_name, volume, chapter, page)' - exit 2 - end - found = false - trace.each do |elem| - elem.shift - if elem == insert - found = true - break - end - end - unless found - begin - prep = @db.prepare "INSERT INTO manga_trace VALUES (NULL, #{manga_id}, ?, ?)" - prep.bind_param 1, volume_value - prep.bind_param 2, chapter_value - prep.execute - rescue SQLite3::Exception => e - db_error_exit('could not insert chapter into trace database', e) - end - end - end - - def get_trace(manga_name) - manga_id = get_manga(manga_name)[0] - begin - ret = @db.execute "SELECT * FROM manga_trace WHERE mangaId=#{manga_id}" - rescue SQLite3::Exception => e - db_error_exit("could not get trace database of #{manga_name}", e) - end - ret - end - - def delete_trace(manga_name, chapter) - manga_id = get_manga(manga_name)[0] - begin - @db.execute "DELETE FROM manga_trace WHERE mangaId=#{manga_id} AND volume=#{chapter[0]} and chapter=#{chapter[1]}" - rescue SQLite3::Exception => e - db_error_exit('could not erase element from trace database', e) - end - end - - #init database - def initialize - begin - @db = SQLite3::Database.new Dir.home + '/.MangaScrap/db/manga.db' - @db.execute 'CREATE TABLE IF NOT EXISTS manga_list ( - Id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, site TEXT, - link TEXT, author TEXT, artist TEXT, type TEXT, status BOOL, genres TEXT, - release INT, html_name TEXT, alternative_names NTEXT, rank INT, rating INT, - rating_max INT)' - rescue SQLite3::Exception => e - db_error_exit('Exception occurred when opening / creating manga table', e) - end - begin - @db.execute 'CREATE TABLE IF NOT EXISTS manga_todo ( - Id INTEGER PRIMARY KEY, mangaId INTERGER, volume INTERGER, chapter DOUBLE, - page INTEGER)' - rescue SQLite3::Exception => e - db_error_exit('exception occured while trying to open / create todo table', e) - end - begin - @db.execute 'CREATE TABLE IF NOT EXISTS manga_trace ( - Id INTEGER PRIMARY KEY, mangaId INTERGER, volume INTERGER, chapter DOUBLE)' - rescue SQLite3::Exception => e - db_error_exit('exception occured while trying to open / create trace table', e) - end - end -end diff --git a/Classes/arguments/argument_manager.rb b/Classes/arguments/argument_manager.rb deleted file mode 100644 index 5f98912..0000000 --- a/Classes/arguments/argument_manager.rb +++ /dev/null @@ -1,4 +0,0 @@ -# class used for the argument management -class Argument_manager - -end diff --git a/Classes/arguments/query.rb b/Classes/arguments/query.rb deleted file mode 100644 index f5491d0..0000000 --- a/Classes/arguments/query.rb +++ /dev/null @@ -1,4 +0,0 @@ -#class used to get the mangas with the query option -class Query - -end diff --git a/Classes/html/html.rb b/Classes/html/html.rb deleted file mode 100644 index 4568749..0000000 --- a/Classes/html/html.rb +++ /dev/null @@ -1,230 +0,0 @@ -$copied_js_css = false - -class HTML - # adds variables to @traces in order to sort / check - def add_data_to_traces - @traces.each do |chap| - filename = html_chapter_filename(chap[3], chap[2]) - chap << "#####elem#####" - chap << Dir.glob(file_name(@dir + @path_pictures, chap[2], chap[3], -1, true)).size - chap << '.' + filename - end - end - - # gets all the pictures for each chapter - def get_pictures_of_chapter(chapter) - ret = [] - i = 1 - Dir.glob(file_name(@dir + @path_pictures, chapter[2], chapter[3], -1, true)).each do |file| - file_relative_pos = file_name('../../mangas/' + @manga_data[1] + '/', chapter[2], chapter[3], i) + '.jpg' - ret << "

#{i}

" - ret << "
\n" - ret << " \n" - ret << "
\n" - i += 1 - end - ret.join - end - - # generates the html for 1 chapter - def html_chapter(arr) - template = File.open(Dir.home + '/.MangaScrap/templates/chapter_template.html').read - template = template.gsub('#####index#####', '../' + @manga_data[1] + '.html') - template = template.gsub('#####name#####', @manga_name) - if arr[0] != nil - template = template.gsub('#####next#####', arr[0][4]).gsub('#####html_path#####', '.').gsub('#####elem#####', 'next') - template = template.gsub('#####next_url#####', arr[0][6]) - else - template = template.gsub('#####next#####', '') - template = template.gsub('#####next_url#####', '') - end - if arr[2] != nil - template = template.gsub('#####prev#####', arr[2][4]).gsub('#####html_path#####', '.').gsub('#####elem#####', 'prev') - template = template.gsub('#####prev_url#####', arr[2][6]) - else - template = template.gsub('#####prev#####', '') - template = template.gsub('#####prev_url#####', '') - end - chap_data = 'Chapter ' + ((arr[1][3] % 1 == 0) ? arr[1][3].to_i : arr[1][3]).to_s + ' ' + volume_int_to_string(arr[1][2], true) - max_chap_data = 'Chapter ' + ((@traces[0][3] % 1 == 0) ? @traces[0][3].to_i : @traces[0][3]).to_s + ' ' + volume_int_to_string(@traces[0][2], true) - template = template.gsub('#####chapter_data#####', chap_data + ' / ' + max_chap_data) - template = template.gsub('#####pictures#####', get_pictures_of_chapter(arr[1])) - template = template.gsub('#####manga_index#####', @index_path) - template = template.gsub('#', '%23') - File.open(@dir + @path_html + html_chapter_filename(arr[1][3], arr[1][2]), 'w') {|f| f.write(template) } - end - - # generates html for all the chapters and links them ( prev and next buttons ) - def html_generate_chapters(manga_name) - puts 'updating html chapters of ' + manga_name - traces = @traces.reject{|chapter| chapter[5] == 0} - if traces.size > 0 - if traces.size == 1 - arr = [nil, traces[0], nil] - html_chapter(arr) - else - arr = [nil, traces[0], traces[1]] - html_chapter(arr) - traces.each_cons(3) do |nxt, current, prev| - arr = [nxt, current, prev] - html_chapter(arr) - end - arr = [traces[traces.size - 2], traces[traces.size - 1], nil] - html_chapter(arr) - end - else - puts 'warning : no chapters in database, could not generate chapters'.yellow - end - end - - # converts the array of traces to an array of html code - the chapter index - - # used by the chapter index of each manga - def chapter_list_to_a_list(template) - ret = '' - buff = '' - biggest = @traces.map { |a| [a[2], 0].max }.max - @traces = @traces.sort_by { |a| [(a[2] < 0) ? (biggest + a[2] * -1) * -1 : a[2] * -1, -a[3]] } - @traces.each do |chapter| - ret += "
  • \n" - chapter_buff = (chapter[3] % 1 == 0) ? chapter[3].to_i.to_s : chapter[3].to_s - volume_buff = volume_int_to_string(chapter[2], true) - buff = chapter[4].gsub('#####html_path#####', @manga_data[1]).gsub('#####elem#####', volume_buff + ' Chapter ' + chapter_buff).gsub('#', '%23') - ret += buff - ret += "
  • \n" - end - template = template.gsub('#####list#####', ret) - template.gsub('#####first#####', buff) - end - - # generates the chapter index, includes description, cover, names of artist and author, ... - def generate_chapter_index(manga_name, index = true) - if @params[8] == 'true' || @force_html == true - puts 'updating chapter index of ' + manga_name - @manga_init = true - @manga_data = @db.get_manga(manga_name) - @dir = @params[1] + @manga_data[4].split('/')[2].split('.')[0] - @path_to_cover = '/mangas/' + @manga_data[1] + '.jpg' - @path_pictures = '/mangas/' + @manga_data[1] + '/' - @path_html = '/html/' + @manga_data[1] - @manga_name = @manga_data[11] - @traces = @db.get_trace(@manga_data[1]) - @index_path = @dir + '/index.html' #todo : manage sites - add_data_to_traces - dir_create(@dir + @path_html) - template = File.open(Dir.home + '/.MangaScrap/templates/chapter_index_template.html') - template = template.read - template = template.gsub('#####name#####', @manga_name) - template = template.gsub('#####description#####', @manga_data[2].gsub("\n", '
    ')) - template = template.gsub('#####site#####', html_a_buffer(@manga_data[4])) - template = template.gsub('#####author#####', html_a_buffer(@manga_data[5])) - template = template.gsub('#####artist#####', html_a_buffer(@manga_data[6])) - template = template.gsub('#####type#####', html_a_buffer(@manga_data[7])) - template = template.gsub('#####status#####', html_a_buffer(@manga_data[8])) - template = template.gsub('#####genres#####', html_a_buffer(@manga_data[9])) - template = template.gsub('#####date#####', html_a_buffer(@manga_data[10].to_s)) - template = template.gsub('#####alternative_names#####', @manga_data[12].split('; ').join('
    ')) - template = template.gsub('#####cover#####', '..' + @path_to_cover) - template = template.gsub('#####index_path#####', '../index.html') - template = template.gsub('#####rank#####', @manga_data[13].to_s) - template = template.gsub('#####rating#####', @manga_data[14].to_s + ' / ' + @manga_data[15].to_s) - template = chapter_list_to_a_list(template).gsub('#', '%23') - File.open(@dir + @path_html + '.html', 'w') {|f| f.write(template)} - html_generate_chapters(manga_name) if index - end - end - - # manages the js of the index - def index_js_data - ret = "var data = [\n" - mangas = @db.get_manga_list(true) - mangas.each do |manga| - ret += " ['" + manga[11].gsub('; ', '
    ').gsub("'", '###guillemet###') + "', '" - ret += manga[12].gsub('; ', '
    ').gsub("'", '###guillemet###') + "', '" - ret += manga[2].gsub("\r", '
    ').gsub("\n", '
    ').gsub("'", '###guillemet###') + "', '" - ret += manga[5] + "', '" - ret += manga[6] + "', '" - ret += manga[8] + "', '" - ret += manga[9] + "', " - ret += manga[10].to_s + ", '" - ret += manga[7] + "', " - ret += manga[13].to_s + ', ' - ret += (manga[14] * 100 / manga[15]).round(2).to_s + "],\n" - end - ret += '];' - ret - end - - # generates the links for each manga ( cover + name ), used for the manga index - def html_get_data - mangas = @db.get_manga_list - ret = [] - i = 0 - mangas.sort.each do |manga| - ret << "
    " - ret << " ' - source_buffer = ' ' - if @params[9] == 'false' - manga_genres = @db.get_manga(manga[0])[9].split(', ') - if (@params[10].split(', ') & manga_genres).empty? - ret << source_buffer - else - # warning : sites must be managed - ret << ' ' - end - else - ret << source_buffer - end - ret << '

    ' + description_manipulation(manga[0].gsub('_', ' '), 25, 3).gsub('\n', '
    ') + '

    ' - ret << '
    ' - ret << '
    ' - i += 1 - end - return ret.join("\n") - end - - # generates the manga index ( the page containing the links to all mangas ) - def generate_index - if @params[8] == 'true' || @force_html == true - puts 'updating html of manga index' - # todo : manage sites - template = File.open(Dir.home + '/.MangaScrap/templates/manga_index_template.html').read - template = template.gsub('#####list#####', html_get_data) - File.open(@dir + '/index.html', 'w') {|f| f.write(template)} - js = File.open(Dir.home + '/.MangaScrap/templates/manga_index_template.js').read - js = js.gsub('#####tab#####', index_js_data) - File.open(@dir + '/html/js/manga_index.js', 'w') {|f| f.write(js)} - end - end - - # the copy of the css and JS is only done one per execution of MangaScrap - def css_and_js_init - unless $copied_js_css - dir_create(@params[1] + 'mangafox/html/css') - dir_create(@params[1] + 'mangafox/html/js') - # manga index - template = File.open(Dir.home + '/.MangaScrap/templates/manga_index_template.css').read - File.open(@params[1] + 'mangafox/html/css/manga_index.css', 'w') {|f| f.write(template) } - # chapter index - template = File.open(Dir.home + '/.MangaScrap/templates/chapter_index_template.css').read - File.open(@params[1] + 'mangafox/html/css/chapter_index.css', 'w') {|f| f.write(template) } - # chapter - template = File.open(Dir.home + '/.MangaScrap/templates/chapter_template.css').read - File.open(@params[1] + 'mangafox/html/css/chapter.css', 'w') {|f| f.write(template) } - # todo : this file may need to be moved5780x1080 - template = File.open(Dir.home + '/.MangaScrap/templates/chapter_template.js').read - File.open(@params[1] + 'mangafox/html/js/chapter.js', 'w') {|f| f.write(template) } - $copied_js_css = true - end - end - - # the constructor copies all the css and places them in the html dir - def initialize(db, force_html = false) - @force_html = force_html - @params = Params.instance.get_params - @dir = @params[1] + 'mangafox/' - @nsfw_genres = @params[10].split(', ') - @manga_init = false - @db = db - css_and_js_init - end -end \ No newline at end of file diff --git a/MangaScrap.rb b/MangaScrap.rb index cac01bb..817e9f4 100755 --- a/MangaScrap.rb +++ b/MangaScrap.rb @@ -5,96 +5,72 @@ # if you have a question, please go here : # https://github.com/Hellfire01/MangaScrap # -# MangaScrap's return values : +# Note : +# MangaScrap will install it's databases and templates +# in ~/.MangaScrap +# +# MangaScrap's return values :+ # 0 : good # 1 : fatal error ( ruby native code exceptions ) # 2 : db error # 3 : connection error # 4 : unexpected error ( not yet managed stuff ) # 5 : argument error +# 6 : gem error require 'singleton' require 'open-uri' require 'pp' +def load_gem(gem) + begin + require gem + rescue Exception => e + puts '' + puts "exception while trying to load #{gem}, please follow the installation instructions in the install directory" + puts 'message is : ' + e.message + puts 'please note that a ruby update may require a re-download of the gems' + puts '' + exit 6 + end +end + # gems -require 'colorize' -require 'nokogiri' -require 'sqlite3' +load_gem 'colorize' +load_gem 'nokogiri' +load_gem 'sqlite3' -require_relative 'Classes/html/html' -require_relative 'Classes/html/html_buffer' -require_relative 'Classes/Download/mangafox' -require_relative 'Classes/DownloadDisplay' -require_relative 'Classes/arguments/query' -require_relative 'Classes/arguments/argument_manager' +# files +require_relative 'sources/init' require_relative 'sources/scan/scan' require_relative 'sources/scan/scan_utils' -require_relative 'sources/download' -require_relative 'sources/update' -require_relative 'sources/delete_diff' -require_relative 'sources/help' -require_relative 'sources/add' -require_relative 'sources/init' -require_relative 'sources/list' -require_relative 'sources/delete' -require_relative 'sources/params' -require_relative 'sources/clear' -require_relative 'sources/redl' -require_relative 'sources/html_manager' +require_relative 'sources/instructions/delete_diff' +require_relative 'sources/instructions/basic_instructions' +require_relative 'sources/instructions/params' +require_relative 'sources/instructions/redl' require_relative 'sources/utils/utils_file' +require_relative 'sources/utils/utils_args' require_relative 'sources/utils/utils_co' -require_relative 'sources/utils/utils_db' require_relative 'sources/utils/utils_manga' require_relative 'sources/utils/utils_html' require_relative 'sources/utils/utils_debug' -require_relative 'Classes/DB/manga_db' -require_relative 'Classes/DB/params_db' +require_relative 'sources/Classes/html/html' +require_relative 'sources/Classes/html/html_buffer' +require_relative 'sources/Classes/Download/mangafox' +require_relative 'sources/Classes/DownloadDisplay' +require_relative 'sources/Classes/instructions/Parsers/Instruction_parser' +require_relative 'sources/Classes/instructions/Parsers/Data_parser' +require_relative 'sources/Classes/instructions/Parsers/Query_Parser' +require_relative 'sources/Classes/instructions/Parsers/File_parser' +require_relative 'sources/Classes/instructions/query' +require_relative 'sources/Classes/instructions/Instructions_exec' +require_relative 'sources/Classes/instructions/Manga_data_filter' +require_relative 'sources/Classes/DB/Manga_data' +require_relative 'sources/Classes/DB/Manga_database' +require_relative 'sources/Classes/DB/Params' -db = initialize_mangascrap(__dir__) +initialize_mangascrap + +args = Instructions_exec.new +args.run -if ARGV.size == 0 - update(db) -else - case ARGV[0] - when '-u', '--update' - update(db) - when '-uf', '--update-fast' - update(db, true) - when '-a', '--add' - add(db, false) - when '-da', '--data' - add(db, true) - when '-ht', '--html' - html_manager(db) - when '-hti', '--html-index' - HTML.new(db).generate_index - when '-dl', '--download' - download(db) - when '-l', '--list' - list(db) - when '-d', '--delete' - delete(db) - when '-df', '--delete-files' - delete(db, true) - when '-pl', '--param_list' - param_list - when '-ps', '--param_set' - param_set - when '-pr', '--param_reset' - param_reset - when '-c', '--clear' - clear(db) - when '-redl', '--re-download' - re_dl(db) - #when '-sca', '--scan-add' - # scan(db, 'add') - #when '-scc', '--scan-correct' - # scan(db, 'correct') - when '-h', '--help' - help - else - puts 'error, unknown instruction : ' + ARGV[0] - puts '--help for help' - end -end diff --git a/README.md b/README.md index acf696b..9e1cfce 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ## MangaScrap MangaScrap is a Ruby based script that will allow you to download your mangas and save them on your computer
    -It possese a database that allows it to know what chapters where not downloaded yet
    -Unlike other manga downloaders, it creates a local website on your computer allowing you to browse your mangas offline !
    +It posses a database that allows it to know what chapters where not downloaded yet
    +Unlike other manga downloader, it creates a local website on your computer allowing you to browse your mangas offline !
    #### basic usage : ./MangaScrap -a [manganame] => will add a manga to follow to the database
    @@ -12,22 +12,26 @@ Unlike other manga downloaders, it creates a local website on your computer allo #### notes : ./MangaScrap -h => displays instructions
    The programm will download all mangas in Documents/mangas/ by default
    -( it will create the mangas folder automaticaly )
    +( it will create the mangas folder automatically )
    ### Changelog : +0.11.0 : the argument management completely changed and is now much easier to use ( added instructions and a new way to use MangaScrap ) + the html was heavily optimised + added a management of bad gem loads
    + +###### Warning : the database changes and is not compatible with the previous versions
    Please look at the file migration/0.10.x_to_0.11.x.txt
    + 0.10.2 : added a few clickable elements on each chapter and a reading progression
    0.10.1 : added arrow control on chapter pages, it is now possible to go to the next / previous page with left and right arrows
    -0.10.0 : big performance improvement, added JS to the HTML, many additions to the database to display more informations and text coloration
    +0.10.0 : big performance improvement, added JS to the HTML, many additions to the database to display more information and text coloration
    ###### Warning : the database changes and is not compatible with the previous versions
    Please look at the file migration/0.9.x_to_0.10.x.txt
    0.9.6 : drastically impoved the terminal display, it is now far easier to read
    -0.9.5 : added instructions to install the dependencies on debain
    -0.9.4 : fixed an error preventing the program to be lanched correctly the very first time
    +0.9.5 : added instructions to install the dependencies on debian
    +0.9.4 : fixed an error preventing the program to be launched correctly the very first time
    0.9.3 : fixed a few bugs on the html generation
    0.9.2 : reimplemented the delete diff, fixed a db error when trying to remove certain chapters from the traces and optimised the usage of the params
    -0.9.1 : improved the html : it is now fully portable and can be copyed ( with the pictures ) to any destination and remain readable / usable
    +0.9.1 : improved the html : it is now fully portable and can be copied ( with the pictures ) to any destination and remain readable / usable
    0.9.0 : implemented 3 new options ( params ) for the html management + added the hti option ( shell )
    ###### Warning : the database changes and is not compatible with the previous versions
    Please look at the file migration/0.8.x_to_0.9.x.txt
    diff --git a/migration/0.10.x_to_0.11.x.txt b/migration/0.10.x_to_0.11.x.txt new file mode 100644 index 0000000..e780605 --- /dev/null +++ b/migration/0.10.x_to_0.11.x.txt @@ -0,0 +1,8 @@ +1 - update your manga.db +sqlite3 ~/.MangaScrap.db/manga.bd +ALTER TABLE manga_trace ADD date VARCHAR(32); +ALTER TABLE manga_trace ADD nb_pages INTEGER; +ALTER TABLE manga_todo ADD date VARCHAR(32); +ALTER TABLE manga_list ADD date VARCHAR(32); +ALTER TABLE manga_list ADD no_auto_updates BOOL; +ALTER TABLE manga_list ADD ignore_todo BOOL; diff --git a/migration/0.9.X_to_1.0.x.txt b/migration/0.9.X_to_1.0.x.txt index 8378d60..dff89d6 100644 --- a/migration/0.9.X_to_1.0.x.txt +++ b/migration/0.9.X_to_1.0.x.txt @@ -1,4 +1,4 @@ -1 - Delete your params DB ( please note all important informations sutch as mangapath before doing so if you have changed any parameter ) +1 - Delete your params DB ( please note all important information such as mangapath before doing so if you have changed any parameter ) rm ~/.MangaScrap/db/params.db 2 - Update your manga.db diff --git a/pictures/logos/MangaScrap_logo.png b/pictures/logos/MangaScrap_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1da9a7d41aef40ff5002772f2c030bd28e308ac4 GIT binary patch literal 37713 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 S3+~ e + puts 'Exception while trying to get '.red + @name.yellow + puts 'reason is : '.yellow + e.message + return nil + end + nil + end + + # the function resolve will try to complete the missing information + # for that it will connect to the database and check or look online + # unless get_data_in_db + def resolve(connect, display) + if @status + return true + end + if @name != nil && @site != nil # got manga_data with id + unless is_site_compatible?(display) + return false + end + elsif @link != nil # got manga_data with link + @site = extract_site_from_link(@link) + @name = extract_name_from_link(@link) + unless is_site_compatible?(display) + return false + end + else # Error => the data was not fed correctly to the class + pp self + puts '' + critical_error('the data class was called with incorrect parameters') + end + ret = get_data_in_db + if !ret && connect # if it is not in the database and a connection is required, then it is good + if check_link(display) + @status = true + return true + end + return false + end + if ret && connect # if connect is true, the manga should not be found in the database + puts 'Warning :'.yellow + ' ' + @name + ' of ' + @site + ' is already in the database, ignoring it' + return false + end + if !ret && !connect # if connect is false, it should be in the database + puts 'Warning :'.yellow + ' ' + @name + ' of ' + @site + ' is not in the database, ignoring it' + return false + end + # should the manga not be in the database and Manga_data require connection, + # it will try to valid / invalid the link + if !ret && connect && @link != nil && !check_link(display) + false + else + ret + end + end + + # if data = nil => the manga is not in the database + def initialize(id, name, site, link, data) + @name = name + @site = site + @link = link + @id = id + @data = data + # should the data come from the database it will be ready + @status = (id != nil && name != nil && site != nil && link != nil && data != nil) + @site_dir = '' + @to_complete = '' + @in_db = @status + if @in_db + is_site_compatible?(false) # function used here to get @site_dir and @ to_complete + end + end +end diff --git a/sources/Classes/DB/Manga_database.rb b/sources/Classes/DB/Manga_database.rb new file mode 100644 index 0000000..5e3d678 --- /dev/null +++ b/sources/Classes/DB/Manga_database.rb @@ -0,0 +1,161 @@ +#manga database +class Manga_database + include Singleton + private + def now + Time.new.strftime('%Y/%m/%d') + end + + public + # used to cast the lin of manga_list into a Manga_data + def data_to_manga_data(data) + Manga_data.new(data[0], data[1], data[3], data[4], data) + end + + # tries and caches an sql request + def db_exec(request, error, args = []) + begin + ret = @db.execute request, args + rescue SQLite3::Exception => e + critical_error(error, e) + end + ret + end + + # def add_manga(*args) + def add_manga(manga_data, description, author, artist, type, status, genres, release, html_name, alternative_names, rank, rating, rating_max) + arguments = [manga_data.name, description, manga_data.site, manga_data.link, author, artist, type, status, genres,release, html_name, alternative_names, rank, rating, rating_max, now, false.to_s, false.to_s] + db_exec('INSERT INTO manga_list VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', 'could not insert ' + manga_data.name.yellow + ' into the database', arguments) + end + + def update_manga(manga_name, description, author, artist, genres, html_name, alternative_names, rank, rating, rating_max) + args = [description, author, artist, html_name, genres, alternative_names, rank, rating, rating_max] + # manga_name variable must always be last ( note for future updates ) + args << manga_name + db_exec('UPDATE manga_list SET description=?, author=?, artist=?, html_name=?, genres=?, alternative_names=?, rank=?, rating=?, rating_max=? WHERE name=?', 'could not update ' + manga_name.yellow, args) + end + + def delete_manga(manga_data) + clear_todo(manga_data) + db_exec('DELETE FROM manga_trace WHERE mangaId=?', "Exception while deleting #{manga_data.name} from trace database", [manga_data.id]) + db_exec('DELETE FROM manga_list WHERE Id=?', "Exception while deleting #{manga_data.name} from database", [manga_data.id]) + end + + def get_manga(manga_data) + if manga_data == nil + puts 'Error '.red + ': while trying to get manga in database => Data is nil' + exit 2 + end + # todo use db_exec correctly + ret = db_exec("SELECT * FROM manga_list WHERE name='#{manga_data.name}' and site='#{manga_data.site}'", "Exception while getting #{manga_data.name} in database") + ret[0] + end + + def manga_in_data?(manga_data) + if get_manga(manga_data) == nil + return false + end + true + end + + # todo il faut adapter le html + def get_manga_list(site = nil) + if site == nil + buff = db_exec('SELECT * FROM manga_list ORDER BY name COLLATE NOCASE', 'Exception while getting manga list') + else + buff = db_exec('SELECT * FROM manga_list WHERE site=? ORDER BY name COLLATE NOCASE', 'Exception while getting manga list', [site]) + end + ret = [] + buff.each do |manga| + ret << data_to_manga_data(manga) + end + ret + end + + # _todo database + def add_todo(manga_data, volume_value, chapter_value, page_nb) + if manga_data == nil || volume_value == nil || chapter_value == nil || page_nb == nil + puts 'Error while trying to insert element in todo database => nil' + p insert + puts '(manga_name, volume, chapter, page)' + exit 2 + end + todo = get_todo(manga_data) + insert = [manga_data.id, volume_value, chapter_value, page_nb, now] + todo.each do |elem| + elem.shift + if elem == insert + return false + end + end + db_exec('INSERT INTO manga_todo VALUES (NULL, ?, ?, ?, ?, ?)', 'could not add todo for ' + manga_data.name, insert) + true + end + + def get_todo(manga_data) + db_exec('SELECT * FROM manga_todo WHERE mangaId=?', 'exception on database while getting todo of ' + manga_data.name, [manga_data.id]) + end + + def delete_todo(id) + db_exec('DELETE FROM manga_todo WHERE Id=?', 'exception on database while deleting todo element', [id]) + end + + def clear_todo(manga_data) + db_exec('DELETE FROM manga_todo WHERE mangaId=?', "exception on database while deleting todo of #{manga_data.name}", [manga_data.id]) + end + + # trace database + def add_trace(manga_data, volume_value, chapter_value, nb_pages) + if manga_data == nil || volume_value == nil || chapter_value == nil + puts 'Error while trying to insert element in trace database' + puts ((manga_data == nil) ? 'manga_data is nil' : 'volume or chapter value is nil') + exit 2 + end + trace = get_trace(manga_data) + insert = [manga_data.id, volume_value, chapter_value, now, nb_pages] + found = false + trace.each do |elem| # checking if the trace does not already exist in database to avoid duplicates + elem.shift # the first element is shift as it contains the id of the trace + if elem == insert + found = true + break + end + end + unless found + db_exec('INSERT INTO manga_trace VALUES (NULL, ?, ?, ?, ?, ?)', 'could not add trace for ' + manga_data.name.yellow, insert) + end + end + + def get_trace(manga_data) + db_exec('SELECT * FROM manga_trace WHERE mangaId=?', "could not get trace database of #{manga_data.name}", [manga_data.id]) + end + + def delete_trace(manga_data, chapter) + db_exec('DELETE FROM manga_trace WHERE mangaId=? AND volume=? and chapter=?', 'could not erase element from trace database', [manga_data.id, chapter[0], chapter[1]]) + end + + #init database + def initialize + begin + @db = SQLite3::Database.new Dir.home + '/.MangaScrap/db/manga.db' + # manga_db + @db.execute 'CREATE TABLE IF NOT EXISTS manga_list ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, site TEXT, + link TEXT, author TEXT, artist TEXT, type TEXT, status BOOL, genres TEXT, + release INTEGER, html_name TEXT, alternative_names NTEXT, rank INTEGER, rating INTEGER, + rating_max INTEGER, date VARCHAR(32), no_auto_updates BOOL, ignore_todo BOOL)' + # todo_db + @db.execute 'CREATE TABLE IF NOT EXISTS manga_todo ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, mangaId INTEGER, volume INTEGER, chapter DOUBLE, page INTEGER, date VARCHAR(32))' + @db.execute 'CREATE TABLE IF NOT EXISTS manga_todo_string ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, mangaId INTEGER, chap TEXT, page INTEGER, date VARCHAR(32))' + # traces_db + @db.execute 'CREATE TABLE IF NOT EXISTS manga_trace ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, mangaId INTEGER, volume INTEGER, chapter DOUBLE, date VARCHAR(32), nb_pages INTEGER)' + @db.execute 'CREATE TABLE IF NOT EXISTS manga_trace_string ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, mangaId INTEGER, chap TEXT, date VARCHAR(32), nb_pages INTEGER)' + rescue SQLite3::Exception => e + critical_error('exception occured while trying to open / create tables', e) + end + end +end diff --git a/Classes/DB/params_db.rb b/sources/Classes/DB/Params.rb similarity index 90% rename from Classes/DB/params_db.rb rename to sources/Classes/DB/Params.rb index e8f456c..a6e9259 100644 --- a/Classes/DB/params_db.rb +++ b/sources/Classes/DB/Params.rb @@ -10,9 +10,9 @@ def get_params raise 'db is nil' end rescue StandardError => e - db_error_exit('could not get params', e) + critical_error('could not get params', e) rescue SQLite3::Exception => e - db_error_exit('could not get params', e) + critical_error('could not get params', e) end ret[0] end @@ -23,7 +23,7 @@ def set_param(param, value) prep.bind_param 1, value prep.execute rescue SQLite3::Exception => e - db_error_exit('could not update ' + param, e) + critical_error('could not update ' + param, e) end end @@ -45,7 +45,7 @@ def reset_parameters(init = false) prep.bind_param 7, 'true' prep.execute rescue SQLite3::Exception => e - db_error_exit('could not set / reset parameters', e) + critical_error('could not set / reset parameters', e) end end @@ -93,7 +93,7 @@ def initialize reset_parameters(true) end rescue SQLite3::Exception => e - db_error_exit('exception while retrieving params / initializing params', e) + critical_error('exception while retrieving params / initializing params', e) end end end diff --git a/Classes/Download/mangafox.rb b/sources/Classes/Download/mangafox.rb similarity index 69% rename from Classes/Download/mangafox.rb rename to sources/Classes/Download/mangafox.rb index 06939e0..1359db8 100644 --- a/Classes/Download/mangafox.rb +++ b/sources/Classes/Download/mangafox.rb @@ -2,10 +2,10 @@ class Download_Mangafox private def link_err(data, chapter = false) if !chapter - @db.add_todo(@manga_name, data[0], data[1], data[2]) + @db.add_todo(@manga_data, data[0], data[1], data[2]) @aff.error_on_page_download('X') else - @db.add_todo(@manga_name, data[0], data[1], -1) + @db.add_todo(@manga_data, data[0], data[1], -1) @aff.error_on_page_download('X') end false @@ -16,9 +16,10 @@ def get_links @links end + # todo => vérifier s'il n'est pas possible de supprimer cette fonction def link_generator(volume, chapter, page) chapter = chapter.to_i if chapter % 1 == 0 - link = @site + 'manga/' + @manga_name + '/' + link = @manga_data.site + 'manga/' + @manga_data.name + '/' if volume >= 0 vol_buffer = ((volume >= 10) ? '' : '0') link += 'v' + vol_buffer + volume.to_s + '/' @@ -73,7 +74,7 @@ def chapter_link(link, prep_display = '') if page == nil return link_err(data, true) end - @aff.prepare_chapter("downloading chapter #{data[1]}#{MF_volume_string(data[0])} of #{@manga_name}" + prep_display) + @aff.prepare_chapter("downloading chapter #{data[1]}#{MF_volume_string(data[0])} of #{@manga_data.name}" + prep_display) number_of_pages = page.xpath('//div[@class="l"]').text.split.last.to_i page_nb = 1 while page_nb <= number_of_pages @@ -91,13 +92,13 @@ def chapter_link(link, prep_display = '') end end @aff.dump_chapter - @db.add_trace(@manga_name, data[0], data[1]) - HTML_buffer.instance.add_chapter(@manga_name, data[0], data[1]) + @db.add_trace(@manga_data, data[0], data[1], number_of_pages) + HTML_buffer.instance.add_chapter(@manga_data, data[0], data[1]) true end def todo - todo = @db.get_todo(@manga_name) + todo = @db.get_todo(@manga_data) if todo.size != 0 @aff.prepare_todo biggest = todo.map { |a| [a[2], 0].max }.max @@ -124,10 +125,11 @@ def todo end @aff.end_todo end + @downloaded_a_page end - def missing_chapters - traces = @db.get_trace(@manga_name) + def missing_chapters(get_data = true) + traces = @db.get_trace(@manga_data) i = 0 @links.each do |link| data = data_extractor_MF(link) @@ -138,8 +140,9 @@ def missing_chapters i += 1 end if @downloaded_a_page - data - HTML.new(@db).generate_chapter_index(@manga_name) + if get_data + data + end end @aff.dump end @@ -147,15 +150,16 @@ def missing_chapters def update todo missing_chapters + @downloaded_a_page end def data alternative_names = @doc.xpath('//div[@id="title"]/h3').text - raag_data = @doc.xpath('//td[@valign="top"]') - release = raag_data[0].text.to_i - author = raag_data[1].text.gsub(/\s+/, '').gsub(',', ', ') - artist = raag_data[2].text.gsub(/\s+/, '').gsub(',', ', ') - genres = raag_data[3].text.gsub(/\s+/, '').gsub(',', ', ') + release_author_artist_genres = @doc.xpath('//td[@valign="top"]') + release = release_author_artist_genres[0].text.to_i + author = release_author_artist_genres[1].text.gsub(/\s+/, '').gsub(',', ', ') + artist = release_author_artist_genres[2].text.gsub(/\s+/, '').gsub(',', ', ') + genres = release_author_artist_genres[3].text.gsub(/\s+/, '').gsub(',', ', ') description = @doc.xpath('//p[@class="summary"]').text data = @doc.xpath('//div[@class="data"]/span') status = data[0].text.gsub(/\s+/, '').split(',')[0] @@ -166,39 +170,34 @@ def data type = tmp_type[tmp_type.size - 1] html_name = tmp_type.take(tmp_type.size - 1).join(' ') dir_create(@dir) - write_cover(@doc, '//div[@class="cover"]/img', @dir + 'cover.jpg', @params[1] + 'mangafox/mangas/' + @manga_name + '.jpg') + write_cover(@doc, '//div[@class="cover"]/img', @dir + 'cover.jpg', @params[1] + + 'mangafox/mangas/' + @manga_data.name + '.jpg') File.open(@dir + 'description.txt', 'w') do |txt| - txt << data_conc(@manga_name, description_manipulation(description), @site, @site + @manga_name, author, artist, type, status, genres, release, html_name, alternative_names) + txt << data_conc(@manga_data.name, description_manipulation(description), @manga_data.site, @manga_data.link, author, artist, type, status, genres, release, html_name, alternative_names) end - @aff.data_disp(@manga_in_database) - if @manga_in_database - @db.update_manga(@manga_name, description, author, artist, genres, html_name, alternative_names, rank, rating, rating_max) + @aff.data_disp(@manga_data.in_db) + if @manga_data.in_db + @db.update_manga(@manga_data.name, description, author, artist, genres, html_name, alternative_names, rank, rating, rating_max) else - @db.add_manga(@manga_name, description, @site, (@site + 'manga/' + @manga_name), author, artist, type, status, genres, release, html_name, alternative_names, rank, rating, rating_max) + @db.add_manga(@manga_data, description, author, artist, type, status, genres, release, html_name, alternative_names, rank, rating, rating_max) end end - def initialize(db, manga_name, download_data) + def initialize(manga, download_data = true) + @manga_data = manga @downloaded_a_page = false - @manga_name = manga_name @params = Params.instance.get_params - @dir = @params[1] + 'mangafox/mangas/' + manga_name + '/' - @db = db - @site = 'http://mangafox.me/' - @manga_in_database = db.manga_in_data?(manga_name) - doc_link = @site + '/manga/' + manga_name - @doc = get_page(doc_link, true) - @aff = DownloadDisplay.new('mangafox', manga_name) - if download_data == true || @manga_in_database == false - if redirection_detection(@site + '/manga/' + manga_name) - puts "could not find manga #{manga_name} at " + @site + '/manga/' + manga_name - exit 3 - end - data - end + @dir = @params[1] + manga.site_dir + 'mangas/' + manga.name + '/' + @db = Manga_database.instance + # doc is the variable that stores the raw data of the manga's page + @aff = DownloadDisplay.new('mangafox', manga.name) + @doc = get_page(manga.link, true) if @doc == nil - raise 'failed to get manga ' + manga_name + "'s chapter index" + raise 'failed to get manga ' + manga.name + "'s chapter index" + end + if download_data + data end - @links = extract_links(@doc, @manga_name, '//a[@class="tips"]', doc_link).reverse + @links = extract_links(@doc, manga.name, '//a[@class="tips"]', manga.link).reverse end end diff --git a/Classes/DownloadDisplay.rb b/sources/Classes/DownloadDisplay.rb similarity index 100% rename from Classes/DownloadDisplay.rb rename to sources/Classes/DownloadDisplay.rb diff --git a/sources/Classes/html/html.rb b/sources/Classes/html/html.rb new file mode 100644 index 0000000..229e97f --- /dev/null +++ b/sources/Classes/html/html.rb @@ -0,0 +1,248 @@ +$copied_js_css = false + +class HTML + private + # adds variables to @traces in order to sort / check + def get_traces(manga_data) + ret = [] + tmp = @db.get_trace(manga_data) + tmp.each do |chap| + filename = html_chapter_filename(chap[3], chap[2]) + href = "#####elem#####" + nb_pages = Dir.glob(file_name(@dir + @path_pictures, chap[2], chap[3], -1, true)).size + ret << Struct::HTML_data.new(chap[2], chap[3], chap[4], href, nb_pages, '.' + filename) + end + ret + end + + # gets all the pictures for each chapter + def get_pictures_of_chapter(chapter) + ret = [] + i = 1 + Dir.glob(file_name(@dir + @path_pictures, chapter[:volume], chapter[:chapter], -1, true)).each do |file| + file_relative_pos = file_name('../../mangas/' + @manga_data[1] + '/', chapter[:volume], chapter[:chapter], i) + '.jpg' + ret << "

    #{i}

    " + ret << "
    \n" + ret << " \n" + ret << "
    \n" + i += 1 + end + ret.join + end + + # generates the html for 1 chapter + def html_chapter(arr) + template = @chapter_template + template = template.gsub('#####index#####', '../' + @manga_data[1] + '.html') + template = template.gsub('#####name#####', @manga_name) + if arr[0] != nil + template = template.gsub('#####next#####', arr[0][:href]).gsub('#####html_path#####', '.').gsub('#####elem#####', 'next') + template = template.gsub('#####next_url#####', arr[0][:file_name]) + else + template = template.gsub('#####next#####', '') + template = template.gsub('#####next_url#####', '') + end + if arr[2] != nil + template = template.gsub('#####prev#####', arr[2][:href]).gsub('#####html_path#####', '.').gsub('#####elem#####', 'prev') + template = template.gsub('#####prev_url#####', arr[2][:file_name]) + else + template = template.gsub('#####prev#####', '') + template = template.gsub('#####prev_url#####', '') + end + chap_data = 'Chapter ' + ((arr[1][:chapter] % 1 == 0) ? arr[1][:chapter].to_i : arr[1][:chapter]).to_s + ' ' + volume_int_to_string(arr[1][:volume], true) + max_chap_data = 'Chapter ' + ((@traces[0][:chapter] % 1 == 0) ? @traces[0][:chapter].to_i : @traces[0][:chapter]).to_s + ' ' + volume_int_to_string(@traces[0][:volume], true) + template = template.gsub('#####chapter_data#####', chap_data + ' / ' + max_chap_data) + template = template.gsub('#####pictures#####', get_pictures_of_chapter(arr[1])) + template = template.gsub('#####manga_index#####', @dir + 'index.html') + template = template.gsub('#', '%23') + File.open(@path_html + html_chapter_filename(arr[1][:chapter], arr[1][:volume]), 'w') {|f| f.write(template) } + end + + # generates html for all the chapters and links them ( prev and next buttons ) + def html_generate_chapters(manga_data) + puts 'updating html chapters of ' + manga_data.name + traces = @traces.reject{|chapter| chapter[:nb_pages] == 0} + if traces.size > 0 + if traces.size == 1 + arr = [nil, traces[0], nil] + html_chapter(arr) + else + arr = [nil, traces[0], traces[1]] + html_chapter(arr) + traces.each_cons(3) do |nxt, current, prev| + arr = [nxt, current, prev] + html_chapter(arr) + end + arr = [traces[traces.size - 2], traces[traces.size - 1], nil] + html_chapter(arr) + end + else + puts 'warning : no chapters in database, could not generate chapters'.yellow + end + end + + # converts the array of traces to an array of html code - the chapter index - + # used by the chapter index of each manga + def chapter_list_to_a_list(template) + ret = '' + buff = '' + biggest = @traces.map { |a| [a[:volume], 0].max }.max + @traces = @traces.sort_by { |a| [(a[:volume] < 0) ? (biggest + a[:volume] * -1) * -1 : a[:volume] * -1, -a[:chapter]] } + @traces.each do |chapter| + ret += "
  • \n" + chapter_buff = (chapter[:chapter] % 1 == 0) ? chapter[:chapter].to_i.to_s : chapter[:chapter].to_s + volume_buff = volume_int_to_string(chapter[:volume], true) + buff = chapter[:href].gsub('#####html_path#####', @manga_data[1]).gsub('#####elem#####', volume_buff + ' Chapter ' + chapter_buff).gsub('#', '%23') + ret += buff + ret += "
  • \n" + end + template = template.gsub('#####list#####', ret) + template.gsub('#####first#####', buff) + end + + # manages the js of the index + def index_js_data(site) + ret = "var data = [\n" + mangas = @db.get_manga_list(site) + mangas.sort{|a, b| a.link <=> b.link}.each do |manga| + ret += " ['" + manga.data[11].gsub('; ', '
    ').gsub("'", '###guillemet###') + "', '" + ret += manga.data[12].gsub('; ', '
    ').gsub("'", '###guillemet###') + "', '" + ret += manga.data[2].gsub("\r", '
    ').gsub("\n", '
    ').gsub("'", '###guillemet###') + "', '" + ret += manga.data[5] + "', '" + ret += manga.data[6] + "', '" + ret += manga.data[8] + "', '" + ret += manga.data[9] + "', " + ret += manga.data[10].to_s + ", '" + ret += manga.data[7] + "', " + ret += manga.data[13].to_s + ', ' + ret += (manga.data[14] * 100 / manga.data[15]).round(2).to_s + "],\n" # rating and rating max + end + ret += '];' + ret + end + + # generates the links for each manga ( cover + name ), used for the manga index + def html_get_data(site) + mangas = @db.get_manga_list(site) + ret = [] + i = 0 + mangas.sort{|a, b| a.link <=> b.link}.each do |manga| + ret << " ' + i += 1 + end + ret.join("\n") + end + + def file_copy(file_name, dest) + file = File.open(file_name).read + File.open(dest, 'w') {|f| f.write(file)} + end + + def copy_html_related_files + unless $copied_js_css + dir_create(@dir + 'html/css/') + dir_create(@dir + 'html/js/') + # css + file_copy(__dir__ + '/../../../templates/chapter_index_template.css', @dir + '/html/css/chapter_index.css') + file_copy(__dir__ + '/../../../templates/chapter_template.css', @dir + '/html/css/chapter.css') + file_copy(__dir__ + '/../../../templates/manga_index_template.css', @dir + '/html/css/manga_index.css') + # js + file_copy(__dir__ + '/../../../templates/chapter_index_template.js', @dir + '/html/js/chapter_index.js') + file_copy(__dir__ + '/../../../templates/chapter_template.js', @dir + '/html/js/chapter.js') + $copied_js_css = true + end + end + + public + # generates the chapter index, includes description, cover, names of artist and author, ... + def generate_chapter_index(manga_data, index = true) + if @params[8] == 'true' || @force_html == true + @dir = @params[1] + get_dir_from_site(manga_data.site) + puts 'updating chapter index of ' + manga_data.name + @manga_init = true + @manga_data = [] + if manga_data.in_db + @manga_data = manga_data.data + else + @manga_data = @db.get_manga(manga_data) + end + @params[1] + manga_data.site_dir + @path_to_cover = '/mangas/' + @manga_data[1] + '.jpg' + @path_pictures = '/mangas/' + @manga_data[1] + '/' + @manga_name = @manga_data[11] + @path_html = @dir + 'html/' + @manga_data[1] + @index_path = @dir + 'index.html' #todo : manage sites + @traces = get_traces(manga_data) + dir_create(@path_html) + template = File.open('templates/chapter_index_template.html').read + template = template.gsub('#####name#####', @manga_name) + template = template.gsub('#####description#####', @manga_data[2].gsub("\n", '
    ')) + template = template.gsub('#####site#####', html_a_buffer(@manga_data[4])) + template = template.gsub('#####author#####', html_a_buffer(@manga_data[5])) + template = template.gsub('#####artist#####', html_a_buffer(@manga_data[6])) + template = template.gsub('#####type#####', html_a_buffer(@manga_data[7])) + template = template.gsub('#####status#####', html_a_buffer(@manga_data[8])) + template = template.gsub('#####genres#####', html_a_buffer(@manga_data[9])) + template = template.gsub('#####date#####', html_a_buffer(@manga_data[10].to_s)) + template = template.gsub('#####alternative_names#####', @manga_data[12].split('; ').join('
    ')) + template = template.gsub('#####cover#####', '..' + @path_to_cover) + template = template.gsub('#####index_path#####', '../index.html') + template = template.gsub('#####rank#####', @manga_data[13].to_s) + template = template.gsub('#####rating#####', @manga_data[14].to_s + ' / ' + @manga_data[15].to_s) + template = chapter_list_to_a_list(template).gsub('#', '%23') + File.open(@path_html + '.html', 'w') {|f| f.write(template)} + if index + @chapter_template = File.open('templates/chapter_template.html').read + html_generate_chapters(manga_data) + end + end + end + + # generates the manga index ( the page containing the links to all mangas ) + def generate_index + if @params[8] == 'true' || @force_html == true + puts 'updating html of manga index' + # every single site has it's own directory witch is get through the function get_dir_from_site + sites = ['http://mangafox.me/'] + sites.each do |site| + @dir = @params[1] + get_dir_from_site(site) + copy_html_related_files + template = File.open('templates/manga_index_template.html').read + template = template.gsub('#####list#####', html_get_data(site)) + File.open(@dir + 'index.html', 'w') {|f| f.write(template)} + js = File.open('templates/manga_index_template.js').read + js = js.gsub('#####tab#####', index_js_data(site)) + File.open(@dir + 'html/js/manga_index.js', 'w') {|f| f.write(js)} + end + end + # important !!! => les fichiers css DOIVENT être copiés + end + + # the constructor copies all the css and places them in the html dir + def initialize(force_html = false) + @force_html = force_html + @params = Params.instance.get_params + @nsfw_genres = @params[10].split(', ') + @manga_init = false + @db = Manga_database.instance + @dir = '' + @manga_data = [] + @traces = [] + end +end \ No newline at end of file diff --git a/Classes/html/html_buffer.rb b/sources/Classes/html/html_buffer.rb similarity index 90% rename from Classes/html/html_buffer.rb rename to sources/Classes/html/html_buffer.rb index 3c66a31..97be6a0 100644 --- a/Classes/html/html_buffer.rb +++ b/sources/Classes/html/html_buffer.rb @@ -20,7 +20,6 @@ def get_data end def initialize - Struct.new('Updated', :name, :downloaded) @dl = [] end end diff --git a/sources/Classes/instructions/Instructions_exec.rb b/sources/Classes/instructions/Instructions_exec.rb new file mode 100644 index 0000000..10db249 --- /dev/null +++ b/sources/Classes/instructions/Instructions_exec.rb @@ -0,0 +1,184 @@ +# class used for the argument management +# it returns the required commands on success and nil on failure +class Instructions_exec + attr_reader :data_to_prepare + private + def required_arguments(instruction, args) + if args.size == 0 + puts 'Error : '.red + ' the ' + instruction.yellow + ' option needs at least an argument' + false + else + true + end + end + + def get_data_parser(instruction) + parser = Data_parser.new(instruction) + # id needs 2 arguments : name ( args[0] and site args[1] ) + parser.on('id', 2) do |args| + data = Manga_data.new(nil, args[0], args[1], nil, nil) + @data_to_prepare << data + end + # link needs one argument : link ( args[0] ) + parser.on('link', 1) do |args| + link = args[0] + if link.size != 0 && link[link.size - 1] == '/' + # this ensures that all links do not finish by a '/' + link = link.chomp('/') + end + data = Manga_data.new(nil, nil, nil, link, nil) + @data_to_prepare << data + end + # File parser uses args[0] as file name and directly outputs it's + # content in @data_to_prepare + parser.on('file', 1) do |args| + e = File_parser.new(args[0], @data_to_prepare) + unless e.good + puts 'ignoring file instruction'.yellow + end + end + # query + parser.on('query', 1) do |args| # <====================================================== + puts 'query is currently a placeholder' +# query = Query_Manager.new +# data = query.run(args[0]) + end + # all does not use any arguments + # even tho the elements from the database are valid, they are sent to + # @data_to_prepare to avoid duplicates + parser.on('all', 0) do || + @data_to_prepare += Manga_database.instance.get_manga_list + end + parser + end + + def get_valid_data(function, connection, args, error_if_no_elements = true) + tmp = args + tmp = tmp.join(' ') + parser = get_data_parser(function) + parser.parse args + filter = Manga_data_filter.new(@data_to_prepare) + ret = filter.run(connection, true) + @data_to_prepare.clear + if ret.size == 0 && error_if_no_elements + puts 'Warning : '.yellow + 'ignoring instruction ' + '['.yellow + function + ' ' + + tmp + ']'.yellow + '. Reason is : ' + 'no elements to use'.yellow + end + ret + end + + def init_parser + @parser = Instruction_parser.new + # manga download + @parser.on 'add' do |args| + buff = get_valid_data('add', true, args) + add(buff, true) unless buff.empty? + end + @parser.on 'update' do |args| + buff = get_valid_data('update', false, args) + update(buff) unless buff.empty? + end + @parser.on 'download', 'dl' do |args| + buff = get_valid_data('download', true, args, false) + download(buff) unless buff.empty? + end + @parser.on 'correct' do |args| + buff = get_valid_data('correct', true, args) # <======================= + # todo correct + # fonction pour ajouter les nouveaux champs de données aux traces + # permet aussi de supprimer les chapitres avec un mauvais nombre de pages + # cette fonction vient étendre delete-diff + end + @parser.on 'redl', 're-download' do |args| + re_download(args, self) + end + # html + @parser.on 'html' do |args| + buff = get_valid_data('html', false, args, false) + html_manager(buff) + end + # manga database related + @parser.on 'clear' do |args| + buff = get_valid_data('clear', false, args) + clear(buff) unless buff.empty? + end + @parser.on 'todo' do |args| + buff = get_valid_data('update', false, args) + update(buff, true) unless buff.empty? + end + @parser.on 'delete' do |args| + buff = get_valid_data('delete', false, args) + delete(buff) unless buff.empty? + end + @parser.on 'delete-db' do |args| + buff = get_valid_data('delete-db', false, args) + delete(buff, false) unless buff.empty? + end + @parser.on 'output' do |args| + buff = get_valid_data('output', false, args, false) + output(buff) unless buff.empty? + end + @parser.on 'to-file' do |args| + buff = get_valid_data('to-file', false, args, false) # <================= + # todo to-file + puts 'placeholder for to-file' + pp buff + end + @parser.on 'info' do |args| + buff = get_valid_data('list', false, args, false) + infos(buff, true) unless buff.empty? + end + # params related + @parser.on 'macros' do |args| # <========================================== + # todo macros + puts 'placeholder for macros' + tmp = get_data_parser('macros') + tmp.parse args + end + @parser.on 'param', 'params' do |args| + params_management(args) if required_arguments('param', args) + end + # other + # help and version completely ignore the arguments that are given to them + @parser.on 'help', '--help', '-h' do || + help + end + @parser.on 'version', '--version' do || + version + end + @parser.jump_when('id', 2) + @parser.jump_when('link', 1) + @parser.jump_when('file', 1) + @parser.jump_when('query', 1) + @parser.jump_when('set', 2) + @parser.jump_when('p', 1) + @parser.jump_when('c', 1) + @parser.jump_when('v', 1) + end + + public + def clear_data + @data_to_prepare.clear + end + + # executes the program + def run + if ARGV.size == 0 + puts 'no arguments, executing default' + puts '' + puts '' 'executing : ' + 'update'.green + ' ' + 'all'.yellow + puts '' + buff = get_valid_data('update', false, ['all']) + update(buff) unless buff.empty? + # todo => default execution from params ? + else + @parser.parse ARGV + end + puts '' + end + + def initialize + init_parser + @data_to_prepare = [] + end +end diff --git a/sources/Classes/instructions/Manga_data_filter.rb b/sources/Classes/instructions/Manga_data_filter.rb new file mode 100644 index 0000000..09d2207 --- /dev/null +++ b/sources/Classes/instructions/Manga_data_filter.rb @@ -0,0 +1,56 @@ +=begin +this class is used to ensure that only the required Manga_data are used by the +instructions's functions. +The instructions are in 2 distinct groups : +- those that need data from the database +- Those that need data from internet +=end +class Manga_data_filter + private + def add_to_return(todo) + # checking if the data is not already in the new array to avoid duplicates + if @ret.select{|elem| elem.link == todo.link}[0] == nil + @ret << todo + end + end + + def connection(data_display) + @array.each do |todo| + if todo.resolve(true, data_display) + add_to_return(todo) + else # the data cannot be resolved, ignoring it + next + end + end + end + + def no_connection(data_display) + @array.each do |todo| + if todo.in_db + add_to_return(todo) + elsif todo.resolve(false, data_display) + add_to_return(todo) + else # the data cannot be resolved, ignoring it + next + end + end + end + + public + # function used to filter the array and return the "good" array + def run(connect, data_display) + if connect + connection(data_display) + else + no_connection(data_display) + end + @ret.sort{|a, b| a.link <=> b.link} + @ret + end + + def initialize(array) + @array = array + @ret = [] + @db = Manga_database.instance + end +end diff --git a/sources/Classes/instructions/Parsers/Data_parser.rb b/sources/Classes/instructions/Parsers/Data_parser.rb new file mode 100644 index 0000000..dd80002 --- /dev/null +++ b/sources/Classes/instructions/Parsers/Data_parser.rb @@ -0,0 +1,69 @@ +# this class is used to cut the arguments given to MangaScrap and allow +class Data_parser + private + # executes the previously parsed arguments + def run + @to_exec.each do |args| + @instructions[args[0]][1].call args[1] + end + end + + # function used to exit whenever there is not enought arguments for a data + def nb_arguments_exit(arg, qt, count) + puts 'Error :'.red + ' for instruction "' + @command.yellow + ' ' + @instruction.yellow + '"' + puts arg.yellow + ' was given ' + count.to_s.yellow + ' argument(s), ' + qt.to_s.yellow + ' are required' + exit 4 + end + + def bad_argument_exit(error) + puts 'Error :'.red + ' for instruction "' + @command.yellow + ' ' + @instruction.yellow + '"' + puts 'unrecognised argument "' + error.yellow + '"' + exit 4 + end + + # the arguments are cut with the @instructions + def args_extract(arg, qt) + ret = [] # extracted arguments + count = 0 + while @args.size != 0 && count < qt + ret << @args[0] + @args.shift + count += 1 + end + if count != qt + nb_arguments_exit(arg, qt, count) + end + ret + end + + public + # gets an array of arguments and cuts it into more readable arrays witch are then used by run + def parse(args) + @args = args + @instruction = args.join(' ') + until @args.empty? + arg = @args[0] + if @instructions.key? arg + @args.shift + buff = args_extract(arg, @instructions[arg][0]) + @to_exec << [arg, buff] + else + bad_argument_exit(arg) + end + end + run + end + + # adds instruction with a block + def on(instruction, arguments, &bloc) + @instructions[instruction] = [arguments, bloc] + end + + def initialize(command) + @to_exec = [] + @instructions = {} + @args = '' + @command = command + @instruction = '' + end +end diff --git a/sources/Classes/instructions/Parsers/File_parser.rb b/sources/Classes/instructions/Parsers/File_parser.rb new file mode 100644 index 0000000..f641752 --- /dev/null +++ b/sources/Classes/instructions/Parsers/File_parser.rb @@ -0,0 +1,55 @@ +class File_parser + private + # extracting all the elements from the file + # ignores empty lines and commented ones + def parse_file + line_count = 0 + @text.each_line do |line| + if line == nil || line == '' || line.size == 0 || line[0] == '#' || line[0] == "\n" + next + end + elements = line.split(' ') + if elements.size > 2 + puts 'Waring : '.red + 'in file "' + @file_name.yellow + '" ignoring line ' + line_count.to_s.yellow + ' ( it has more than one space and is not a comment )' + else + if elements.size == 1 + @ret << Manga_data.new(nil, nil, nil, elements[0], nil) + else + @ret << Manga_data.new(nil, elements[0], elements[1], nil, nil) + end + end + line_count += 1 + end + end + + # file opening with exception handling + def open_file + begin + @text = File.open(@file_name).read + @text.gsub!(/\r\n?/, '\n') + rescue => e + critical_error('could not open file ' + @file_name.yellow, e) + end + true + end + + def run + if open_file && parse_file + @status = true + end + end + + public + # this function allows to know if the data whas correctly extracted from the file or not + def good + @status + end + + def initialize(file_name, array) + @file_name = file_name + @ret = array + @text = nil + @status = false + run + end +end diff --git a/sources/Classes/instructions/Parsers/Instruction_parser.rb b/sources/Classes/instructions/Parsers/Instruction_parser.rb new file mode 100644 index 0000000..403ffe9 --- /dev/null +++ b/sources/Classes/instructions/Parsers/Instruction_parser.rb @@ -0,0 +1,109 @@ +# this class is used to cut the arguments given to MangaScrap and allow +class Instruction_parser + private + # function used to exit whenever there is not enought argument for a 'jump' + def jump_error_exit(prev, jump, parent) + puts 'Error : '.red + prev.yellow + ' requires ' + jump.to_s.yellow + ' more argument(s)' + puts 'in : [' + parent + ']' + exit 4 + end + + # executes the previously parsed arguments + def run + lock = true + @to_exec.each do |args| + buff = args + if lock + lock = false + puts '' + else + puts '', '', '', '', '' + end + display = 'executing : ' + buff[0].green + ' ' + buff[1].join(' ') + @jump.each do |e| + display = display.gsub(e[0] + ' ', e[0].yellow + ' ') # todo improve this to avoid colorizing arguments + end + puts display + puts '' + @instructions[args[0]].call args[1] + end + end + + # the arguments are cut with the @instructions + # the @jump variables are used to allow arguments ( such as a file name ) to have + # values as instructions + def args_extract(arg) + ret = [] # extracted arguments + jump = 0 # used if a @jump argument is found + prev = @args[0] # used for error display purposes + # args_extract tries to shift all of @args but stops when an @instruction is found + while @args.size != 0 + if jump == 0 + prev = @args[0] + end + key = @instructions.key?(@args[0]) + buff = @jump.select{|e| e[0] == @args[0]}[0] + if buff != nil + jump = buff[1] + 1 + end + if jump == 0 && key + return ret + else + if @args.empty? + jump_error_exit(prev, jump, arg + ' ' + ret) + end + ret << @args[0] + @args.shift + end + if jump != 0 + jump -= 1 + end + end + if jump != 0 + jump_error_exit(prev, jump, arg.green + ' ' + prev) + end + ret + end + + public + # gets an array of arguments and cuts it into more readable arrays witch are then used by run + def parse(args) + @args = args + until @args.empty? + arg = @args[0] + if @instructions.key? arg + @args.shift + buff = args_extract(arg) + @to_exec << [arg, buff] + else + puts 'Error : '.red + 'unrecognised instruction "' + arg.yellow + '"' + exit 4 + end + end + run + end + + # adds instruction with a block + def on(*args, &test) + args.each do |arg| + @instructions[arg] = test + end + end + + # this option allows for file names / site names / ... that are recognised as instructions + # ex : file update + def jump_when(data, values) + unless data != nil && data != '' && values <= 0 + @jump << [data, values] + end + end + + def initialize(nb_args = nil) + @to_exec = [] + @jump = [] + @instructions = {} + @on_array = nil + @args = '' + @nb_args = nb_args + end +end diff --git a/sources/Classes/instructions/Parsers/Query_Parser.rb b/sources/Classes/instructions/Parsers/Query_Parser.rb new file mode 100644 index 0000000..1db2a0a --- /dev/null +++ b/sources/Classes/instructions/Parsers/Query_Parser.rb @@ -0,0 +1,19 @@ +class Query_Parser + private + + public + def run(query) + @query = query + pp @query + end + + # adds instruction with a block + def on(instruction, arguments, is_array_in_db = false, &bloc) + @instructions[instruction] = [arguments, is_array_in_db, bloc] + end + + def initialize + @instructions = {} + @query = [] + end +end diff --git a/sources/Classes/instructions/query.rb b/sources/Classes/instructions/query.rb new file mode 100644 index 0000000..f3f2858 --- /dev/null +++ b/sources/Classes/instructions/query.rb @@ -0,0 +1,46 @@ +#class used to get the mangas with the query option +class Query_Manager + private + def init_parser + @parser.on('date', 'year') do |arg| + + end + @parser.on('site', 'string') do |arg| + + end + @parser.on('author', 'string', true) do |arg| + + end + @parser.on('artist', 'string', true) do |arg| + + end + @parser.on('genres', 'string', true) do |arg| + + end + @parser.on('added', 'date') do |arg| + + end + @parser.on('type', 'string') do |arg| + + end + @parser.on('name', 'string') do |arg| + + end + @parser.on('description', 'string') do |arg| + + end + end + + public + def run(query) + @query = query.split(' ') + @parser.run(@query) + + end + + def initialize + @parser = Query_Parser.new + @query = '' + init_parser + end +end diff --git a/sources/add.rb b/sources/add.rb deleted file mode 100644 index 202f79d..0000000 --- a/sources/add.rb +++ /dev/null @@ -1,58 +0,0 @@ -def add_file(ret, db, data, html) - ret.each do |name| - site = 'http://mangafox.me/' - if name.size != 1 - site == name[1] - end - if db.manga_in_data?(name[0]) == true && data == false - puts name[0] + ' is already in database' - else - if site != 'http://mangafox.me/' - puts 'sorry, MangaScrap does not deal with other sites than mangafox ( yet )' - exit 4 - else - if get_mf_class(db, name[0], data) == nil - puts 'adding next element' - end - html.generate_chapter_index(name[0], false) - end - end - end -end - -def add(db, data) - html = HTML.new(db) - if ARGV.size == 1 - puts 'not enough arguments' - exit 5 - end - manga_name = ARGV[1] - site = 'http://mangafox.me/' - file = false - if ARGV.size > 2 - ret = get_mangas - if ret != nil - file = true - add_file(ret, db, data, html) - else - site = ARGV[2] - end - end - unless file - if db.manga_in_data?(manga_name) && data == false - puts manga_name + ' is already in database' - else - if site != 'http://mangafox.me/' - puts 'sorry, MangaScrap does not deal with other sites than mangafox ( yet )' - exit 4 - else - if get_mf_class(db, manga_name, data) == nil - exit 3 - end - html.generate_chapter_index(manga_name, false) - end - end - end - html.generate_index - puts 'done' -end diff --git a/sources/clear.rb b/sources/clear.rb deleted file mode 100644 index bc25d3f..0000000 --- a/sources/clear.rb +++ /dev/null @@ -1,30 +0,0 @@ -def confirm_clear (db, name) - puts "going to delete #{name}'s todo database elements" - puts "Write 'YES' to continue" - ret = STDIN.gets.chomp - puts '' - if ret == 'YES' - db.clear_todo(name) - puts "deleted #{name}'s todo from database" - else - puts "did not delete #{name}" - end -end - -def clear (db) - if ARGV.size < 2 - puts "need a manga's name to delete" - elsif ARGV.size == 2 - confirm_clear(db, ARGV[1]) - else - ret = get_mangas - if ret != nil - ret.each do |name| - confirm_clear(db, name[0]) - end - else - puts 'error while trying to get content of file ( -f option )' - exit 5 - end - end -end diff --git a/sources/delete.rb b/sources/delete.rb deleted file mode 100644 index 27247e6..0000000 --- a/sources/delete.rb +++ /dev/null @@ -1,82 +0,0 @@ -def del(path, extension) - delete_folder = false - Dir.glob(path + '/*').sort.each do |file| - delete_folder = true - puts 'deleting file : ' + file - File.delete(file) - end - if delete_folder - Dir.delete(path) - end - if File.exist?(path + extension) - puts 'deleting file : ' + path + extension - File.delete(path + extension) - end -end - -def files_to_delete(params, path_site, name) - del(params[1] + path_site + 'html/' + name, '.html') - del(params[1] + path_site + 'mangas/' + name, '.jpg') -end - -def confirm_delete (db, name, delete_files) - unless db.manga_in_data?(name) - puts "did not find #{name} in database" - return false - end - puts "going to delete #{name} from database " + ((delete_files) ? 'and delete files' : 'only') - puts "Write 'YES' to continue" - ret = STDIN.gets.chomp - puts '' - if ret == 'YES' - if delete_files - puts 'deleting files' - erase_files(db, name) - end - db.delete_manga(name) - puts "deleted #{name} from database" - return true - end - puts "did not delete #{name}" - false -end - -def erase_files(db, name) - data = db.get_manga(name) - params = Params.instance.get_params - if data != nil - if data[3] == 'http://mangafox.me/' - files_to_delete(params, 'mangafox/', name) - else - puts 'unmanaged site for deletion : ' + data[3] - puts 'please report this error' - exit 4 - end - else - puts 'did not find manga in database' - exit 1 - end -end - -def delete(db, delete_files = false) - html = HTML.new(db) - if ARGV.size < 2 - puts "need a manga's name to delete" - elsif ARGV.size == 2 - if confirm_delete(db, ARGV[1], delete_files) - html.generate_index - end - else - ret = get_mangas - if ret != nil - ret.each do |name| - if confirm_delete(db, name, delete_files) - html.generate_index - end - end - else - puts 'error while trying to get content of file ( -f option )' - exit 5 - end - end -end diff --git a/sources/download.rb b/sources/download.rb deleted file mode 100644 index ed83839..0000000 --- a/sources/download.rb +++ /dev/null @@ -1,69 +0,0 @@ -def download_file(ret, db) - tab_dw = [] - tab_name = [] - site = '' - ret.each do |manga| - if manga.size == 1 - site = 'http://mangafox.me/' - else - site == manga[1] - end - if db.manga_in_data?(manga[0]) - puts manga[0] + ' is already in database' - else - if site != 'http://mangafox.me/' - puts 'sorry, MangaScrap does not deal with other sites than mangafox ( yet )' - exit 4 - end - end - elem = get_mf_class(db, manga[0], false) - if elem == nil - puts "error while downloading #{manga[0]}, going to next element" - next - end - tab_dw << elem - tab_name << manga[0] - end - i = 0 - tab_name.each do |manga| - puts 'downloading ' + manga - tab_dw[i].update - i += 1 - end -end - -def no_file(site, db) - if site != 'http://mangafox.me/' - puts 'sorry, sites other than mangafox are not yet managed' - exit 4 - else - dw = get_mf_class(db, ARGV[1], false) - if dw == nil - exit 3 - end - dw.update - end -end - -def download(db) - if ARGV.size < 2 - puts 'not enough arguments' - exit 5 - end - file = false - site = 'http://mangafox.me/' - ret = nil - if ARGV.size > 2 - ret = get_mangas - if ret != nil - file = true - else - site = ARGV[2] - end - end - if !file - no_file(site, db) - else - download_file(ret, db) - end -end diff --git a/sources/help.rb b/sources/help.rb deleted file mode 100644 index 491b825..0000000 --- a/sources/help.rb +++ /dev/null @@ -1,10 +0,0 @@ -def help - begin - file = File.open('utils/help.txt', 'r') - content = file.read - puts content - rescue Errno::ENOENT => e - puts 'could not open help file' - puts e.message - end -end diff --git a/sources/html_manager.rb b/sources/html_manager.rb deleted file mode 100644 index 7207d2e..0000000 --- a/sources/html_manager.rb +++ /dev/null @@ -1,25 +0,0 @@ -def html_manager(db) - html = HTML.new(db) - case ARGV.size - when 1 - # index generation at end - when 2 - if db.manga_in_data?(ARGV[1]) - html.generate_chapter_index(ARGV[1], true) - else - puts 'could not find ' + ARGV[1] + ' in database' - exit 5 - end - when 3 - ret = get_mangas - if ret != nil - ret.each do |name| - html.generate_chapter_index(name[0], true) - end - end - else - puts 'bad amount of arguments, expecting a manga name or a manga file' - exit 5 - end - html.generate_index -end diff --git a/sources/init.rb b/sources/init.rb index efd1643..1f45c44 100644 --- a/sources/init.rb +++ b/sources/init.rb @@ -1,44 +1,19 @@ -def copy_dir_pictures(location, path_pictures, dir) - path = path_pictures + dir - dir_create(Dir.home + path) - logos = Dir[location + '/pictures/' + dir + '/*'] - logos.each do |logo| - # WARNING => check if files exists - copy_file(logo, Dir.home + path + '/' + logo.split('/').last) - end -end - -def copy_pictures(location) - path_pictures = '/.MangaScrap' + '/pictures' - dir_create(Dir.home + path_pictures) - copy_dir_pictures(location, path_pictures, '/logos') - copy_dir_pictures(location, path_pictures, '/other') -end - -def copy_templates(location) - templates = Dir[location + '/templates/*'] - templates.each do |template| - # WARNING => check if files exists - copy_file(template, Dir.home + '/.MangaScrap/templates/' + template.split('/').last) - end -end - -def initialize_mangascrap(location) +#anything that needs to be done before the rest of MangaScrap is executed is placed here +def initialize_mangascrap + Struct.new('Arg', :name, :sub_args, :nb_args, :does_not_need_args?) + Struct.new('Sub_arg', :name, :nb_args) + Struct.new('Updated', :name, :downloaded) + Struct.new('Query_arg', :name, :arg_type, :sql_column, :sub_string) + Struct.new('HTML_data', :volume, :chapter, :date, :href, :nb_pages, :file_name) begin - dir_create(Dir.home + '/.MangaScrap') dir_create(Dir.home + '/.MangaScrap/db') - dir_create(Dir.home + '/.MangaScrap/templates') - copy_templates(location) - copy_pictures(location) rescue StandardError => error puts 'error while initializing MangaScrap' puts "error message is : '" + error.message + "'" exit 5 end - db = Manga_database.new init_utils if Params.instance.get_params[11] == 'false' String.disable_colorization = true end - db end diff --git a/sources/instructions/basic_instructions.rb b/sources/instructions/basic_instructions.rb new file mode 100644 index 0000000..5a0fc10 --- /dev/null +++ b/sources/instructions/basic_instructions.rb @@ -0,0 +1,178 @@ +# whenever a user confirmation is required, this function is called +def require_confirmation (message, manga_list = nil) + puts message + if manga_list != nil + output(manga_list) + puts '' + end + puts "Write 'YES' to continue" + ret = STDIN.gets.chomp + puts '' + if ret == 'YES' || ret == 'Yes' || ret == 'yes' + true + else + false + end +end + +# adds all mangas to the database +def add(mangas, generate_chapters = false) + puts 'adding ' + mangas.size.to_s + ' element(s) to database' + html = HTML.new + mangas.each do |manga| + dw = manga.get_download_class + if dw == nil + next + end + if generate_chapters + html.generate_chapter_index(manga, false) + end + end + html.generate_index + puts 'done' +end + +# updates all mangas by calling the right classes +# should todo_only = true, the function missing_chapters will be called instead of +# update ( witch also calls missing_chapters ) +def update(mangas, todo_only = false) + puts 'updating ' + mangas.size.to_s + ' element(s)' + html = HTML.new + params = Params.instance.get_params + mangas.each do |manga| + dw = manga.get_download_class + if dw == nil + next + end + if todo_only + generate_html = dw.todo + else + generate_html = dw.update + end + if params[6] == 'true' + generate_html = (delete_diff(dw.get_links, manga) || generate_html) + end + html.generate_chapter_index(manga) if generate_html + end + html.generate_index + puts 'done' +end + +# basically just adds all mangas before updating them all +# also re-filters the mangas to ensure that there are no errors +def download(mangas) + add(mangas) + puts '' + filter = Manga_data_filter.new(mangas) + mangas_prepared = filter.run(false, true) + update(mangas_prepared) +end + +# ensures that the HTML class is correctly used to generate all the needed html +def html_manager(mangas) + html = HTML.new + mangas.each do |manga| + html.generate_chapter_index(manga, true) + end + html.generate_index + puts 'done' +end + +# just displays the version of MangaScrap witch is contained in a file +def version + begin + file = File.new(__dir__ + '/../../utils/version.txt', 'r') + line = file.gets + file.close + rescue => err + puts 'Error : '.red + 'could not open version file' + puts err.message + err + end + puts 'MangaScrap version : ' + line +end + +# displays the name + site of the mangas ( used to create manga list files ) +def output(manga_list) + manga_list.sort{|a, b| a.link <=> b.link}.each do |manga| + puts manga.name + ' ' + manga.site + end +end + +# infos is used to get all the available information on the ùanga from the database +# this includes traces and _todo +def infos(manga_list) + # todo infos + puts 'currently a placeholder' + pp manga_list +# manga_list.sort{|a, b| a.link <=> b.link}.each do |manga| +# end +end + +# delete all _todo elements from the manga list +def clear(manga_list) + if require_confirmation('you are about to ' + 'delete'.red + ' all todo elements of the following element(s) :', manga_list) + db = Manga_database.instance + manga_list.each do |manga| + db.clear_todo(manga) + end + puts 'deleted all todo elements' + else + puts 'did not delete anything' + end +end + +# deletes all the mangas of the list from the database +# should delete_files be set at true, the files will also be deleted +def delete(manga_list, delete_files = true) + if require_confirmation('you are about to ' + 'delete'.red + ' the following element(s) from the database' + ((delete_files) ? + ' and '.yellow + 'all of the downloaded pages' : + ' but ' + 'not'.yellow + ' the downloaded files' ), manga_list) + db = Manga_database.instance + path = Params.instance.get_params[1] + manga_list.each do |manga| + db.delete_manga(manga) + if delete_files + site_dir = get_dir_from_site(manga.site) + delete_files(path + site_dir + 'html/' + manga.name, '.html') + delete_files(path + site_dir + 'mangas/' + manga.name, '.jpg') + end + end + puts 'deleted all elements' + HTML.new.generate_index + else + puts 'did not delete anything' + end +end + +# displays the help after colorizing it +def help + begin + file = File.open('utils/help.txt', 'r') + content = file.read + content = content.gsub('_todo', 'todo') + instructions = %w(link id file query all add update download redl param version help list output delete delete-db html todo clear infos todo reset set) + instructions.each do |instruction| + content = content.gsub('[' + instruction + ']g', instruction.green).gsub('[' + instruction + ']y', instruction.yellow) + end + content = content.gsub('INSTRUCTIONS', 'INSTRUCTIONS'.red) + content = content.gsub('EXAMPLES', 'EXAMPLES'.red) + content = content.gsub('DESCRIPTION', 'DESCRIPTION'.red) + content = content.gsub('Warning !', 'Warning !'.red) + content = content.gsub('NOT', 'NOT'.red) + content = content.gsub('note :', 'note :'.magenta) + content = content.gsub('definition :', 'definition :'.magenta) + content = content.gsub('definitions :', 'definitions :'.magenta) + content = content.gsub('[data arguments compatible]', '[data arguments compatible]'.blue) + content = content.gsub('[own arguments]', '[own arguments]'.blue) + content = content.gsub('[in database]', '[in database]'.blue) + content = content.gsub('[not in database]', '[not in database]'.blue) + content = content.gsub('[elements required]', '[elements required]'.blue) + content = content.gsub('[data argument]', '[data argument]'.blue) + content = content.gsub('[data arguments]', '[data arguments]'.blue) + puts content + rescue Errno::ENOENT => e + puts 'could not open help file' + puts e.message + end +end diff --git a/sources/delete_diff.rb b/sources/instructions/delete_diff.rb similarity index 60% rename from sources/delete_diff.rb rename to sources/instructions/delete_diff.rb index a09b188..d3a4197 100644 --- a/sources/delete_diff.rb +++ b/sources/instructions/delete_diff.rb @@ -7,15 +7,17 @@ def delete_bad_files(traces, data, dir) end #todo => attention à ce que le fichier repéré soit bel et bien celui supprimé -#todo => cette fonction nécessite -def delete_diff(db, chap_list, name) +def delete_diff(chap_list, manga_data) params = Params.instance.get_params - # todo : manage site - dir = params[1] + 'mangafox/mangas/' + name + '/' + db = Manga_database.instance + dir = params[1] + manga_data.site_dir + manga_data.to_complete + manga_data.name + '/' chap_list = chap_list.map{|elem| data_extractor_MF(elem).shift(2)}.reverse - trace = db.get_trace(name) - trace = trace.each {|elem| elem.shift} # delete id of each chapter - trace = trace.each {|elem| elem.shift} # delete id of manga + tmp_trace = db.get_trace(manga_data) + trace = [] + tmp_trace.each do |e| + # creating an array that contains only volume and chapter values + trace << [e[2], e[3]] + end pb = trace.select{|elem| !chap_list.include?(elem)} deleted = false pb.each do |chap| @@ -25,15 +27,12 @@ def delete_diff(db, chap_list, name) File.delete(file) end # todo : manage site - Dir.glob(params[1] + 'mangafox/html/' + name + html_chapter_filename(chap[1], chap[0])).each do |file| + Dir.glob(params[1] + 'mangafox/html/' + manga_data.name + html_chapter_filename(chap[1], chap[0])).each do |file| puts 'deleting file : '.yellow + file File.delete(file) end - db.delete_trace(name, chap) + db.delete_trace(manga_data, chap) deleted = true end - if deleted - puts '' - HTML.new(db).generate_chapter_index(name) - end + deleted end diff --git a/sources/params.rb b/sources/instructions/params.rb similarity index 69% rename from sources/params.rb rename to sources/instructions/params.rb index 1507d47..ad32601 100644 --- a/sources/params.rb +++ b/sources/instructions/params.rb @@ -1,7 +1,7 @@ # reading the file, adding DB values and setting the colors ( when needed ) def param_list params = Params.instance.get_params - template = File.open(Dir.home + '/.MangaScrap/templates/params.txt').read + template = File.open('templates/params.txt').read template = template.gsub('#{params[1]}', params[1].green) template = template.gsub('#{params[2]}', params[2].to_s.green.to_s) template = template.gsub('#{params[3]}', params[3].to_s.green.to_s) @@ -94,47 +94,47 @@ def param_check_string(db, param, display, value) end # checking arguments for params -def args_check - if ARGV.size < 3 +def args_check(args) + if args.size < 3 puts 'not enough arguments for parameter set' puts '--help for help' exit 5 end - if ARGV[2].size == 0 + if args[2].size == 0 puts 'you cannot give an empty argument' exit 5 end end # this function call the good params set function for the good variable type -def param_set +def param_set(args) db = Params.instance - args_check - case ARGV[1] + args_check(args) + case args[1] when 'dd' - param_check_bool(db, ARGV[1], ['delete diff', 'delete_diff'], ARGV[2]) + param_check_bool(db, args[1], ['delete diff', 'delete_diff'], args[2]) when 'ce' - param_check_bool(db, ARGV[1], ['catch exception', 'catch_execption'], ARGV[2]) + param_check_bool(db, args[1], ['catch exception', 'catch_execption'], args[2]) when 'mp' - param_check_string(db, ARGV[1], ['manga path', 'manga_path'], ARGV[2]) + param_check_string(db, args[1], ['manga path', 'manga_path'], args[2]) when 'bs' - param_check_nb(db, ARGV[1], ['between sleep', 'between_sleep'], ARGV[2].to_f, 0.1) + param_check_nb(db, args[1], ['between sleep', 'between_sleep'], args[2].to_f, 0.1) when 'fs' - param_check_nb(db, ARGV[1], ['failure sleep', 'failure_sleep'], ARGV[2].to_f, 0.1) + param_check_nb(db, args[1], ['failure sleep', 'failure_sleep'], args[2].to_f, 0.1) when 'es' - param_check_nb(db, ARGV[1], ['error sleep', 'error_sleep'], ARGV[2].to_f, 0.5) + param_check_nb(db, args[1], ['error sleep', 'error_sleep'], args[2].to_f, 0.5) when 'nb' - param_check_nb(db, ARGV[1], ['number of tries', 'nb_tries_on_fail'], ARGV[2].to_i, 1) + param_check_nb(db, args[1], ['number of tries', 'nb_tries_on_fail'], args[2].to_i, 1) when 'gh' - param_check_bool(db, ARGV[1], ['generate html', 'generate_html'], ARGV[2]) + param_check_bool(db, args[1], ['generate html', 'generate_html'], args[2]) when 'hn' - param_check_bool(db, ARGV[1], ['html nsfw', 'html_nsfw'], ARGV[2]) + param_check_bool(db, args[1], ['html nsfw', 'html_nsfw'], args[2]) when 'nd' - param_check_string(db, ARGV[1], ['nsfw data', 'html_nsfw_data'], ARGV[2]) + param_check_string(db, args[1], ['nsfw data', 'html_nsfw_data'], args[2]) when 'ct' - param_check_bool(db, ARGV[1], ['color text', 'color_text'], ARGV[2]) + param_check_bool(db, args[1], ['color text', 'color_text'], args[2]) else - puts 'error, unknown parameter id : ' + ARGV[1] + puts 'error, unknown parameter id : ' + args[1] puts '--help for help' exit 5 end @@ -146,17 +146,17 @@ def param_reset puts '' puts 'WARNING ! You are about to reset your parameters !' puts 'the parameters will be set to :' - puts 'manga path = ' + Dir.home + '/Documents/mangas/' - puts 'between sleep = 0.1' - puts 'failure sleep = 0.1' - puts 'number of tries = 20' - puts 'error sleep = 30' - puts 'delete diff = true' - puts 'catch exception = true' - puts 'generate html = true' - puts 'html nsfw = true' - puts 'html nsfw data = Ecchi, Mature, Smut, Adult' - puts 'color text = true' + puts 'manga path = ' + (Dir.home + '/Documents/mangas/').yellow + puts 'between sleep = ' + '0.1'.yellow + puts 'failure sleep = ' + '0.1'.yellow + puts 'number of tries = ' + '20'.yellow + puts 'error sleep = ' + '30'.yellow + puts 'delete diff = ' + 'true'.yellow + puts 'catch exception = ' + 'true'.yellow + puts 'generate html = ' + 'true'.yellow + puts 'html nsfw = ' + 'true'.yellow + puts 'html nsfw data = ' + 'Ecchi, Mature, Smut, Adult'.yellow + puts 'color text = ' + 'true'.yellow puts '' puts "Write 'YES' to continue" ret = STDIN.gets.chomp @@ -168,3 +168,17 @@ def param_reset puts 'did not reset parameters' end end + +def params_management(args) + case args[0] + when 'list' + param_list + when 'reset' + param_reset + when 'set' + param_set(args) + else + puts 'Error : '.red + 'unrecognised argument ' + args[0].yellow + ' for params' + puts './MangaScrap help'.yellow + ' for help' + end +end \ No newline at end of file diff --git a/sources/redl.rb b/sources/instructions/redl.rb similarity index 52% rename from sources/redl.rb rename to sources/instructions/redl.rb index daf7fe1..0a1a129 100644 --- a/sources/redl.rb +++ b/sources/instructions/redl.rb @@ -1,8 +1,12 @@ -def redl_volume(db, manga_name, dw, volume) +def redl_volume(element, volume) failure = true + dw = element.get_download_class + if dw == nil + return false + end links = dw.get_links links.sort.each do |link| - data = data_extractor_MF(link) + data = element.extract_values_from_link(link) if data[0] == volume if failure failure = false @@ -13,18 +17,22 @@ def redl_volume(db, manga_name, dw, volume) end end if failure - puts 'did not find any links in the chapter index with the requested volume' + puts 'did not find any links in the chapter index with the requested volume for ' + element.name + ' of ' + element.site else puts 'done' - HTML.new(db).generate_chapter_index(manga_name) end + !failure end -def redl_chapter(db, manga_name, dw, chapter, volume) +def redl_chapter(element, chapter, volume) failure = true + dw = element.get_download_class + if dw == nil + return false + end links = dw.get_links links.each do |link| - data = data_extractor_MF(link) + data = element.extract_values_from_link(link) if data[0] == volume && data[1] == chapter failure = false puts 'downloading chapter ' + chapter.to_s @@ -33,18 +41,22 @@ def redl_chapter(db, manga_name, dw, chapter, volume) end end if failure - puts 'did not find any links in the chapter index with the requested chapter' + puts 'did not find any links in the chapter index with the requested chapter for ' + element.name + ' of ' + element.site else puts 'done' - HTML.new(db).generate_chapter_index(manga_name) end + !failure end -def redl_page(dw, page, chapter, volume) +def redl_page(element, page, chapter, volume) failure = true + dw = element.get_download_class + if dw == nil + return false + end links = dw.get_links links.each do |link| - data = data_extractor_MF(link) + data = element.extract_values_from_link(link) if data[0] == volume && data[1] == chapter new_link = dw.link_generator(volume, chapter, page) if redirection_detection(new_link) @@ -57,10 +69,24 @@ def redl_page(dw, page, chapter, volume) end end if failure - puts 'did not find any links in the chapter index with the requested page' + puts 'did not find any links in the chapter index with the requested page for ' + element.name + ' of ' + element.site else puts 'done' end + !failure +end + +def redl_manager(element, volume, chapter, page) + if chapter != nil && page != nil # chapter + page ( volume optional ) + redl_page(element, page, chapter, volume) + elsif chapter != nil && page == nil # chapter only ( volume optional ) + redl_chapter(element, chapter, volume) + elsif volume != nil && chapter == nil && page == nil # volume only + redl_volume(element, volume) + else + critical_error("Unmanaged redl values : volume = #{volume} / chapter = #{chapter} / page = #{page}") + end + false end def check_redl_options(volume, chapter, page) @@ -70,87 +96,52 @@ def check_redl_options(volume, chapter, page) puts '-2 => TBD' puts '-3 => NA' puts '-4 => ANT' - exit 5 + false end if chapter != nil && chapter < 0 puts 'chapter value cannot be negative' - exit 5 + false end if page != nil && page < 0 puts 'page value cannot be negative' - exit 5 - end -end - -def redl_manager(db, manga_name, volume, chapter, page) - dw = get_mf_class(db, manga_name, false) - if dw == nil - exit 3 + false end if volume != nil && chapter == nil && page != nil # volume + page but no chapter puts 'error : you cannot request the page of a volume, the chapter value is needed' - exit 5 - elsif chapter != nil && page != nil # chapter + page ( volume optional ) - redl_page(dw, page, chapter, volume) - elsif chapter != nil && page == nil # chapter only ( volume optional ) - redl_chapter(db, manga_name, dw, chapter, volume) - elsif volume != nil && chapter == nil && page == nil # volume only - redl_volume(db, manga_name, dw, volume) - else - puts 'unmanaged error, please report it' - puts "volume = #{volume} / chapter = #{chapter} / page = #{page}" - exit 2 + false end + true end -def redl_arg_extract(db, manga_name) - volume = nil - chapter = nil +def re_download(args, instruction_class) page = nil - ARGV.each do |elem| - arg = elem.dup - case arg[0] - when 'v' - arg[0] = '' - volume = mangafox_volume_string_to_int(arg) - when 'c' - arg[0] = '' - chapter = arg.to_f - if chapter % 1 == 0 - chapter = chapter.to_i - end - when 'p' - arg[0] = '' - page = arg.to_i + chapter = nil + volume = nil + parser = get_data_parser('redl') + parser.on('p', 1) do |_page| + page = _page[0].to_i + end + parser.on('c', 1) do |_chapter| + chapter = _chapter[0].to_i + end + parser.on('v', 1) do |_volume| + volume = mangafox_volume_string_to_int(_volume[0]) + end + parser.parse args + data_to_prepare = instruction_class.data_to_prepare + unless check_redl_options(volume, chapter, page) + instruction_class.clear_data + puts 'cannot execute redl instruction, ignoring it'.yellow + return + end + filter = Manga_data_filter.new(data_to_prepare) + elements = filter.run(false, true) + instruction_class.clear_data + elements.each do |e| + if redl_manager(e, volume, chapter, page) + HTML.new.generate_chapter_index(e) else - puts "Error : the first character of the values should be 'v', 'c', or 'p', not '" + arg[0] + "'" - puts './MangaScrap -h for help' - exit 5 + return end end - if volume == nil - volume = -1 - end - check_redl_options(volume, chapter, page) - redl_manager(db, manga_name, volume, chapter, page) -end - -def re_dl(db) - if ARGV.size < 3 - puts 'error : not enought arguments' - puts 'MangaScrapp -h for help' - exit 5 - end - manga = db.get_manga(ARGV[1]) - if manga == nil - puts 'manga \'' + ARGV[1] + '\' was not found in database' - exit 5 - end - if manga[3] == 'http://mangafox.me/' - ARGV.delete_at(1) - ARGV.delete_at(0) - redl_arg_extract(db, manga[1]) - else - puts 'site ' + manga[3] + ' is not yet managed' - end end diff --git a/sources/list.rb b/sources/list.rb deleted file mode 100644 index c64a267..0000000 --- a/sources/list.rb +++ /dev/null @@ -1,11 +0,0 @@ -def list(db) - if ARGV.size == 1 - list = db.get_manga_list - list.each do |elem| - puts elem - # todo mettre nombre de chapitres téléchargés et infos supp - end - else - # todo lits mangas in site - end -end diff --git a/sources/scan/scan.rb b/sources/scan/scan.rb index 5985758..82f28cd 100644 --- a/sources/scan/scan.rb +++ b/sources/scan/scan.rb @@ -1,47 +1,46 @@ def get_data(dir, db, name) - puts "scanning" - data = scan_dir(dir, db, name) - return data + puts 'scanning' + scan_dir(dir, db, name) end def scan_add(db, name, dir, data, chap_list) # calls scan correct if delete diff == true end def scan_correct(db, name, dir, chap_list) # does not add the manga - puts "deleting difference trace / chapter list" + puts 'deleting difference trace / chapter list' delete_diff(db, chap_list, dir, name) - puts "deleting excess files" + puts 'deleting excess files' delete_bad_files(db_to_trace(db, name), get_data(dir, db, name), dir) - puts "done" + puts 'done' end # entry point => argv scan def scan(db, mode) # todo => must be able to use files ( -f option ) - puts "preparing" + puts 'preparing' params = Params.instance if ARGV.size != 3 - puts "wrong number of arguments : expected 2" + puts 'wrong number of arguments : expected 2' exit 5 end site = ARGV[1] name = ARGV[2] - if site != "mangafox" - puts "unmanaged site " + site + if site != 'mangafox' + puts 'unmanaged site ' + site exit 4 end - puts "getting chapter list from site" - chap_list = Download_mf.new(db, name, false).get_links() - dir = params.get_params()[1] + site + "/" + name + "/" + puts 'getting chapter list from site' + chap_list = Download_mf.new(db, name, false).get_links + dir = params.get_params[1] + site + '/' + name + '/' Dir.chdir(dir) - if mode == "add" + if mode == 'add' data = get_data(dir, db, name) - puts "adding pages to trace database" + puts 'adding pages to trace database' scan_add(db, name, dir, data, chap_list) - elsif mode == "correct" - puts "correcting scan" + elsif mode == 'correct' + puts 'correcting scan' scan_correct(db, name, dir, chap_list) else - puts "critical error : unkown scan mode : " + mode + puts 'critical error : unkown scan mode : ' + mode end end @@ -49,5 +48,5 @@ def scan(db, mode) # todo => must be able to use files ( -f option ) #One last scan must be performed to manage the todo files #L=> add them to todo database #Note : -#Chapters ith the wrong number of pages should be completely deleted and redownloaded -#( new paramater ? => correct = trim || severe ) +#Chapters with the wrong number of pages should be completely deleted and re-downloaded +#( new parameter ? => correct = trim || severe ) diff --git a/sources/scan/scan_utils.rb b/sources/scan/scan_utils.rb index 539d960..f4e697e 100644 --- a/sources/scan/scan_utils.rb +++ b/sources/scan/scan_utils.rb @@ -5,60 +5,60 @@ def value_extract(elem) end chapter = (elem[0] == 'c') ? true : false elem[0] = '' - if chapter == true + if chapter elem = elem.to_f elem /= 10 else elem = elem.to_i end - return elem + elem end # from a page name returns an array with the values or the file name ( if it is not a manga page ) def extract_page_data(file_name) ret = Array.new - data = file_name[/[^.]+/].split("_").reject{|elem| elem == "v"}.reject{|elem| elem == ""} # cleaning array - if data.first != "manga" || data.size != 4 - puts "error on file " + file_name + " it does not seem to be a manga page generated by Mangascrap" + data = file_name[/[^.]+/].split('_').reject{|elem| elem == 'v'}.reject{|elem| elem == ''} # cleaning array + if data.first != 'manga' || data.size != 4 + puts 'error on file ' + file_name + ' it does not seem to be a manga page generated by Mangascrap' return file_name end data.shift case data.first - when "####" + when '####' tmp1 = -1 - when "TBD" + when 'TBD' tmp1 = -2 - when "NA" + when 'NA' tmp1 = -3 - when "ANT" + when 'ANT' tmp1 = -4 else tmp1 = value_extract(data.first) end if tmp1 == nil || (tmp2 = value_extract(data[1])) == nil || (tmp3 = value_extract(data[2])) == nil - puts "error of " + file_name + " it does not seem to be a file generated by Mangascrap" + puts 'error of ' + file_name + ' it does not seem to be a file generated by Mangascrap' p data return file_name end ret << tmp1 << tmp2 << tmp3 - return ret + ret end # get all file names from a directory and extract the values def scan_dir(dir, db, name) Dir.chdir dir - ls = Dir["*"] + ls = Dir['*'] if ls == nil - puts "could not find any files in the " + File.expand_path(dir) + " directory" + puts 'could not find any files in the ' + File.expand_path(dir) + ' directory' exit 5 end ls.sort_by!{|elem| elem} data = Array.new ls.each do |page| - if (page == "cover.jpg" || page == "description.txt") + if page == 'cover.jpg' || page == 'description.txt' next end data << extract_page_data(page) end - return data + data end diff --git a/sources/update.rb b/sources/update.rb deleted file mode 100644 index 95124f1..0000000 --- a/sources/update.rb +++ /dev/null @@ -1,66 +0,0 @@ -def update_exec(name, db, params) - # todo : site needs to be managed - dw = get_mf_class(db, name, false) - if dw != nil - dw.update - if params[6] == 'true' - delete_diff(db, dw.get_links, name) - end - end -end - -def update_manga(db, name, fast, params) - manga = db.get_manga(name) - if manga == nil - puts 'error : ' + name + ' no such manga in database' - exit 5 - end - # todo : site needs to be managed - if manga[3] == 'http://mangafox.me/' - if fast == true && manga[8] == 'Ongoing' - update_exec(name, db, params) - elsif !fast - update_exec(name, db, params) - end - else - puts 'did not find ' + manga[3] + ' in available site list, leaving' - exit 5 - end -end - -def update_all(db, fast, params) - list = db.get_manga_list - puts 'updating all mangas in database'.yellow - list.each do |elem| - update_manga(db, elem[0], fast, params) - end -end - -def update(db, fast = false) - params = Params.instance.get_params - case ARGV.size - when 0, 1 - update_all(db, fast, params) - when 2 - if db.manga_in_data?(ARGV[1]) - update_manga(db, ARGV[1], fast, params) - else - puts 'could not find ' + ARGV[1] + ' in database' - exit 5 - end - when 3 - ret = get_mangas - if ret != nil - ret.each do |name| - update_manga(db, name[0], fast, params) - end - else - puts 'error while trying to get content of file ( -f option )' - exit 5 - end - else - puts 'bad number of arguments for update, --help for help' - exit 5 - end - HTML.new(db).generate_index -end diff --git a/sources/utils/utils_args.rb b/sources/utils/utils_args.rb new file mode 100644 index 0000000..5adca89 --- /dev/null +++ b/sources/utils/utils_args.rb @@ -0,0 +1,22 @@ +# the files of this file are used to "translate" arguments into usable data +# print error and leave + +def get_dir_from_site(site) + case site + when 'mangafox', 'mangafox.me', 'http://mangafox.me', 'http://mangafox.me/' + return 'mangafox/' + else + critical_error('the function get_dir_from_site was called with a bad argument (' + site.yellow + ')') + end +end + +def critical_error(message, error = nil) + puts 'Critical Error : ' .red + message + if error != nil + puts 'error type is : ' + error.class.to_s + end + puts "\ncaller backtrace :".yellow + puts caller + puts "\nplease report this on github (unless you edited the code)\n\n".yellow + exit 4 +end diff --git a/sources/utils/utils_co.rb b/sources/utils/utils_co.rb index 28c967c..aac8886 100644 --- a/sources/utils/utils_co.rb +++ b/sources/utils/utils_co.rb @@ -89,8 +89,9 @@ def redirection_detection(url) def get_page(link, silent = false) tries ||= $nb_tries begin - page = Nokogiri::HTML(open(link, 'User-Agent' => "Ruby/#{RUBY_VERSION}")) do |noko| - noko.noblanks.noerror + html = open(link, 'User-Agent' => "Ruby/#{RUBY_VERSION}") + page = Nokogiri::HTML(html) do |nokogiri| + nokogiri.noblanks.noerror end rescue StandardError => error tries = download_rescue(tries, link, error, 'could not download picture', silent) @@ -131,42 +132,6 @@ def get_pic(link, silent = false) page end -# mangafox only => gets the link and returns the values in an array -def data_extractor_MF(link) - if link[link.size - 1] == '/' - page = 1 - end - link += '1.html' - link_split = link.split('/') - page = link_split[link_split.size - 1].chomp('.html').to_i - link_split[link_split.size - 2][0] = '' - chapter = link_split[link_split.size - 2].to_f - if chapter % 1 == 0 - chapter = chapter.to_i - end - if link_split.size == 8 - link_split[link_split.size - 3][0] = '' - if link_split[link_split.size - 3] =~ /\A\d+\z/ - volume = link_split[link_split.size - 3].to_i - else - if link_split[link_split.size - 3] == 'NA' - volume = -3 - elsif link_split[link_split.size - 3] == 'TBD' - volume = -2 - elsif link_split[link_split.size - 3] == 'ANT' - volume = -4 - else - volume = -42 # error value - end - end - else - volume = -1 # no volume - end - ret = Array.new - ret << volume << chapter << page - ret -end - # from the index page of a manga, extracts the links def extract_links(doc, manga_name, xpath, page_link) tries = $nb_tries diff --git a/sources/utils/utils_db.rb b/sources/utils/utils_db.rb deleted file mode 100644 index cb79652..0000000 --- a/sources/utils/utils_db.rb +++ /dev/null @@ -1,18 +0,0 @@ -# displays an error message and exits -def db_error_exit(message, error) - puts message - puts "message is : '" + error.message + "'" - exit 2 -end - -# returns the mangafox class on success or displays an error message and returns nil -def get_mf_class(db, manga_name, data) - begin - ret = Download_Mangafox.new(db, manga_name, data) - return ret - rescue => e - puts ("error while trying to get #{manga_name}").red - puts 'reason is : '.yellow + e.message - return nil - end -end diff --git a/sources/utils/utils_file.rb b/sources/utils/utils_file.rb index 9f13d48..33e83d3 100644 --- a/sources/utils/utils_file.rb +++ b/sources/utils/utils_file.rb @@ -6,7 +6,7 @@ def dir_create(directory) unless Dir.exist?(directory) puts directory + ' does not exist, creating it' list = directory.split('/') - build = '/' + build = '/' #todo => adapt dir_create for windows list = list.reject {|elem| elem.empty?} list.each do |elem| build += elem + '/' @@ -129,3 +129,15 @@ def copy_file(file, dest) end false end + +def delete_files(path, extension) + Dir.glob(path + '/*').sort.each do |file| + puts 'deleting file : ' + file + File.delete(file) + end + Dir.delete(path) + if File.exist?(path + extension) + puts 'deleting file : ' + path + extension + File.delete(path + extension) + end +end diff --git a/sources/utils/utils_manga.rb b/sources/utils/utils_manga.rb index 2601c19..8eda4a2 100644 --- a/sources/utils/utils_manga.rb +++ b/sources/utils/utils_manga.rb @@ -110,3 +110,38 @@ def description_manipulation(description, line_size = 120, min_nb_lines = 0) end ret end + +def data_extractor_MF(link) + if link[link.size - 1] == '/' + page = 1 + end + link += '1.html' + link_split = link.split('/') + page = link_split[link_split.size - 1].chomp('.html').to_i + link_split[link_split.size - 2][0] = '' + chapter = link_split[link_split.size - 2].to_f + if chapter % 1 == 0 + chapter = chapter.to_i + end + if link_split.size == 8 + link_split[link_split.size - 3][0] = '' + if link_split[link_split.size - 3] =~ /\A\d+\z/ + volume = link_split[link_split.size - 3].to_i + else + if link_split[link_split.size - 3] == 'NA' + volume = -3 + elsif link_split[link_split.size - 3] == 'TBD' + volume = -2 + elsif link_split[link_split.size - 3] == 'ANT' + volume = -4 + else + volume = -42 # error value + end + end + else + volume = -1 # no volume + end + ret = Array.new + ret << volume << chapter << page + ret +end diff --git a/sources/utils_co.rb b/sources/utils_co.rb deleted file mode 100644 index c10ebf8..0000000 --- a/sources/utils_co.rb +++ /dev/null @@ -1,124 +0,0 @@ -$nb_tries = -1 -$between_sleep = -1 -$failure_sleep = -1 -$error_sleep = -1 -$catch_fatal = "false" - -#inits the global variables -def init_utils() - db = Params.new() - params = db.get_params() - $between_sleep = params[2] - $failure_sleep = params[3] - $nb_tries = params[4] - $error_sleep = params[5] - $catch_fatal = params[7] -end - -def sleep_manager(error) - if error.class.to_s == "SocketError" # connection error - sleep($error_sleep) - else - sleep($failure_sleep) - end -end - -def download_rescue(tries, link, error, message) - if tries > 0 - tries -= 1 - sleep_manager(error) - return tries - else - print "\n" - STDOUT.flush - puts message + ' ' + link + ' after ' + $nb_tries.to_s + ' tries' - puts "message is : " + error.message - return nil - end -end - -def rescue_fatal(error) - if $catch_fatal == "false" - print "\n" - STDOUT.flush - puts "Warning : exception occured, message is : " + error.message - puts "Exception class is : " + error.class.to_s - puts "raising it again" - puts "" - raise error - else - if error.class.to_s != "fatal" - raise error - end - end -end - -# detects if there whas a redirection on the required link -def redirection_detection(url) - tries ||= $nb_tries - begin - open(url) do |resp| - if (resp.base_uri.to_s != url) - return true - end - end - rescue StandardError => error - if tries > 0 - tries -= 1 - sleep_manager(error) - retry - else - puts "connection is lost or could not find manga, stopping programm" - puts url - puts "message is : " + error.message - exit 3 - end - rescue Exception => error - rescue_fatal(error) - end - return false -end - -# conect to link and download page -def get_page(link) - tries ||= $nb_tries - begin - page = Nokogiri::HTML(open(link, "User-Agent" => "Ruby/#{RUBY_VERSION}")) do |noko| - noko.noblanks.noerror - end - rescue StandardError => error - tries = download_rescue(tries, link, error, 'could not download picture') - if (tries == nil) - return nil - end - retry - rescue Exception => error - rescue_fatal(error) - end - sleep($between_sleep) - return page -end - -# conect to link and download picture -def get_pic(link) - safe_link = link.gsub(/[\[\]]/) { '%%%s' % $&.ord.to_s(16) } - tries ||= $nb_tries - begin - page = open(safe_link, "User-Agent" => "Ruby/#{RUBY_VERSION}") - rescue URI::InvalidURIError => error - puts "Warning : bad url" - puts link - puts "message is : " + error.message - return nil - rescue StandardError => error - tries = download_rescue(tries, link, error, 'could not download picture') - if (tries == nil) - return nil - end - retry - rescue Exception => error - rescue_fatal(error) - end - sleep($between_sleep) - return page -end diff --git a/sources/utils_db.rb b/sources/utils_db.rb deleted file mode 100644 index 90a8022..0000000 --- a/sources/utils_db.rb +++ /dev/null @@ -1,16 +0,0 @@ -def db_error_exit(message, error) - puts message - puts "message is : '" + error.message + "'" - exit 2 -end - -def get_mf_class(db, manga_name, data) - begin - ret = Download_mf.new(db, manga_name, data) - return ret - rescue => e - puts "error while trying to add #{manga_name}" - puts "reason is : " + e.message - return nil - end -end diff --git a/sources/utils_manga.rb b/sources/utils_manga.rb deleted file mode 100644 index 34212f4..0000000 --- a/sources/utils_manga.rb +++ /dev/null @@ -1,168 +0,0 @@ -# used for the description.txt file of every manga -def data_conc(manga_name, description, site, link, author, artist, type, status, genres, release) - ret = "name = " + manga_name + "\n" - ret += "author = " + author + "\n" - ret += "artist = " + artist + "\n" - ret += "release year = " + release.to_s + "\n" - ret += "type = " + type + "\n" - ret += "status = " + status + "\n" - ret += "genres = " + genres + "\n" - ret += "\n" - ret += "site = " + site + "\n" - ret += "link = " + link + "\n" - ret += "\n" - ret += "description :\n" - ret += "\n" - ret += description - ret += "\n" - return ret -end - -# determines if directory exists -def dir_create(directory) - if directory[0, 1] == "~" - directory["~"] = Dir.home - end - if Dir.exist?(directory) == false - puts directory + " does not exist, creating it" - list = directory.split('/') - build = "/" - list = list.reject {|elem| elem.empty?} - list.each do |elem| - build += elem + '/' - if Dir.exist?(build) == false - Dir.mkdir(build) - end - end - end -end - -# open -f option file and return array -def get_mangas() - ret = Array.new - if (ARGV[1] == "-f") - line_num = 0 - begin - text = File.open(ARGV[2]).read - rescue => e - puts e.message - exit 5 - end - text.gsub!(/\r\n?/, "\n") - text.each_line do |line| - if (line == nil || line.size <= 1 || line[0] == '#' || line[0] == '\n') - next - end - elems = line.split(" ") - if (elems.size > 2) - puts "there is more than one space on line #{line_num} this should not be possible, ./MangaScrap -h for help" - exit 5 - end - if (elems.size == 1) - elems << "http://mangafox.me/" - end - ret << elems - line_num += 1 - end - else - return nil - end - ARGV.delete_at(2) - ARGV.delete_at(1) - return ret -end - -# get file name -def file_name(dir, vol_value, chap_value, page_value) - chap_str = chap_value.to_s - val = chap_str.index('.') - if (val != nil) - chap_str[val] = '' - else - chap_str += '0' - end - if vol_value != -42 - if vol_value == -1 - vol_buffer = "####" - elsif vol_value == -2 - vol_buffer = "_TBD" - elsif vol_value == -3 - vol_buffer = "__NA" - elsif vol_value == -4 - vol_buffer = "_ANT" - else - vol_buffer = ((vol_value >= 1000) ? "" : ((vol_value >= 100) ? "0" : ((vol_value >= 10) ? "00" : "000"))) - vol_buffer += vol_value.to_s - end - end - chap_buffer = ((chap_value >= 1000) ? "" : ((chap_value >= 100) ? "0" : ((chap_value >= 10) ? "00" : "000"))) - page_buffer = ((page_value >= 1000) ? "" : ((page_value >= 100) ? "0" : ((page_value >= 10) ? "00" : "000"))) - name_buffer = dir + "manga_v" + vol_buffer + "_c" + chap_buffer + chap_str + "_p" + page_buffer + page_value.to_s - return name_buffer -end - -# used to display the progression of the downloads -def chapter_progression(i) - if i > 1 - if i % 50 == 0 - printf ';' - elsif i % 10 == 0 - printf ',' - else - printf '.' - end - else - printf '.' - end - STDOUT.flush -end - -# used to write the downloaded picture -def write_pic(pic_buffer, data, dir) - dir_create(dir) - name_buffer = file_name(@dir, data[0], data[1], data[2]) - if pic_buffer != nil - File.open(name_buffer + ".jpg", 'wb') do |pic| - pic << pic_buffer.read - end - if (File.exist?(name_buffer + ".txt") == true) - File.delete(name_buffer + ".txt") - end - else - File.open(name_buffer + ".txt", 'w') do |pic| - pic << "could not be downloaded" - end - puts "Error : no picture to save" - return false - end - return true -end - -# transforms the raw text into a more lisible format -def description_manipulation(description) - ret = "" - description.delete!("\C-M") - description.tr("\t", '') - description = description.squeeze(" ") - description.each_line do |line| - lock = true - tmp_line = "" - count = 0 - line.split(" ").each do |word| - count += word.length - if count > 120 - tmp_line += "\n" - count = 0 - end - if lock == true - lock = false - elsif count != 0 - tmp_line += " " - count += 1 - end - tmp_line += word - end - ret += tmp_line.strip() + "\n" - end - return ret -end diff --git a/utils/exemple.txt b/utils/exemple.txt index 74b5111..5ac89d1 100644 --- a/utils/exemple.txt +++ b/utils/exemple.txt @@ -1,30 +1,30 @@ black_clover http://mangafox.me/ -crepuscule_yamchi -doulou_dalu +crepuscule_yamchi http://mangafox.me/ +doulou_dalu http://mangafox.me/ #comments MUST start with a '#' as the very first character of the line #this is a comment -doupo_cangqiong -gate_jietai_kare_no_chi_nite_kaku_tatakeri -nejimaki_kagyu -onepunch_man -overlord -panlong -qwan +doupo_cangqiong http://mangafox.me/ +gate_jietai_kare_no_chi_nite_kaku_tatakeri http://mangafox.me/ +nejimaki_kagyu http://mangafox.me/ +onepunch_man http://mangafox.me/ +overlord http://mangafox.me/ +panlong http://mangafox.me/ +qwan http://mangafox.me/ # empty lines are ignored re_monster http://mangafox.me/ -school_shock -seirei_tsukai_no_kenbu_hyouju_issei -sky_blue -tegami_bachi -tensei_shitara_slime_datta_ken -the_gamer -unbreakable_machine_doll -witchcraft_works -world_trigger -yuragi_sou_no_yuuna_san -the_new_gate +school_shock http://mangafox.me/ +seirei_tsukai_no_kenbu_hyouju_issei http://mangafox.me/ +sky_blue http://mangafox.me/ +tegami_bachi http://mangafox.me/ +tensei_shitara_slime_datta_ken http://mangafox.me/ +the_gamer http://mangafox.me/ +unbreakable_machine_doll http://mangafox.me/ +witchcraft_works http://mangafox.me/ +world_trigger http://mangafox.me/ +yuragi_sou_no_yuuna_san http://mangafox.me/ +the_new_gate http://mangafox.me/ diff --git a/utils/help.txt b/utils/help.txt index 7fb5bda..8c900ec 100644 --- a/utils/help.txt +++ b/utils/help.txt @@ -1,97 +1,134 @@ - arguments for MangaScrap : + DESCRIPTION : + MangaScrap is a web crawler witch downloads mangas and places them on your computer. + It's main features are : + - it's database : MangaScrap will remember what it downloaded and what needs to be downloaded + The database is located in ~/.MangaScrap + - it's local website : MangaScrap will generate a local website using everything it downloaded allowing + you to browse your mangas offline - -a --add [-f 'name of file'] / 'manga name' [manga site (default is manga fox)] - add manga to database - also updates the manga index ( html ) + INSTRUCTIONS : + definitions : + [data arguments compatible] : can use the second group of argument called data arguments + [own arguments] : requires it's own set of arguments. Should there also be [data arguments], they + will always be placed before + [in database] : all elements must be in the database + [not in database] : all elements must not be in the database + [elements required] : the instruction cannot be run without at least an element - -da --data [-f 'name of file'] / 'manga name' - download data ( cover + details ) of targeted manga(s) - also updates the manga index ( html ) + [add]g [data arguments compatible] [elements required] [not in database] + adds the listed elements to the database - -u --update [-f 'name of file'] / [manga name] - update database / manga - will update the index ( html ) of each manga when it finished updating them + [update]g [data arguments compatible] [elements required] [in database] + updates the listed elements + MangaScrap will download the missing pages, delete the excess pages and re-generate the HTML - -uf --update-fast [-f 'name of file'] / [manga name] - only update mangas witch are tagged as "Ongoing" in database - will update the index ( html ) of each manga when it finished updating them + [download]g [data arguments compatible] [elements required] [not in database] + first adds all the elements to the database and then downloads them + equivalent of [add]g and then [update]g - -dl --download [-f 'name of file'] 'manga name' [manga site (default is manga fox)] - add manga and then download + [html]g [data arguments compatible] [in database] + MangaScrap will generate the HTML + should no arguments be passed, only the manga indexes will be updated + should [data arguments] be passed, all selected elements will have there HTML generated. The index is also generated - -redl --redownload 'manga name' [volume] [chapter] [page] - deletes the volume / chapter(s) / page(s) from the trace databe and then updates the manga + [delete]g [data arguments compatible] [elements required] [in database] + deletes the elements from the database and the files ( html + jpeg ) - -l --list [manga name / manga site] - shows content of database + [delete-db]g [data arguments compatible] [elements required] [in database] + deletes the elements from the database only - -d --delete [-f 'name of file'] / [manga name] - deletes manga from database ( not the files !!! ) - also updates the manga index ( html ) + [_todo]g [data arguments compatible] [elements required] [in database] + Downloads all missing pages for all the elements - -df --delete-files [-f 'name of file'] / [manga name] - deletes manga from database and also the files - also updates the manga index ( html ) + [clear]g [data arguments compatible] [elements required] [in database] + Deletes all missing pages from the _todo database - -pl --param_list - lists actual settings + [output]g [data arguments compatible] [elements required] + writes all of the element's name and site on the standard output + mainly used for the [file]y [data argument] ( can be used to write a file containing all the selected elements ) - -ps --param_set 'parameter id' 'value' - sets the speciefied parameter - WARNING : when setting the destination folder of mangas, the already downloaded - pages are NOT moved - 'parameter id' values : - mp => the path to witch the "manga" directory is created ( string ) - bs => the sleep between 2 page downloads ( seconds ) - fs => the sleep between 2 page download fails ( seconds ) - nb => the number of download fails allowed before putting the page in the todo database ( int ) - es => time between 2 errors - such as a conection loss - ( seconds ) - dd => delete the chapters that are not in the link list - after they where moved to an other - volume for example - ( true / false ) + [infos]g [data arguments compatible] [elements required] [in database] + writes all of the available information of the elements on the standard output + this includes anything that is available in the database - -pr --param_reset - resets the parameters to default, will ask for confirmation + [param]g [own arguments] + Allows you to configure the way MangaScrap works. + [param]g can take 3 arguments : - -c --clear [-f 'name of file'] / [manga name] - clears the todo database from the manga ( to be used when trying to update an unexisting chapter ) + [list]y + Shows the status of all the current settings and there description - -ht --html [-f 'name of file'] / [manga name] - generates the manga index + the chapter index and chapters of the required mangas + [reset]y + Resets all the values of all the parameters to there default configuration + Will request a confirmation - -hti --html-index - regenerates the manga index only + [set]y paramId newValue + Allows you to set a new value to one of the parameters. + Requires 2 arguments : the paramId and the new value + The param ids can be found using [param]g [list]y + note : changing the destination directory does NOT move the already downloaded elements - -h --help - displays help + [help]g + [help]y will display these instructions + [version]g + displays the current version of MangaScrap witch can be found in utils/version.txt - -f + 'file name' allows you to do multiple mangas at once - it is used with add, update, download and delete - if used, no other arguments are required - in the file, each manga is separated by a line break - syntax is : manga_name [site] - the site will be ignored for update and delete + [data arguments] + definition : the data arguments are the arguments that are used to get elements by [link]y, [id]y, [file]y, ... - example : - black_clover - crepuscule_yamchi http://mangafox.me/ + [all]y + Selects all elements in the database - Usage exemples : - ./MangaScrap -a my_super_manga - adds 'my_super_manga' to the database for future update - ./MangaScrap -a -f my_manga_list.txt + [id]y name site + Gets one element with the combination of the name and of the site. + The name MUST be the one of the url + + [link]y link + Gets one element with the complete link of the element + + [file]y fileName + Gets all the element present in the file + All the elements must be of the name + site format + Empty lines are not a problem + It is possible to place comments but the commented line MUST start with a # + + [query]y query + Allows you to get elements from the database directly + Currently just a placeholder + + EXAMPLES : + + ./MangaScrap.rb [add]g [id]y super_manga super_site + adds 'super_manga' of 'super_site' to the database for future update + + ./MangaScrap.rb [add]g [file]y my_manga_list.txt adds all mangas of 'my_manga_list.txt' to the database for future update - ./MangaScrap + + ./MangaScrap.rb [update]g [all]y updates all mangas in database - ./MangaScrap -f file.txt + + ./MangaScrap.rb [update]g [file]y file.txt updates all mangas in file.txt - ./MangaScrap -ps mp /home/toto/ - sets the path of the manga directory to the last argument - WARNING : it does NOT move the already downloaded pages + + ./MangaScrap.rb [param]g [set]y mp /home/toto/ + sets the path of the destination directory to /home/toto + + note : the instructions can also be chained + + ./MangaScrap.rb [version]g [add]g [file]y file.txt [update]g [all]y + this will make MangaScrap display it's current version, add the content of file.txt to the database + and then update everything + + note : MangaScrap is smart enough to allow names such as 'id' or 'update' + + ./MangaScrap.rb [add]g [id]y id mangafox [update]g [file]y update + this will add the manga 'id' of mangafox and then will update all elements contained in the 'update' file + Warning ! - The [manga name] is found in the url + The element's name is found in the url ex : "bleach" => http://mangafox.me/manga/bleach/ => "bleach" ex : "black clover" => http://mangafox.me/manga/black_clover/ => "black_clover" ex : "crepuscule" => http://mangafox.me/manga/crepuscule_yamchi/ => "crepuscule_yamchi" @@ -99,8 +136,6 @@ information concerning the display of the download : . => downloaded a page , => downloaded a page ( multiple of 10 ) - ; => downloaded a page ( pultiple of 50 ) + ; => downloaded a page ( multiple of 50 ) ? => could not download a page ( 404 not found ) X => could not download a page ( other error ) - - diff --git a/utils/version.txt b/utils/version.txt new file mode 100644 index 0000000..d9df1bb --- /dev/null +++ b/utils/version.txt @@ -0,0 +1 @@ +0.11.0 diff --git a/version.txt b/version.txt deleted file mode 100644 index 5eef0f1..0000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.10.2