First cut.

This commit is contained in:
Vahagn Khachatryan
2018-11-20 18:24:14 +00:00
commit 68f43a2a50
32 changed files with 17670 additions and 0 deletions

36
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
// {
// "name": "Launch index.html",
// "type": "firefox",
// "request": "launch",
// "reAttach": true,
// "file": "${workspaceFolder}/index.html"
// },
// {
// "name": "Launch localhost",
// "type": "firefox",
// "request": "launch",
// "reAttach": true,
// "url": "http://localhost/index.html",
// "webRoot": "${workspaceFolder}"
// },
// {
// "name": "Attach",
// "type": "firefox",
// "request": "attach"
// },
{
"name": "Launch addon",
"type": "firefox",
"request": "launch",
"reAttach": true,
"addonType": "webExtension",
"addonPath": "${workspaceFolder}/src"
}
]
}

5619
src/3pty/bluebird.js Normal file

File diff suppressed because it is too large Load Diff

6
src/3pty/bootstrap-theme.min.css vendored Normal file

File diff suppressed because one or more lines are too long

6
src/3pty/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

7
src/3pty/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
src/3pty/jquery-3.1.1.slim.min.js vendored Normal file

File diff suppressed because one or more lines are too long

11391
src/3pty/jszip.js Normal file

File diff suppressed because it is too large Load Diff

1
src/3pty/tether.min.js vendored Normal file

File diff suppressed because one or more lines are too long

38
src/epub.js Normal file
View File

@@ -0,0 +1,38 @@
class EpubWriter{
constructor(book_id) {
this.zip = new JSZip();
this.filename = "test";
this.createEpubStruct();
// this.image
// this.book_id = book_id;
// this.raw_book = [];
// this.chapter_list = [];
}
createEpubStruct(){
this.zip.file("mimetype", "application/epub+zip", {compression: "STORE"});
this.zip.file("META-INF/container.xml", `
<?xml version='1.0' encoding='utf-8'?>
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
<rootfiles>
<rootfile media-type="application/oebps-package+xml" full-path="EPUB/content.opf"/>
</rootfiles>
</container>
`);
}
addFile(name, content){
console.log(`epub ${name}: ${content}`)
this.zip.file("EPUB/"+name, content)
}
generateAsync(){
return this.zip.generateAsync({
type: 'blob',
compression: "DEFLATE"})
}
}

BIN
src/icon/book-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/icon/book-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

BIN
src/icon/book-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

BIN
src/icon/book-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/icon/book-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/icon/book-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 539.077 539.077" style="enable-background:new 0 0 539.077 539.077;" xml:space="preserve">
<g>
<g>
<path d="M213.385,539.077H78.615c-30.961,0-56.154-25.181-56.154-56.154V67.385c0-1.206,0.186-2.358,0.537-3.444
c-0.351-2.544-0.537-5.144-0.537-7.787C22.462,25.192,47.654,0,78.615,0h381.846c6.208,0,11.231,5.023,11.231,11.231
s-5.023,11.231-11.231,11.231H78.615c-18.579,0-33.692,15.113-33.692,33.692s15.113,33.692,33.692,33.692h381.846
c6.208,0,11.231,5.023,11.231,11.231v101.077c0,6.208-5.023,11.231-11.231,11.231c-6.208,0-11.231-5.023-11.231-11.231v-89.846
H78.615c-12.635,0-24.304-4.19-33.692-11.253v381.868c0,18.579,15.113,33.692,33.692,33.692h134.769
c6.208,0,11.231,5.023,11.231,11.231C224.615,534.054,219.592,539.077,213.385,539.077z"/>
<path d="M370.615,539.077c-80.502,0-146-65.498-146-146s65.498-146,146-146s146,65.498,146,146S451.117,539.077,370.615,539.077z
M370.615,269.538c-68.108,0-123.538,55.43-123.538,123.538s55.43,123.538,123.538,123.538s123.538-55.43,123.538-123.538
S438.724,269.538,370.615,269.538z"/>
<path d="M370.615,460.462c-0.088,0-0.197,0-0.285,0l0,0c-0.022,0-0.022,0-0.022,0c-0.022,0-0.022,0-0.022,0l0,0
c-1.404-0.044-2.742-0.351-3.948-0.855c-1.228-0.483-2.369-1.228-3.4-2.172l0,0c0,0,0-0.022-0.022-0.022c0,0,0,0,0-0.022l0,0
c-0.088-0.066-0.175-0.154-0.263-0.241l-44.901-44.901c-4.387-4.387-4.387-11.494,0-15.881s11.494-4.387,15.881,0l25.752,25.752
v-85.196c0-6.208,5.023-11.231,11.231-11.231s11.231,5.023,11.231,11.231v85.196l25.752-25.752c4.387-4.387,11.494-4.387,15.881,0
s4.387,11.494,0,15.881l-44.901,44.901c-0.088,0.088-0.175,0.175-0.263,0.241l0,0c0,0.022,0,0.022,0,0.022
c-0.022,0-0.022,0.022-0.022,0.022l0,0c-1.031,0.943-2.172,1.689-3.4,2.172c-1.228,0.504-2.544,0.812-3.948,0.855l0,0
c0,0,0,0-0.022,0c0,0,0,0-0.022,0l0,0C370.813,460.462,370.703,460.462,370.615,460.462z M438,67.385H78.615
c-6.208,0-11.231-5.023-11.231-11.231s5.023-11.231,11.231-11.231H438c6.208,0,11.231,5.023,11.231,11.231
S444.208,67.385,438,67.385z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

