Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# Archive.org Ripper

This script lets you download books page-by-page from [archive.org](https://archive.org) in the event that there is no PDF link. Any book with a <14 day loan period is like this, as you can see:
This program lets you download books page-by-page from [archive.org](https://archive.org) in the event that there is no PDF link. Any book with a <14 day loan period is like this, as you can see:

![](./archive.png)

The script needs your login credentials to borrow the book, then it will run on its own using your session.
The program needs your login credentials to borrow the book, then it will run on its own using your session.

Do not use this program in an illegal manner. Thanks!

## Setup

Go to the Releases page if you want to download the GUI version, packaged into an executable. This is the most user-friendly option.

For a command-line interface, just clone this repo, create a virtual environment, run `pip install -r requirements.txt`, and then `python ripper.py` with optional arguments.

## Screenshots

![](./screenshot.png)
Expand All @@ -17,6 +23,4 @@ Do not use this program in an illegal manner. Thanks!

- Searching for books instead of inputting id directly

- GUI

- Option to convert to pdf or epub instead of saving each page individually
73 changes: 73 additions & 0 deletions archiveripper/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------

*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash

# qtcreator generated files
*.pro.user*

# xemacs temporary files
*.flc

# Vim temporary files
.*.swp

# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*

# MinGW generated files
*.Debug
*.Release

# Python byte code
*.pyc

# Binaries
# --------
*.dll
*.exe

10 changes: 10 additions & 0 deletions archiveripper/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This directory contains the GUI version of archiveripper

import sys
from PySide6.QtWidgets import QApplication
from .ripper import ArchiveRipper

app = QApplication(sys.argv)
widget = ArchiveRipper()
widget.show()
sys.exit(app.exec())
File renamed without changes.
79 changes: 79 additions & 0 deletions archiveripper/ripper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from PySide6.QtCore import Slot
from PySide6.QtWidgets import QDialog, QMainWindow, QMessageBox

from .api import ArchiveReaderClient
from .ui.ui_ArchiveRipper import Ui_ArchiveRipper
from .ui.ui_CredentialsDialog import Ui_CredentialsDialog
from .ui.ui_NewRipDialog import Ui_NewRipDialog


class ArchiveRipper(QMainWindow):

def __init__(self):
super(ArchiveRipper, self).__init__()
self.ui = Ui_ArchiveRipper()
self.ui.setupUi(self)

# dialogs
self.new_rip_dialog = QDialog()
self.new_rip_dialog.ui = Ui_NewRipDialog()
self.new_rip_dialog.ui.setupUi(self.new_rip_dialog)
self.new_rip_dialog.accepted.connect(self.new_rip)

self.credentials_dialog = QDialog()
self.credentials_dialog.ui = Ui_CredentialsDialog()
self.credentials_dialog.ui.setupUi(self.credentials_dialog)
self.credentials_dialog.accepted.connect(self.update_credentials)


# signals
self.ui.actionNew_Rip.triggered.connect(self.new_rip_dialog.exec)
self.ui.actionExit.triggered.connect(self.close)

# api client
self.client = ArchiveReaderClient()

# application state
self.credentials = {
"email": None,
"password": None
}
self.book_url = None
self.dest_path = None
self.start_page = None
self.end_page = None

@Slot()
def new_rip(self):
book_url = self.new_rip_dialog.ui.bookUrl.text()
self.book_url = book_url[book_url.rfind("/") + 1:]
self.dest_path = self.new_rip_dialog.ui.filePath.text()

self.verify_login()
msgbox = QMessageBox()
msgbox.setWindowTitle("New Rip Status")
try:
self.client.schedule_loan_book(self.book_url)
msgbox.setText("Borrowed book successfully")
msgbox.setIcon(QMessageBox.Information)
except Exception as e:
msgbox.setText(f"Failed to borrow book!\n{e}")
msgbox.setIcon(QMessageBox.Critical)
finally:
msgbox.exec()

def verify_login(self):
if not self.credentials["email"] or not self.credentials["password"]:
self.credentials_dialog.exec()
try:
self.client.login(self.credentials["email"], self.credentials["password"])
except Exception as e:
msgbox = QMessageBox()
msgbox.setText(f"Failed to log in!\n{e}")
msgbox.setIcon(QMessageBox.Critical)
msgbox.exec()

@Slot()
def update_credentials(self):
self.credentials["email"] = self.credentials_dialog.ui.email.text()
self.credentials["password"] = self.credentials_dialog.ui.password.text()
90 changes: 90 additions & 0 deletions archiveripper/ui/ui_ArchiveRipper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'ArchiveRipper.ui'
##
## Created by: Qt User Interface Compiler version 6.1.3
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide6.QtCore import * # type: ignore
from PySide6.QtGui import * # type: ignore
from PySide6.QtWidgets import * # type: ignore


class Ui_ArchiveRipper(object):
def setupUi(self, ArchiveRipper):
if not ArchiveRipper.objectName():
ArchiveRipper.setObjectName(u"ArchiveRipper")
ArchiveRipper.resize(640, 480)
self.actionExit = QAction(ArchiveRipper)
self.actionExit.setObjectName(u"actionExit")
self.actionPreferences = QAction(ArchiveRipper)
self.actionPreferences.setObjectName(u"actionPreferences")
self.actionAbout = QAction(ArchiveRipper)
self.actionAbout.setObjectName(u"actionAbout")
self.actionNew_Rip = QAction(ArchiveRipper)
self.actionNew_Rip.setObjectName(u"actionNew_Rip")
self.actionOpen_Rip = QAction(ArchiveRipper)
self.actionOpen_Rip.setObjectName(u"actionOpen_Rip")
self.actionExport = QAction(ArchiveRipper)
self.actionExport.setObjectName(u"actionExport")
self.actionSave = QAction(ArchiveRipper)
self.actionSave.setObjectName(u"actionSave")
self.actionSave_As = QAction(ArchiveRipper)
self.actionSave_As.setObjectName(u"actionSave_As")
self.centralwidget = QWidget(ArchiveRipper)
self.centralwidget.setObjectName(u"centralwidget")
ArchiveRipper.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(ArchiveRipper)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 640, 21))
self.menuFile = QMenu(self.menubar)
self.menuFile.setObjectName(u"menuFile")
self.menuHelp = QMenu(self.menubar)
self.menuHelp.setObjectName(u"menuHelp")
ArchiveRipper.setMenuBar(self.menubar)
self.statusbar = QStatusBar(ArchiveRipper)
self.statusbar.setObjectName(u"statusbar")
ArchiveRipper.setStatusBar(self.statusbar)

