diff --git a/.gitignore b/.gitignore index 9f22510..84696d6 100644 --- a/.gitignore +++ b/.gitignore @@ -132,4 +132,5 @@ dmypy.json .kindle_session # remove MacOS .DS_Store -.DS_Store \ No newline at end of file +.DS_Store +EPUB/ \ No newline at end of file diff --git a/gui/icon_rc.py b/gui/icon_rc.py index 7783cd3..00a0a19 100644 --- a/gui/icon_rc.py +++ b/gui/icon_rc.py @@ -1813,10 +1813,17 @@ qt_resource_struct = b"\ \x00\x00\x01\x81Cj\xfeG\ " + def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + qInitResources() diff --git a/gui/ui_kindle.py b/gui/ui_kindle.py index 2a13a02..d7d8614 100644 --- a/gui/ui_kindle.py +++ b/gui/ui_kindle.py @@ -8,265 +8,293 @@ ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, - QFont, QFontDatabase, QGradient, QIcon, - QImage, QKeySequence, QLinearGradient, QPainter, - QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QDialog, QGroupBox, - QHBoxLayout, QHeaderView, QLabel, QLineEdit, - QPlainTextEdit, QPushButton, QRadioButton, QSizePolicy, - QSpinBox, QTableView, QTextBrowser, QVBoxLayout, - QWidget) +from PySide6.QtCore import ( + QCoreApplication, + QDate, + QDateTime, + QLocale, + QMetaObject, + QObject, + QPoint, + QRect, + QSize, + QTime, + QUrl, + Qt, +) +from PySide6.QtGui import ( + QBrush, + QColor, + QConicalGradient, + QCursor, + QFont, + QFontDatabase, + QGradient, + QIcon, + QImage, + QKeySequence, + QLinearGradient, + QPainter, + QPalette, + QPixmap, + QRadialGradient, + QTransform, +) +from PySide6.QtWidgets import ( + QAbstractItemView, + QApplication, + QCheckBox, + QDialog, + QGroupBox, + QHBoxLayout, + QHeaderView, + QLabel, + QLineEdit, + QPlainTextEdit, + QPushButton, + QRadioButton, + QSizePolicy, + QSpinBox, + QTableView, + QTextBrowser, + QVBoxLayout, + QWidget, +) import gui.icon_rc as icon_rc + class Ui_MainDialog(object): def setupUi(self, MainDialog): if not MainDialog.objectName(): - MainDialog.setObjectName(u"MainDialog") + MainDialog.setObjectName("MainDialog") MainDialog.resize(1190, 872) icon = QIcon() - icon.addFile(u":/icon/resource/kindle.png", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile(":/icon/resource/kindle.png", QSize(), QIcon.Normal, QIcon.Off) MainDialog.setWindowIcon(icon) self.horizontalLayout = QHBoxLayout(MainDialog) self.horizontalLayout.setSpacing(10) - self.horizontalLayout.setObjectName(u"horizontalLayout") + self.horizontalLayout.setObjectName("horizontalLayout") self.leftLayout = QVBoxLayout() - self.leftLayout.setObjectName(u"leftLayout") + self.leftLayout.setObjectName("leftLayout") self.listBox = QGroupBox(MainDialog) - self.listBox.setObjectName(u"listBox") + self.listBox.setObjectName("listBox") self.verticalLayout_5 = QVBoxLayout(self.listBox) - self.verticalLayout_5.setObjectName(u"verticalLayout_5") + self.verticalLayout_5.setObjectName("verticalLayout_5") self.widget = QWidget(self.listBox) - self.widget.setObjectName(u"widget") + self.widget.setObjectName("widget") self.widget.setMinimumSize(QSize(0, 0)) self.widget.setBaseSize(QSize(0, 0)) self.horizontalLayout_6 = QHBoxLayout(self.widget) - self.horizontalLayout_6.setObjectName(u"horizontalLayout_6") + self.horizontalLayout_6.setObjectName("horizontalLayout_6") self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0) self.radioEBOK = QRadioButton(self.widget) - self.radioEBOK.setObjectName(u"radioEBOK") + self.radioEBOK.setObjectName("radioEBOK") self.radioEBOK.setChecked(True) self.horizontalLayout_6.addWidget(self.radioEBOK) self.radioPDO = QRadioButton(self.widget) - self.radioPDO.setObjectName(u"radioPDO") + self.radioPDO.setObjectName("radioPDO") self.horizontalLayout_6.addWidget(self.radioPDO) self.fetchButton = QPushButton(self.widget) - self.fetchButton.setObjectName(u"fetchButton") + self.fetchButton.setObjectName("fetchButton") self.horizontalLayout_6.addWidget(self.fetchButton) - self.verticalLayout_5.addWidget(self.widget, 0, Qt.AlignRight) self.bookView = QTableView(self.listBox) - self.bookView.setObjectName(u"bookView") + self.bookView.setObjectName("bookView") self.bookView.setSelectionBehavior(QAbstractItemView.SelectRows) self.verticalLayout_5.addWidget(self.bookView) self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.label_2 = QLabel(self.listBox) - self.label_2.setObjectName(u"label_2") + self.label_2.setObjectName("label_2") self.horizontalLayout_4.addWidget(self.label_2) self.outDirEdit = QLineEdit(self.listBox) - self.outDirEdit.setObjectName(u"outDirEdit") + self.outDirEdit.setObjectName("outDirEdit") self.horizontalLayout_4.addWidget(self.outDirEdit) self.browseButton = QPushButton(self.listBox) - self.browseButton.setObjectName(u"browseButton") + self.browseButton.setObjectName("browseButton") self.horizontalLayout_4.addWidget(self.browseButton) self.downloadButton = QPushButton(self.listBox) - self.downloadButton.setObjectName(u"downloadButton") + self.downloadButton.setObjectName("downloadButton") self.horizontalLayout_4.addWidget(self.downloadButton) self.selectedButton = QPushButton(self.listBox) - self.selectedButton.setObjectName(u"selectedButton") + self.selectedButton.setObjectName("selectedButton") self.horizontalLayout_4.addWidget(self.selectedButton) self.dedrmCkb = QCheckBox(self.listBox) - self.dedrmCkb.setObjectName(u"dedrmCkb") + self.dedrmCkb.setObjectName("dedrmCkb") self.horizontalLayout_4.addWidget(self.dedrmCkb) - self.verticalLayout_5.addLayout(self.horizontalLayout_4) - self.leftLayout.addWidget(self.listBox) self.groupBox_2 = QGroupBox(MainDialog) - self.groupBox_2.setObjectName(u"groupBox_2") + self.groupBox_2.setObjectName("groupBox_2") self.groupBox_2.setMaximumSize(QSize(16777215, 200)) self.verticalLayout_7 = QVBoxLayout(self.groupBox_2) - self.verticalLayout_7.setObjectName(u"verticalLayout_7") + self.verticalLayout_7.setObjectName("verticalLayout_7") self.logBrowser = QTextBrowser(self.groupBox_2) - self.logBrowser.setObjectName(u"logBrowser") + self.logBrowser.setObjectName("logBrowser") self.verticalLayout_7.addWidget(self.logBrowser) - self.leftLayout.addWidget(self.groupBox_2) - self.horizontalLayout.addLayout(self.leftLayout) self.rightLayout = QVBoxLayout() self.rightLayout.setSpacing(6) - self.rightLayout.setObjectName(u"rightLayout") + self.rightLayout.setObjectName("rightLayout") self.settingsBox = QGroupBox(MainDialog) - self.settingsBox.setObjectName(u"settingsBox") + self.settingsBox.setObjectName("settingsBox") self.settingsBox.setMaximumSize(QSize(400, 16777215)) self.verticalLayout = QVBoxLayout(self.settingsBox) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self.loginGroupBox = QGroupBox(self.settingsBox) - self.loginGroupBox.setObjectName(u"loginGroupBox") + self.loginGroupBox.setObjectName("loginGroupBox") self.horizontalLayout_2 = QHBoxLayout(self.loginGroupBox) - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.verticalLayout_2 = QVBoxLayout() - self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.verticalLayout_2.setObjectName("verticalLayout_2") self.radioCOM = QRadioButton(self.loginGroupBox) - self.radioCOM.setObjectName(u"radioCOM") + self.radioCOM.setObjectName("radioCOM") self.verticalLayout_2.addWidget(self.radioCOM) self.radioCN = QRadioButton(self.loginGroupBox) - self.radioCN.setObjectName(u"radioCN") + self.radioCN.setObjectName("radioCN") self.radioCN.setChecked(True) self.verticalLayout_2.addWidget(self.radioCN) self.radioJP = QRadioButton(self.loginGroupBox) - self.radioJP.setObjectName(u"radioJP") + self.radioJP.setObjectName("radioJP") self.verticalLayout_2.addWidget(self.radioJP) self.radioDE = QRadioButton(self.loginGroupBox) - self.radioDE.setObjectName(u"radioDE") + self.radioDE.setObjectName("radioDE") self.verticalLayout_2.addWidget(self.radioDE) self.radioUK = QRadioButton(self.loginGroupBox) - self.radioUK.setObjectName(u"radioUK") + self.radioUK.setObjectName("radioUK") self.verticalLayout_2.addWidget(self.radioUK) self.horizontalLayout_2.addLayout(self.verticalLayout_2) self.loginButton = QPushButton(self.loginGroupBox) - self.loginButton.setObjectName(u"loginButton") + self.loginButton.setObjectName("loginButton") self.loginButton.setMaximumSize(QSize(80, 16777215)) self.horizontalLayout_2.addWidget(self.loginButton) - self.verticalLayout.addWidget(self.loginGroupBox) self.cookiesGroupBox = QGroupBox(self.settingsBox) - self.cookiesGroupBox.setObjectName(u"cookiesGroupBox") + self.cookiesGroupBox.setObjectName("cookiesGroupBox") self.verticalLayout_3 = QVBoxLayout(self.cookiesGroupBox) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.verticalLayout_3.setObjectName("verticalLayout_3") self.cookieTextEdit = QPlainTextEdit(self.cookiesGroupBox) - self.cookieTextEdit.setObjectName(u"cookieTextEdit") + self.cookieTextEdit.setObjectName("cookieTextEdit") self.cookieTextEdit.setEnabled(False) self.verticalLayout_3.addWidget(self.cookieTextEdit) self.horizontalLayout_3 = QHBoxLayout() - self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.radioFromInput = QRadioButton(self.cookiesGroupBox) - self.radioFromInput.setObjectName(u"radioFromInput") + self.radioFromInput.setObjectName("radioFromInput") self.horizontalLayout_3.addWidget(self.radioFromInput) self.radioFromBrowser = QRadioButton(self.cookiesGroupBox) - self.radioFromBrowser.setObjectName(u"radioFromBrowser") + self.radioFromBrowser.setObjectName("radioFromBrowser") self.radioFromBrowser.setChecked(True) self.horizontalLayout_3.addWidget(self.radioFromBrowser) - self.verticalLayout_3.addLayout(self.horizontalLayout_3) - self.verticalLayout.addWidget(self.cookiesGroupBox) self.groupBox = QGroupBox(self.settingsBox) - self.groupBox.setObjectName(u"groupBox") + self.groupBox.setObjectName("groupBox") self.verticalLayout_4 = QVBoxLayout(self.groupBox) - self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.verticalLayout_4.setObjectName("verticalLayout_4") self.csrfEdit = QLineEdit(self.groupBox) - self.csrfEdit.setObjectName(u"csrfEdit") + self.csrfEdit.setObjectName("csrfEdit") self.verticalLayout_4.addWidget(self.csrfEdit) - self.verticalLayout.addWidget(self.groupBox) self.horizontalLayout_5 = QHBoxLayout() - self.horizontalLayout_5.setObjectName(u"horizontalLayout_5") + self.horizontalLayout_5.setObjectName("horizontalLayout_5") self.label = QLabel(self.settingsBox) - self.label.setObjectName(u"label") + self.label.setObjectName("label") self.horizontalLayout_5.addWidget(self.label) self.cutLengthSpin = QSpinBox(self.settingsBox) - self.cutLengthSpin.setObjectName(u"cutLengthSpin") + self.cutLengthSpin.setObjectName("cutLengthSpin") self.cutLengthSpin.setMaximum(999) self.cutLengthSpin.setValue(100) self.horizontalLayout_5.addWidget(self.cutLengthSpin) - self.verticalLayout.addLayout(self.horizontalLayout_5) - self.rightLayout.addWidget(self.settingsBox, 0, Qt.AlignTop) self.copyrightBox = QGroupBox(MainDialog) - self.copyrightBox.setObjectName(u"copyrightBox") + self.copyrightBox.setObjectName("copyrightBox") self.verticalLayout_6 = QVBoxLayout(self.copyrightBox) - self.verticalLayout_6.setObjectName(u"verticalLayout_6") + self.verticalLayout_6.setObjectName("verticalLayout_6") self.label_6 = QLabel(self.copyrightBox) - self.label_6.setObjectName(u"label_6") + self.label_6.setObjectName("label_6") self.verticalLayout_6.addWidget(self.label_6) self.label_3 = QLabel(self.copyrightBox) - self.label_3.setObjectName(u"label_3") + self.label_3.setObjectName("label_3") self.label_3.setTextFormat(Qt.MarkdownText) self.verticalLayout_6.addWidget(self.label_3) self.label_4 = QLabel(self.copyrightBox) - self.label_4.setObjectName(u"label_4") + self.label_4.setObjectName("label_4") self.label_4.setTextFormat(Qt.MarkdownText) self.verticalLayout_6.addWidget(self.label_4) self.label_5 = QLabel(self.copyrightBox) - self.label_5.setObjectName(u"label_5") + self.label_5.setObjectName("label_5") self.verticalLayout_6.addWidget(self.label_5) - self.rightLayout.addWidget(self.copyrightBox, 0, Qt.AlignTop) - self.horizontalLayout.addLayout(self.rightLayout) self.horizontalLayout.setStretch(0, 1) @@ -274,44 +302,127 @@ class Ui_MainDialog(object): self.retranslateUi(MainDialog) QMetaObject.connectSlotsByName(MainDialog) + # setupUi def retranslateUi(self, MainDialog): - MainDialog.setWindowTitle(QCoreApplication.translate("MainDialog", u"Kindle \u4e0b\u8f7d\u52a9\u624b", None)) - self.listBox.setTitle(QCoreApplication.translate("MainDialog", u"\u4e0b\u8f7d\u5217\u8868", None)) - self.radioEBOK.setText(QCoreApplication.translate("MainDialog", u"\u7535\u5b50\u4e66", None)) - self.radioPDO.setText(QCoreApplication.translate("MainDialog", u"\u4e2a\u4eba\u6587\u6863", None)) - self.fetchButton.setText(QCoreApplication.translate("MainDialog", u"\u83b7\u53d6\u4e0b\u8f7d\u5217\u8868", None)) - self.label_2.setText(QCoreApplication.translate("MainDialog", u"\u76ee\u6807\u6587\u4ef6\u5939", None)) - self.outDirEdit.setText(QCoreApplication.translate("MainDialog", u"DOWNLOADS", None)) - self.browseButton.setText(QCoreApplication.translate("MainDialog", u"\u6d4f\u89c8...", None)) - self.downloadButton.setText(QCoreApplication.translate("MainDialog", u"\u4e0b\u8f7d\u5168\u90e8", None)) - self.selectedButton.setText(QCoreApplication.translate("MainDialog", u"Download Selected", None)) - self.dedrmCkb.setText(QCoreApplication.translate("MainDialog", u"DeDRM", None)) - self.groupBox_2.setTitle(QCoreApplication.translate("MainDialog", u"\u8f93\u51fa", None)) - self.logBrowser.setHtml(QCoreApplication.translate("MainDialog", u"\n" -"\n" -"