50
src/icon/book.svg Normal file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<circle style="fill:#005C83;" cx="256" cy="256" r="256"/>
<path style="fill:#00263F;" d="M191.666,437.01l74.761,74.761C402.975,506.298,512,393.891,512,256
c0-10.316-0.628-20.484-1.814-30.48L393.667,109L191.666,437.01z"/>
<rect x="153.78" y="109" style="fill:#FF6838;" width="194.47" height="270.74"/>
<rect x="240.48" y="109" style="fill:#A04630;" width="107.78" height="270.74"/>
<rect x="327.87" y="109" style="fill:#AE4E32;" width="65.793" height="270.74"/>
<rect x="327.87" y="211.47" style="fill:#FF9451;" width="65.793" height="65.793"/>
<rect x="178.24" y="155" style="fill:#FFCF35;" width="121.11" height="22.707"/>
<rect x="240.48" y="155" style="fill:#FFB344;" width="58.868" height="22.707"/>
<rect x="178.24" y="188.77" style="fill:#FFCF35;" width="121.11" height="22.707"/>
<rect x="240.48" y="188.77" style="fill:#FFB344;" width="58.868" height="22.708"/>
<polygon style="fill:#FFFFFF;" points="255.526,323.654 209.167,370.013 209.167,259.795 174.167,259.795 174.167,370.014
127.808,323.654 103.06,348.402 191.666,437.01 280.274,348.403 "/>
<polygon style="fill:#B9EAFF;" points="191.666,437.01 280.274,348.403 255.526,323.654 209.167,370.013 209.167,259.795
191.666,259.795 "/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/icon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/icon/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

55
src/manifest.json Normal file
View File

@@ -0,0 +1,55 @@
{
"manifest_version": 2,
"name": "Safari-Hunt",
"version": "1.0",
"description": "Hunt safarionline",
"icons": {
"16": "icon/book-download-16.png",
"24": "icon/book-download-24.png",
"32": "icon/book-download-32.png",
"64": "icon/book-download-64.png"
},
"permissions": [
"activeTab",
"tabs",
"downloads",
"*://*.safaribooksonline.com/*"
],
"browser_action": {
"default_icon": {
"16": "icon/book-download-16.png",
"24": "icon/book-download-24.png",
"32": "icon/book-download-32.png",
"64": "icon/book-download-64.png"
},
"default_title": "Hunt Safari Online"
},
"background": {
"page": "safarihunt.html"
},
"sidebar_action": {
"default_icon": {
"16": "icon/book-download-16.png",
"24": "icon/book-download-24.png",
"32": "icon/book-download-32.png",
"64": "icon/book-download-64.png"
},
"default_title": "Hunt Safari Online",
"default_panel": "sidebar.html",
"open_at_install":true
}
// "content_scripts": [
// {
// "matches": ["*://*.safaribooksonline.com/*"],
// "js": ["safarihunt.js"]
// }
// ]
}

7
src/popup.html Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="module" src="safarihunt.js"></script>
</head>
</html>

1
src/popup.js Normal file
View File

@@ -0,0 +1 @@
// document.body.style.border = "5px solid red";

7
src/safarihunt.html Normal file
View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="module" src="safarihunt.js"></script>
</head>
</html>

2
src/safarihunt.js Normal file
View File

@@ -0,0 +1,2 @@
console.log("test")
// document.body.style.border = "5px solid red";

