diff --git a/bin/epubr b/bin/epubr
new file mode 100644
index 0000000..01b3626
--- /dev/null
+++ b/bin/epubr
@@ -0,0 +1,166 @@
+#!/usr/bin/env ruby
+
+require 'bundler/inline'
+
+#gemfile do
+# source 'https://rubygems.org'
+# gem 'zip'
+# gem 'nokogiri'
+# gem 'tty-prompt'
+# gem 'debug'
+#end
+
+
+require 'zip'
+require 'nokogiri'
+require 'tty-prompt'
+require 'debug'
+
+
+class Epub
+ def initialize(file)
+ @file = file
+ end
+
+ def read_epub
+ Zip::File.open(@file) do |zip_file|
+ opf_name = find_opf_file(zip_file)
+ opf_entry = zip_file.find_entry(opf_name)
+ opf_content = opf_entry.get_input_stream.read
+ opf_doc = Nokogiri::XML(opf_content)
+
+ yield zip_file, opf_doc, opf_name if block_given?
+ end
+ end
+
+ def write_epub
+ read_epub do |zip_file, opf_doc, opf_name|
+ yield zip_file, opf_doc, opf_name if block_given?
+ zip_file.get_output_stream(opf_name) do |output_stream|
+ output_stream.write(opf_doc.to_xml)
+ end
+ end
+ end
+
+ def opf
+ read_epub do |zip_file, opf_doc, opf_name|
+ opf_doc
+ end
+ end
+
+ def series
+ read_epub do |zip_file, opf_doc, opf_name|
+ series_element = opf_doc.at_css('meta[name="calibre:series"]')
+ series_element["content"] if series_element
+ end
+ end
+
+ def series=(new_series_name)
+ write_epub do |zip_file, opf_doc, opf_name|
+ series_element = opf_doc.at_css('meta[name="calibre:series"]')
+
+ if series_element && new_series_name.nil?
+ # Delete the element for the series
+ series_element.remove
+ elsif series_element
+ # Update the content of the element
+ series_element['content'] = new_series_name
+ else
+ # Create a new element for the series if it doesn't exist
+ opf_namespace_url = "http://www.idpf.org/2007/opf"
+ metadata_element = opf_doc.at_css("metadata, opf|metadata", 'opf' => opf_namespace_url)
+ series_element = Nokogiri::XML::Node.new('meta', opf_doc)
+ series_element['name'] = 'calibre:series'
+ series_element['content'] = new_series_name
+ metadata_element.add_child(series_element)
+ end
+ end
+ end
+
+ def series_index
+ read_epub do |zip_file, opf_doc, opf_name|
+ series_index = opf_doc.at_css('meta[name="calibre:series_index"]')
+ series_index['content'] if series_index
+ end
+ end
+
+ def series_index=(new_si)
+ write_epub do |zip_file, opf_doc, opf_name|
+ series_index = opf_doc.at_css('meta[name="calibre:series_index"]')
+
+ old_si = series_index['content'] if series_index
+
+ if series_index && new_si.nil?
+ series_index.remove
+ elsif series_index
+ series_index['content'] = new_si
+ else
+ opf_namespace_url = "http://www.idpf.org/2007/opf"
+ metadata_element = opf_doc.at_css("metadata, opf|metadata", 'opf' => opf_namespace_url)
+
+ si_element = Nokogiri::XML::Node.new('meta', opf_doc)
+ si_element['name'] = 'calibre:series_index'
+ si_element['content'] = new_si
+ metadata_element.add_child(si_element)
+ end
+ end
+ end
+
+ def find_opf_file(zip_file)
+ # Locate and read the container.xml file
+ container_xml_entry = zip_file.find_entry('META-INF/container.xml')
+ container_xml = container_xml_entry.get_input_stream.read
+
+ # Parse the container.xml file
+ doc = Nokogiri::XML(container_xml)
+
+ # Find the rootfile element and extract the 'full-path' attribute
+ doc.at_xpath("xmlns:container/xmlns:rootfiles/xmlns:rootfile")['full-path']
+ end
+end
+
+def run(argv)
+ files = []
+ action = nil
+ attribute = nil
+ new_series_name = nil
+
+ argv.each_with_index do |arg, index|
+ if arg == 'get' || arg == 'set'
+ action = arg.to_sym
+ attribute = argv[index + 1]
+ new_series_name = argv[index + 2]
+ break
+ else
+ files << arg if File.exist?(arg)
+ end
+ end
+
+ raise ArgumentError.new("Action must be :get or :set") unless [:get, :set].include?(action)
+ raise ArgumentError.new("Attribute must be 'series' or 'series_index'") unless ['series', 'series_index'].include?(attribute)
+
+ files.sort!
+
+ if attribute == 'series'
+ result = files.map do |file|
+ e = Epub.new(file)
+ {file: }.merge e.series_name(file, action:, new_series_name:)
+ end
+
+ result.each { |r| puts "#{r[:file]} : #{r[:series_name]}" } if action == :get
+ result.each { |r| puts "#{r[:file]} : #{r[:old_series_name]} => #{r[:new_series_name]}" } if action == :set
+
+ elsif attribute == 'series_index'
+ result = files.map do |file|
+ e = Epub.new(file)
+ {file: }.merge(e.series_index(file, action: ))
+ end
+
+ result.each { |r| puts "#{r[:file]} : #{r[:old_series_index]}" } if action == :get
+ result.each { |r| puts "#{r[:file]} : #{r[:old_series_index]} -> #{r[:new_series_index]}" } if action == :set
+ end
+end
+
+if __FILE__ == $PROGRAM_NAME
+ run(ARGV)
+end