", None)) - self.settingsBox.setTitle(QCoreApplication.translate("MainDialog", u"\u8bbe\u7f6e", None)) + MainDialog.setWindowTitle( + QCoreApplication.translate( + "MainDialog", "Kindle \u4e0b\u8f7d\u52a9\u624b", None + ) + ) + self.listBox.setTitle( + QCoreApplication.translate("MainDialog", "\u4e0b\u8f7d\u5217\u8868", None) + ) + self.radioEBOK.setText( + QCoreApplication.translate("MainDialog", "\u7535\u5b50\u4e66", None) + ) + self.radioPDO.setText( + QCoreApplication.translate("MainDialog", "\u4e2a\u4eba\u6587\u6863", None) + ) + self.fetchButton.setText( + QCoreApplication.translate( + "MainDialog", "\u83b7\u53d6\u4e0b\u8f7d\u5217\u8868", None + ) + ) + self.label_2.setText( + QCoreApplication.translate( + "MainDialog", "\u76ee\u6807\u6587\u4ef6\u5939", None + ) + ) + self.outDirEdit.setText( + QCoreApplication.translate("MainDialog", "DOWNLOADS", None) + ) + self.browseButton.setText( + QCoreApplication.translate("MainDialog", "\u6d4f\u89c8...", None) + ) + self.downloadButton.setText( + QCoreApplication.translate("MainDialog", "\u4e0b\u8f7d\u5168\u90e8", None) + ) + self.selectedButton.setText( + QCoreApplication.translate("MainDialog", "Download Selected", None) + ) + self.dedrmCkb.setText(QCoreApplication.translate("MainDialog", "DeDRM", None)) + self.groupBox_2.setTitle( + QCoreApplication.translate("MainDialog", "\u8f93\u51fa", None) + ) + self.logBrowser.setHtml( + QCoreApplication.translate( + "MainDialog", + '\n' + '\n" + "