79
src/sidebar.html Normal file
View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="module" src="safarihunt.js"></script>
<style>
body {
font-family: "Segoe UI", "Lucida Grande", Tahoma, sans-serif;
font-size: 100%;
}
#status {
/* avoid an excessively wide status text */
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
max-width: 600px;
}
@keyframes blink {50% { color: transparent }}
.loader__dot { animation: 1s blink infinite }
.loader__dot:nth-child(2) { animation-delay: 250ms }
.loader__dot:nth-child(3) { animation-delay: 500ms }
</style>
</head>
<body>
<button id="download-book-button" type="button" class="btn btn-outline-primary">Download Book</button>
<div class="status">
<p id="error-message" class="bg-warning"></p>
<div class="loader" id="loading" style="display:none;">
<p class="bg-success">
<strong>
Loading book info
<span class="loader__dot">.</span>
<span class="loader__dot">.</span>
<span class="loader__dot">.</span>
</strong>
</p>
</div>
</div>
<div id="book-info" class="container">
<h4 id="book-info-notice"></h4>
<img id="book-cover" src="" class="img-fluid">
<p class="bg-success">
<span><strong>Book Name:</strong></span>
<span id="book-name"></span>
</p>
<p class="bg-success">
<span ><strong>Chapters: </strong></span>
</p>
<ul id="book-chapter-list" class="list-group">
</ul>
</div>
<div id="download-section">
<p id="download-warning" style="display: none;" class="bg-warning"></p>
<button id="download-button" type="button" style="display: none;" class="btn btn-primary">Download above chapters as zip package</button>
<p id="book-name"><strong></strong></p>
<div id="status"></div>
<img id="image-result" hidden>
</div>
<link rel="stylesheet" href="3pty/bootstrap.min.css">
<script src="3pty/bluebird.js"></script>
<script src="3pty/jszip.js"></script>
<script src="epub.js"></script>
<script src="sidebar.js"></script>
<script src="3pty/jquery-3.1.1.slim.min.js"></script>
<script src="3pty/tether.min.js"></script>
<script src="3pty/bootstrap.min.js"></script>
</body>
</html>

304
src/sidebar.js Normal file
View File