self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuHelp.menuAction())
self.menuFile.addAction(self.actionNew_Rip)
self.menuFile.addAction(self.actionOpen_Rip)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionSave)
self.menuFile.addAction(self.actionSave_As)
self.menuFile.addAction(self.actionExport)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionPreferences)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionExit)
self.menuHelp.addAction(self.actionAbout)

self.retranslateUi(ArchiveRipper)

QMetaObject.connectSlotsByName(ArchiveRipper)
# setupUi

def retranslateUi(self, ArchiveRipper):
ArchiveRipper.setWindowTitle(QCoreApplication.translate("ArchiveRipper", u"ArchiveRipper", None))
self.actionExit.setText(QCoreApplication.translate("ArchiveRipper", u"Exit", None))
#if QT_CONFIG(shortcut)
self.actionExit.setShortcut(QCoreApplication.translate("ArchiveRipper", u"Ctrl+Q", None))
#endif // QT_CONFIG(shortcut)
self.actionPreferences.setText(QCoreApplication.translate("ArchiveRipper", u"Preferences...", None))
#if QT_CONFIG(shortcut)
self.actionPreferences.setShortcut(QCoreApplication.translate("ArchiveRipper", u"Ctrl+,", None))
#endif // QT_CONFIG(shortcut)
self.actionAbout.setText(QCoreApplication.translate("ArchiveRipper", u"About", None))
self.actionNew_Rip.setText(QCoreApplication.translate("ArchiveRipper", u"New Rip...", None))
self.actionOpen_Rip.setText(QCoreApplication.translate("ArchiveRipper", u"Open Rip...", None))
self.actionExport.setText(QCoreApplication.translate("ArchiveRipper", u"Export...", None))
self.actionSave.setText(QCoreApplication.translate("ArchiveRipper", u"Save", None))
self.actionSave_As.setText(QCoreApplication.translate("ArchiveRipper", u"Save As...", None))
self.menuFile.setTitle(QCoreApplication.translate("ArchiveRipper", u"File", None))
self.menuHelp.setTitle(QCoreApplication.translate("ArchiveRipper", u"Help", None))
# retranslateUi

69 changes: 69 additions & 0 deletions archiveripper/ui/ui_CredentialsDialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'CredentialsDialog.ui'
##
## Created by: Qt User Interface Compiler version 6.1.3
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide6.QtCore import * # type: ignore
from PySide6.QtGui import * # type: ignore
from PySide6.QtWidgets import * # type: ignore


class Ui_CredentialsDialog(object):
def setupUi(self, CredentialsDialog):
if not CredentialsDialog.objectName():
CredentialsDialog.setObjectName(u"CredentialsDialog")
CredentialsDialog.resize(400, 100)
self.verticalLayout = QVBoxLayout(CredentialsDialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.formLayout = QFormLayout()
self.formLayout.setObjectName(u"formLayout")
self.email = QLineEdit(CredentialsDialog)
self.email.setObjectName(u"email")

self.formLayout.setWidget(0, QFormLayout.FieldRole, self.email)

self.label = QLabel(CredentialsDialog)
self.label.setObjectName(u"label")

self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)

self.label_2 = QLabel(CredentialsDialog)
self.label_2.setObjectName(u"label_2")

self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2)

self.password = QLineEdit(CredentialsDialog)
self.password.setObjectName(u"password")
self.password.setEchoMode(QLineEdit.Password)

self.formLayout.setWidget(1, QFormLayout.FieldRole, self.password)


self.verticalLayout.addLayout(self.formLayout)

self.buttonBox = QDialogButtonBox(CredentialsDialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)

self.verticalLayout.addWidget(self.buttonBox)


self.retranslateUi(CredentialsDialog)
self.buttonBox.accepted.connect(CredentialsDialog.accept)
self.buttonBox.rejected.connect(CredentialsDialog.reject)

QMetaObject.connectSlotsByName(CredentialsDialog)
# setupUi

def retranslateUi(self, CredentialsDialog):
CredentialsDialog.setWindowTitle(QCoreApplication.translate("CredentialsDialog", u"Enter Archive.org Credentials", None))
self.label.setText(QCoreApplication.translate("CredentialsDialog", u"Archive.org email:", None))
self.label_2.setText(QCoreApplication.translate("CredentialsDialog", u"Password:", None))
# retranslateUi

Loading