", + None, + ) + ) + self.settingsBox.setTitle( + QCoreApplication.translate("MainDialog", "\u8bbe\u7f6e", None) + ) self.loginGroupBox.setTitle("") - self.radioCOM.setText(QCoreApplication.translate("MainDialog", u"\u7f8e\u4e9a(.com)", None)) - self.radioCN.setText(QCoreApplication.translate("MainDialog", u"\u4e2d\u4e9a(.cn)", None)) - self.radioJP.setText(QCoreApplication.translate("MainDialog", u"\u65e5\u4e9a(.jp)", None)) - self.radioDE.setText(QCoreApplication.translate("MainDialog", u"\u5fb7\u4e9a(.de)", None)) - self.radioUK.setText(QCoreApplication.translate("MainDialog", u"\u82F1\u4e9a(.uk)", None)) - self.loginButton.setText(QCoreApplication.translate("MainDialog", u"\u767b\u5f55", None)) - self.cookiesGroupBox.setTitle(QCoreApplication.translate("MainDialog", u"Cookies", None)) - self.radioFromInput.setText(QCoreApplication.translate("MainDialog", u"\u6765\u81ea\u8f93\u5165", None)) - self.radioFromBrowser.setText(QCoreApplication.translate("MainDialog", u"\u6765\u81ea\u6d4f\u89c8\u5668", None)) - self.groupBox.setTitle(QCoreApplication.translate("MainDialog", u"CSRF Token", None)) - self.label.setText(QCoreApplication.translate("MainDialog", u"\u6587\u4ef6\u540d\u622a\u65ad", None)) + self.radioCOM.setText( + QCoreApplication.translate("MainDialog", "\u7f8e\u4e9a(.com)", None) + ) + self.radioCN.setText( + QCoreApplication.translate("MainDialog", "\u4e2d\u4e9a(.cn)", None) + ) + self.radioJP.setText( + QCoreApplication.translate("MainDialog", "\u65e5\u4e9a(.jp)", None) + ) + self.radioDE.setText( + QCoreApplication.translate("MainDialog", "\u5fb7\u4e9a(.de)", None) + ) + self.radioUK.setText( + QCoreApplication.translate("MainDialog", "\u82F1\u4e9a(.uk)", None) + ) + self.loginButton.setText( + QCoreApplication.translate("MainDialog", "\u767b\u5f55", None) + ) + self.cookiesGroupBox.setTitle( + QCoreApplication.translate("MainDialog", "Cookies", None) + ) + self.radioFromInput.setText( + QCoreApplication.translate("MainDialog", "\u6765\u81ea\u8f93\u5165", None) + ) + self.radioFromBrowser.setText( + QCoreApplication.translate( + "MainDialog", "\u6765\u81ea\u6d4f\u89c8\u5668", None + ) + ) + self.groupBox.setTitle( + QCoreApplication.translate("MainDialog", "CSRF Token", None) + ) + self.label.setText( + QCoreApplication.translate( + "MainDialog", "\u6587\u4ef6\u540d\u622a\u65ad", None + ) + ) self.copyrightBox.setTitle("") - self.label_6.setText(QCoreApplication.translate("MainDialog", u"\u9690\u79c1\u58f0\u660e\uff1a\u6211\u4eec\u4e0d\u4f1a\u6536\u96c6\u4efb\u4f55\u7528\u6237\u4fe1\u606f\uff0c\u8bf7\u653e\u5fc3\u4f7f\u7528", None)) - self.label_3.setText(QCoreApplication.translate("MainDialog", u"Copyright 2022 \u00a9 [yihong0618](https://github.com/yihong0618) and [frostming](https://github.com/frostming)", None)) - self.label_4.setText(QCoreApplication.translate("MainDialog", u"GitHub: ", None)) - self.label_5.setText(QCoreApplication.translate("MainDialog", u"License: GPL V3", None)) - # retranslateUi + self.label_6.setText( + QCoreApplication.translate( + "MainDialog", + "\u9690\u79c1\u58f0\u660e\uff1a\u6211\u4eec\u4e0d\u4f1a\u6536\u96c6\u4efb\u4f55\u7528\u6237\u4fe1\u606f\uff0c\u8bf7\u653e\u5fc3\u4f7f\u7528", + None, + ) + ) + self.label_3.setText( + QCoreApplication.translate( + "MainDialog", + "Copyright 2022 \u00a9 [yihong0618](https://github.com/yihong0618) and [frostming](https://github.com/frostming)", + None, + ) + ) + self.label_4.setText( + QCoreApplication.translate( + "MainDialog", + "GitHub: ", + None, + ) + ) + self.label_5.setText( + QCoreApplication.translate("MainDialog", "License: GPL V3", None) + ) + # retranslateUi diff --git a/kindle_download_helper/cli.py b/kindle_download_helper/cli.py index f8d7f93..10859db 100644 --- a/kindle_download_helper/cli.py +++ b/kindle_download_helper/cli.py @@ -9,6 +9,7 @@ from kindle_download_helper.config import ( DEFAULT_OUT_DIR, DEFAULT_SESSION_FILE, DEFAULT_OUT_DEDRM_DIR, + DEFAULT_OUT_EPUB_DIR, ) logger = logging.getLogger("kindle") @@ -18,82 +19,119 @@ logger.addHandler(fh) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + # download selected books for cli def download_selected_books(kindle, options): # get all books and get the default device - print('Getting all books, please wait...') + print("Getting all books, please wait...") books = kindle.get_all_books(filetype=options.filetype) device = kindle.find_device() - + # print all books for idx, book in enumerate(books): - print('Index: ' + '{:>5d}'.format(idx+1) + ' | Title: ' + book['title'] + ' | asin: '+book['asin']) - + print( + "Index: " + + "{:>5d}".format(idx + 1) + + " | Title: " + + book["title"] + + " | asin: " + + book["asin"] + ) + # download loop while True: # get the indices of the books to download - indices = input('Input the index of books you want to download, split by space (q to quit, l to list books).\n').split() - + indices = input( + "Input the index of books you want to download, split by space (q to quit, l to list books).\n" + ).split() + # if input "q", quit # if input "l", list all books again - if indices[0] == 'q': + if indices[0] == "q": break - elif indices[0] == 'l': + elif indices[0] == "l": for idx, book in enumerate(books): - print('Index: ' + '{:>5d}'.format(idx+1) + ' | Title: ' + book['title'] + ' | asin: '+book['asin']) + print( + "Index: " + + "{:>5d}".format(idx + 1) + + " | Title: " + + book["title"] + + " | asin: " + + book["asin"] + ) continue - + # decode the indices downlist = [] flag = True for idx in indices: if idx.isnumeric() == False: - if ':' in idx: + if ":" in idx: # if is not a number, and ":" in it, then it is a range # decode the range - idx_begin, idx_end = [int(i) for i in idx.split(':')] + idx_begin, idx_end = [int(i) for i in idx.split(":")] # append the range to downlist - extend_list = [i for i in range(idx_begin-1, idx_end)] + extend_list = [i for i in range(idx_begin - 1, idx_end)] downlist.extend(extend_list) else: # if is not a number, and no ":" in it, then it is an error - print('Input error, please input numbers!!!') + print("Input error, please input numbers!!!") flag = False break else: # if is a number, then append it to downlist - downlist.append(int(idx)-1) + downlist.append(int(idx) - 1) if not flag: continue - + # remove the duplicate indices downlist = list(set(downlist)) - + # check if the indices are valid if max(downlist) >= len(books) or min(downlist) < 0: - print('Input error, please input numbers between 1 and ' + str(len(books)) + '!!!') + print( + "Input error, please input numbers between 1 and " + + str(len(books)) + + "!!!" + ) continue - + # print the books to download for idx in downlist: - print('Index: ' + '{:>5d}'.format(idx+1) + ' | Title: ' + books[idx]['title'] + ' | asin: '+books[idx]['asin']) - print('Downloading ' + str(len(downlist)) + ' books:') - + print( + "Index: " + + "{:>5d}".format(idx + 1) + + " | Title: " + + books[idx]["title"] + + " | asin: " + + books[idx]["asin"] + ) + print("Downloading " + str(len(downlist)) + " books:") + # ask if to continue while True: - flag = input('Continue? (y/n)') - if flag == 'y' or flag == 'n': + flag = input("Continue? (y/n)") + if flag == "y" or flag == "n": break else: - print('Input error, please input y or n') - if flag == 'n': + print("Input error, please input y or n") + if flag == "n": continue - + # download the books for i, idx in enumerate(downlist): - print('Downloading ' + str(i+1) + '/' + str(len(downlist)) + ' ' + books[idx]['title'] + ' ...') + print( + "Downloading " + + str(i + 1) + + "/" + + str(len(downlist)) + + " " + + books[idx]["title"] + + " ..." + ) kindle.download_one_book(books[idx], device, idx, filetype=options.filetype) - print('Download finished.') + print("Download finished.") + def main(): logger.setLevel(os.environ.get("LOGGING_LEVEL", "INFO")) @@ -165,6 +203,12 @@ def main(): default=DEFAULT_OUT_DEDRM_DIR, help="dwonload output dedrm dir", ) + parser.add_argument( + "-oe", + "--outepubmdir", + default=DEFAULT_OUT_EPUB_DIR, + help="dwonload output epub dir", + ) parser.add_argument( "-s", "--session-file", @@ -211,7 +255,7 @@ def main(): default="", help="Download file for device with this serial number", ) - + parser.add_argument( "--mode", dest="mode", @@ -226,11 +270,16 @@ def main(): # for dedrm if not os.path.exists(options.outdedrmdir): os.makedirs(options.outdedrmdir) + # for epub + if not os.path.exists(options.outepubmdir): + os.makedirs(options.outepubmdir) + kindle = Kindle( options.csrf_token, options.domain, options.outdir, options.outdedrmdir, + options.outepubmdir, options.cut_length, session_file=options.session_file, device_sn=options.device_sn, @@ -265,7 +314,9 @@ def main(): # check the download mode if options.mode == "all": # download all books - kindle.download_books(start_index=options.index - 1, filetype=options.filetype) + kindle.download_books( + start_index=options.index - 1, filetype=options.filetype + ) elif options.mode == "sel": # download selected books download_selected_books(kindle, options) diff --git a/kindle_download_helper/config.py b/kindle_download_helper/config.py index 634d876..23fe3e8 100644 --- a/kindle_download_helper/config.py +++ b/kindle_download_helper/config.py @@ -4,6 +4,7 @@ from kindle_download_helper.user_agents import USER_AGENTS DEFAULT_OUT_DIR = "DOWNLOADS" DEFAULT_OUT_DEDRM_DIR = "DEDRMS" +DEFAULT_OUT_EPUB_DIR = "EPUB" DEFAULT_SESSION_FILE = ".kindle_session" KINDLE_HEADER = { diff --git a/kindle_download_helper/dedrm/kgenpids.py b/kindle_download_helper/dedrm/kgenpids.py index 5470c37..25b67f8 100644 --- a/kindle_download_helper/dedrm/kgenpids.py +++ b/kindle_download_helper/dedrm/kgenpids.py @@ -22,6 +22,7 @@ charMap1 = b"n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap3 = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap4 = b"ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + # crypto digestroutines def MD5(message): ctx = hashlib.md5() diff --git a/kindle_download_helper/kindle.py b/kindle_download_helper/kindle.py index ed175b6..11ada04 100644 --- a/kindle_download_helper/kindle.py +++ b/kindle_download_helper/kindle.py @@ -13,6 +13,8 @@ import re import time import urllib import pathlib +import shutil +from mobi import extract from http.cookies import SimpleCookie import requests @@ -23,8 +25,9 @@ from kindle_download_helper.dedrm import MobiBook, get_pid_list from kindle_download_helper.config import ( KINDLE_URLS, DEFAULT_OUT_DIR, - DEFAULT_SESSION_FILE, DEFAULT_OUT_DEDRM_DIR, + DEFAULT_OUT_EPUB_DIR, + DEFAULT_SESSION_FILE, CONTENT_TYPES, KINDLE_STAT_TEMPLATE, ) @@ -48,6 +51,7 @@ logger.addHandler(fh) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + class Kindle: def __init__( self, @@ -55,6 +59,7 @@ class Kindle: domain="cn", out_dir=DEFAULT_OUT_DIR, out_dedrm_dir=DEFAULT_OUT_DEDRM_DIR, + out_epub_dir=DEFAULT_OUT_EPUB_DIR, cut_length=100, session_file=DEFAULT_SESSION_FILE, **kwargs, @@ -64,6 +69,7 @@ class Kindle: self.total_to_download = 0 self.out_dir = out_dir self.out_dedrm_dir = out_dedrm_dir + self.out_epub_dir = out_epub_dir self.dedrm = False self.cut_length = cut_length self.not_done = False @@ -104,7 +110,7 @@ class Kindle: if not self.session.cookies: logger.debug("No cookie found, trying to load from browsers") try: - self.set_cookie(browser_cookie3.load(domain_name="amazon")) + self.set_cookie(browser_cookie3.load()) except: print("not found browser_cookie3 here, you should use --cookie command") @@ -120,11 +126,11 @@ class Kindle: cookies_dict, cookiejar=None, overwrite=True ) return cookiejar - + def find_device(self): devices = self.get_devices() device_sn = self.device_sn - + if isinstance(device_sn, str) and device_sn != "": for device in devices: if device["deviceSerialNumber"] == device_sn.strip(): @@ -364,7 +370,9 @@ class Kindle: book_title = book.get("title", "") # filter the brackets in the book title - book_title = re.sub(r"(\([^)]*\)|\([^)]*\)|\【[^)]*\】|\[[^)]*\])", "", book_title) + book_title = re.sub( + r"(\([^)]*\)|\([^)]*\)|\【[^)]*\】|\[[^)]*\])", "", book_title + ) book_title = book_title.replace(" ", "") if book.get("category", "") == "KindleEBook": @@ -466,27 +474,28 @@ class Kindle: out = os.path.join(self.out_dir, name) out_dedrm = os.path.join(self.out_dedrm_dir, name) + out_epub = os.path.join(self.out_epub_dir, name.split(".")[0] + ".epub") - #normally one owns no more than 9999 books + # normally one owns no more than 9999 books count_digit_length = 4 size_length = 6 - size_in_mb = round(float(total_size) / (1024*1024), 3) - + size_in_mb = round(float(total_size) / (1024 * 1024), 3) + logger.info( f"[{index+1:>{count_digit_length}}/{self.total_to_download:>{count_digit_length}}][{size_in_mb:> {size_length}}Mb]Downloading {name}" ) - #try if we can writ the file - try : + # try if we can write the file + try: pathlib.Path(out).touch() except OSError as e: - if e.errno == 36 : #means file name too long + if e.errno == 36: # means file name too long name = self.trim_title_suffix(title) + extname logger.info(f"Original filename too long, trim to {name}") out = os.path.join(self.out_dir, name) out_dedrm = os.path.join(self.out_dedrm_dir, name) - else : + else: logger.error(e) with open(out, "wb") as f: @@ -501,6 +510,14 @@ class Kindle: totalpids = get_pid_list(md1, md2, [self.device_serial_number], []) totalpids = list(set(totalpids)) mb.make_drm_file(totalpids, out_dedrm) + time.sleep(1) + # save to EPUB + epub_dir, epub_file = extract(out_dedrm) + print(epub_file) + shutil.copy2(epub_file, out_epub) + # delete it + shutil.rmtree(epub_dir) + except Exception as e: logger.error("DeDRM failed for %s: %s", name, e) pass @@ -511,7 +528,7 @@ class Kindle: def download_books(self, start_index=0, filetype="EBOK"): # use default device device = self.find_device() - + books = self.get_all_books(filetype=filetype, start_index=start_index) if start_index > 0: print(f"resuming the download {start_index + 1}/{self.total_to_download}") @@ -538,7 +555,11 @@ class Kindle: with open(os.path.join(self.out_dir, "key.txt"), "w") as f: f.write(f"Key is: {device['deviceSerialNumber']}") - logger.info("the device serial number can also be found here: {0}".format(os.path.join(self.out_dir, "key.txt"))) + logger.info( + "the device serial number can also be found here: {0}".format( + os.path.join(self.out_dir, "key.txt") + ) + ) def trim_title_suffix(self, title): return re.sub(r"(([^)]+)?|【[^】]+】?)", "", title) diff --git a/requirements.txt b/requirements.txt index 5df0bfb..193c809 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ requests browser-cookie3 +mobi pywin32 ; sys_platform == 'win32'