@@ -0,0 +1,304 @@
document.body.style.border = "5px solid red";
function onError(error) {
console.error(`Error: ${error}`);
$('#error-message').text(`Error: ${error}`);
$('#error-message').show();
$('#loading').hide();
$('#book-info').hide();
}
function getCurrentTab() {
console.debug("Querying active tab.");
var queryInfo = {
active: true,
currentWindow: true
};
return browser.tabs.query(queryInfo)
.then(function(tabs) {
if (tabs.length == 1){
let url = tabs[0].url;
console.info(`Active URL: ${url}`);
return url;
} else {
console.error(`Expected 1 active tab, received: ${tabs}`);
throw 'Failed to get active tab.';
}
}, onError);
}
function extractBookId(url){
console.debug(`Extracting book id from ${url}`);
// match a url like:
// https://www.safaribooksonline.com/library/view/startup-opportunities-2nd/9781119378181/
// https://www.safaribooksonline.com/library/view/startup-growth-engines/77961SEM00001/
let match = url.match(/\/library\/view\/[^\/]+\/(\w+)\//);
let bookId = match && match[1];
if (bookId) {
console.debug(`Extracted book id: ${bookId}`);
return bookId;
}else{
console.error('Could not extract book id from url, only '
+'domain "www.safaribooksonline.com“ is supported.');
throw 'Failed to extract book id.';
}
}
// class MiniWebServer{
// constructor() {
// this.url = "http://127.0.0.1:8080/test.zip"
// }
// downloadTestZip(){
// console.info(`Downloading ${this.url}`)
// return fetch(this.url, {
// credentials: 'include'
// }).then((res) => {
// console.log(`Getting blob.`)
// return res.blob()
// }).then(JSZip.loadAsync)
// .then((zip) => {
// this.zip = zip
// })
// }
// fetchUrl(url){
// let name = "meta/"+url.replace(/[^a-z0-9]/gi, '_').toLowerCase()
// this.zip.file(name)
// .
// }
// }
class Book{
constructor(book_id, epub) {
this.book_id = book_id;
this.raw_book = {};
this.chapter_list = [];
this.book_files = {}
this.book_info = null
this.book_toc = null
this.book_flattoc = null
}
downloadResource(url){
console.info(`Downloading ${url}`)
return fetch(url, {
credentials: 'include'
}).then((res) => {
console.log(`Downloaded.`)
return res;
}, onError)
}
downloadJson(url){
return this.downloadResource(url)
.then((res) => {
return res.json();
}, onError);
}
downloadBookInfo(){
console.info(`Downloading book info for ${this.book_id}`);
let url = `https://www.safaribooksonline.com/api/v1/book/${this.book_id}/`;
return this.downloadJson(url)
.then((book_info)=>{
this.book_info = book_info;
}, onError);
}
downloadChapterList(){
function helper(book, url){
console.info(`Downloading chapter list ${url}`);
return book.downloadJson(url)
.then((chapter_list)=>{
book.chapter_list
= book.chapter_list.concat(chapter_list.results);
if (chapter_list.next != null){
return helper(book, chapter_list.next);
}
}, onError);
}
return helper(this, this.book_info.chapter_list)
.then(() => {
console.info(`Chapter List Downloaded.`);
}, onError);
}
downloadMetaContent(){
let downloads = []
this.insertBookFile({
url: this.book_info.cover,
file: "cover.img",
context: null,
mime: null
})
downloads.push(
this.downloadJson(this.book_info.toc)
.then((json)=>{
this.book_toc=json
}))
downloads.push(
this.downloadJson(this.book_info.flat_toc)
.then((json)=>{
this.book_flattoc=json
}))
downloads.push(
Promise.map(this.book_info.chapters, (chapter) => {
return this.downloadJson(chapter)
.then((json) => {
return this.extractChapterAssets(json)
})
},{concurrency: 1}))
return Promise.all(downloads)
}
downloadMetaContent(){
let downloads = []
// downloads.push(this.downloadResource(this.book_info.cover))
downloads.push(
this.downloadJson(this.book_info.toc)
.then((json)=>{
this.book_toc=json
}))
downloads.push(
this.downloadJson(this.book_info.flat_toc)
.then((json)=>{
this.book_flattoc=json
}))
downloads.push(
Promise.map(this.book_info.chapters, (chapter) => {
return this.downloadJson(chapter)
.then((json) => {
return this.extractChapterAssets(json)
})
},{concurrency: 1}))
return Promise.all(downloads)
}
extractChapterAssets(json){
if (!json.asset_base_url
|| !json.content){
throw "Missing data."
}
// Html
this.insertBookFile({
url: json.content,
file: json.full_path,
context: null,
mime: null
})
// List of images.
for (let idx in json.images){
this.insertBookFile({
url: json.asset_base_url + json.images[idx],
file: json.images[idx],
context: null,
mime: null
})
}
// List of stylesheets.
for (let idx in json.stylesheets){
this.insertBookFile({
url: json.stylesheets[idx].original_url,
file: json.stylesheets[idx].full_path,
context: null,
mime: null
})
}
}
insertBookFile(obj){
this.book_files[obj.url] = obj
}
}
function renderInfo(book){
$("#book-name").text(book.book_info.title);
$("#book-cover").attr("src", book.book_info.cover);
$('#book-info').show();
}
function renderChapterList(book){
// Add chapters to UI
for (let chapter_idx in book.chapter_list) {
console.log(book.chapter_list[chapter_idx])
let chapter = book.chapter_list[chapter_idx];
var chapter_dom = $("<li></li>")
.addClass("list-group-item")
.html(chapter.title)
.attr("chapterIndex", chapter_idx);
$("#book-chapter-list").append(chapter_dom);
}
$('#loading').hide();
}
function createEpub(book, epub){
for (let url in book.raw_book){
let name = "meta/"+book.raw_book[url].url.replace(/[^a-z0-9]/gi, '_').toLowerCase()
epub.addFile(name, book.raw_book[url].clone().blob())
}
// epub.addFile("book_info.json", JSON.stringify(book.book_info))
// epub.addFile("book_files.json", JSON.stringify(book.book_files))
// epub.addFile("book_toc.json", JSON.stringify(book.book_toc))
// epub.addFile("book_flattoc.json", JSON.stringify(book.book_flattoc))
epub.addFile("book.json", JSON.stringify(book, null, '\t'))
}
function onDownloadBookClicked(){
console.info("Begin book download.");
$('#loading').show();
$('#error-message').hide();
$('#book-info').hide();
getCurrentTab()
.then(extractBookId, onError)
.then((book_id) => {
epub = new EpubWriter();
book = new Book(book_id);
book.downloadBookInfo()
.then(() => { renderInfo(book); })
.then(() => { return book.downloadChapterList(); })
.then(() => { return renderChapterList(book); })
.then(() => { return book.downloadContent(); })
.then(() => { return createEpub(book, epub); })
.then(() => { return epub.generateAsync(); })
.then((file) => {
let filename = "books/" + "test" + ".zip"
// book.filename.replace(/[^a-z0-9]/gi, '_').toLowerCase()
let url = window.URL.createObjectURL(file)
browser.downloads.download({ "filename" : filename, url : url})
})
}, onError);
}
document.addEventListener('DOMContentLoaded', function() {
console.log("Start safari book hunter.");
$('#loading').hide();
$('#error-message').hide();
$('#book-info').hide();
$('#download-book-button').click(() => {
onDownloadBookClicked();
});
$('#deselect-all-button').show()
$('#download-button').show()
$('#download-section').hide();
// let bookInfo = new BookInfo();
})