├── .gitignore ├── src ├── D4SApp.ts ├── D4SBookSettings.ts ├── D4SDwlHandler.ts ├── D4SVersionChecker.ts ├── D4SLog.ts ├── D4SBookProperties.ts ├── D4SAuthHelper.ts └── D4SDownloader.ts ├── tslint.json ├── tsconfig.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | package-lock.json 4 | dist 5 | book 6 | .env 7 | -------------------------------------------------------------------------------- /src/D4SApp.ts: -------------------------------------------------------------------------------- 1 | import { D4SDownlodaer } from "./D4SDownloader"; 2 | 3 | const downloader = new D4SDownlodaer(); 4 | downloader.startDownload(); 5 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-console": false 9 | }, 10 | "rulesDirectory": [] 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "outDir": "dist" 9 | }, 10 | "lib": ["es2015"] 11 | } 12 | -------------------------------------------------------------------------------- /src/D4SBookSettings.ts: -------------------------------------------------------------------------------- 1 | export class D4SBookSettings { 2 | bookUrl: string; 3 | email: string; 4 | password: string; 5 | 6 | constructor(bookUrl: string, email: string, password: string) { 7 | this.bookUrl = bookUrl; 8 | this.email = email; 9 | this.password = password; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/D4SDwlHandler.ts: -------------------------------------------------------------------------------- 1 | import { JSDOM } from "jsdom"; 2 | 3 | export class D4SDwlHandler { 4 | cookies: string = ""; 5 | bookId: string = ""; 6 | bookIndex: string = ""; 7 | bookSize: number[] = []; 8 | bookName: string = ""; 9 | 10 | isDoneDownloading: boolean = false; 11 | 12 | dwlSvgs: JSDOM[] = []; 13 | page: number = 1; 14 | pdfMergeNames: string[] = []; 15 | 16 | isNewVersion: boolean = false; 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "digi4school-pdf", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "dist/app.js", 6 | "scripts": { 7 | "start": "tsc && node dist/D4SApp.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "tslint": "^6.1.3", 14 | "typescript": "^4.0.3" 15 | }, 16 | "dependencies": { 17 | "@types/jsdom": "^16.2.4", 18 | "@types/pdfkit": "^0.10.6", 19 | "@types/request": "^2.48.5", 20 | "axios": ">=0.21.1", 21 | "beautiful-dom": "^1.0.8", 22 | "easy-pdf-merge": "^0.2.5", 23 | "install": "^0.13.0", 24 | "jsdom": "^16.4.0", 25 | "npm": "^6.14.8", 26 | "pdfkit": "^0.11.0", 27 | "request": "^2.88.2", 28 | "svg-to-pdfkit": "^0.1.8" 29 | } 30 | } -------------------------------------------------------------------------------- /src/D4SVersionChecker.ts: -------------------------------------------------------------------------------- 1 | import { JSDOM } from "jsdom"; 2 | import request from "request"; 3 | 4 | export class D4SVersionChecker { 5 | public static checkVersion(bookUrl: string, cookies: string, callback: Function) { 6 | request( 7 | { 8 | url: bookUrl + "1/1.svg", 9 | method: "GET", 10 | headers: { 11 | Cookie: cookies, 12 | }, 13 | }, 14 | async (err, res) => { 15 | if (err) return callback(null); 16 | const html = new JSDOM(res.body); 17 | 18 | const svgTags = html.window.document.getElementsByTagName("svg"); 19 | if (svgTags.length >= 1) { 20 | return callback(false); 21 | } else { 22 | return callback(true); 23 | } 24 | } 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # digi4school-pdf 2 | 3 | Welcome to digi4school-pdf! 4 | This script lets you download your books from digi4school. 5 | 6 | ## Requirements 7 | 8 | 1. [Node.JS & NPM](https://nodejs.org/en/) (test with `node --version` and `npm --version`) 9 | 2. [Java 8+](https://www.java.com/en/download/) (test with `java --version`) 10 | 11 | ## Installation 12 | 13 | 1. Clone this repository using HTTPS or Git. 14 | 2. Run `npm install` in the root directory of the project. 15 | 16 | ## Usage 17 | 18 | To download a book, open a new terminal window in the root directory of the project and execute the script by typing `npm start`. 19 | Now, you have to provide the URL of the book to download as well as your login credentials for digi4school. 20 | 21 | ``` 22 | Paste the URL of your book: https://a.digi4school.at/ebook/0000/ 23 | Username/Email: mail@example.com 24 | Password: ******** 25 | ``` 26 | 27 | You can see the progress of the download in the console and when the PDF-file has been generated, you can use a file explorer to navigate to the subdirectory book/ to find your generated PDF-file. 28 | 29 | ## Disclaimer 30 | 31 | This project is for educational purposes only and it's illegal to download and/or use the generated PDF-files. 32 | 33 | -------------------------------------------------------------------------------- /src/D4SLog.ts: -------------------------------------------------------------------------------- 1 | export class D4SLog { 2 | static welcome() { 3 | console.log("\nWelcome to digi4school-pdf!\n"); 4 | } 5 | 6 | static invalidProperties() { 7 | console.log("Please specify valid login and book URL data.\n"); 8 | } 9 | 10 | static error() { 11 | console.log("\nAn unknown error occured. Please try again later.\n"); 12 | } 13 | 14 | static downloadPage(page: number) { 15 | console.log("\nDownloading page " + page + "..."); 16 | } 17 | 18 | static downloadImage(imageHref: string) { 19 | console.log(" Downloading image " + imageHref + "..."); 20 | } 21 | 22 | static startGeneratingPages(startPage: number, endPage: number) { 23 | console.log(`\nGenerating page ${startPage} - ${endPage}...`); 24 | } 25 | 26 | static generatingPage(page: number) { 27 | console.log(" Generating page " + page + "..."); 28 | } 29 | 30 | static mergingPdfs() { 31 | console.log("\nMerging PDF-files..."); 32 | } 33 | 34 | static mergingPdf(pdf: string) { 35 | console.log(" Merging PDF " + pdf + "..."); 36 | } 37 | 38 | static cleaningProject() { 39 | console.log("\nCleaning project..."); 40 | } 41 | 42 | static cleaningDir(dir: string) { 43 | console.log(" Cleaning " + dir + "..."); 44 | } 45 | 46 | static downloadDone(fileName: string) { 47 | console.log(`\nDone! Saved as: \"${fileName}\"\nEnjoy your book!\n`); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/D4SBookProperties.ts: -------------------------------------------------------------------------------- 1 | import { JSDOM } from "jsdom"; 2 | import request from "request"; 3 | 4 | export class D4SBookProperties { 5 | static getBookProperties(cookies: string, bookUrl: string, callback: Function) { 6 | const splitUrl = bookUrl.split("/"); 7 | 8 | request( 9 | { 10 | url: bookUrl, 11 | method: "GET", 12 | headers: { 13 | Cookie: cookies, 14 | }, 15 | }, 16 | async (err, res) => { 17 | if (err) return callback(null); 18 | const html = new JSDOM(res.body); 19 | 20 | const metaTags = html.window.document.getElementsByTagName("meta"); 21 | let bookName = ""; 22 | for (var i = 0; i < metaTags.length; i++) { 23 | if (metaTags.item(i).getAttribute("name") == "title") { 24 | const unescapedName: string = metaTags.item(i).getAttribute("content"); 25 | const splitName: string[] = unescapedName.split("/"); 26 | if (splitName.length > 1) { 27 | splitName.forEach((namePart) => { 28 | bookName += namePart + "-"; 29 | }); 30 | } else { 31 | bookName = splitName[0]; 32 | } 33 | } 34 | } 35 | 36 | const scriptTag = html.window.document.getElementsByTagName("script")[0].innerHTML; 37 | const splitScript = scriptTag.split("[[")[1]; 38 | const splitScriptToValues = splitScript.split("]")[0]; 39 | const splitToSize = splitScriptToValues.split(","); 40 | const bookSize: number[] = [Number(splitToSize[0]), Number(splitToSize[1])]; 41 | 42 | return callback(splitUrl[4], splitUrl[5], bookSize, bookName); 43 | } 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/D4SAuthHelper.ts: -------------------------------------------------------------------------------- 1 | import { JSDOM } from "jsdom"; 2 | import request from "request"; 3 | 4 | export class D4SAuthHelper { 5 | static getCookies(email: string, password: string, bookUrl: string, callback: Function) { 6 | request( 7 | { 8 | url: "https://digi4school.at/br/xhr/login", 9 | method: "POST", 10 | formData: { 11 | email: email, 12 | password: password, 13 | }, 14 | }, 15 | async (err, res) => { 16 | if (err || res.body != "OK") return callback(null); 17 | const setCookies = res.headers["set-cookie"]; 18 | 19 | const sessionCookie = setCookies[0].split(";")[0] + ";"; 20 | const digi4sCookie = setCookies[1].split(";")[0] + ";"; 21 | 22 | request( 23 | { 24 | url: bookUrl, 25 | method: "GET", 26 | headers: { 27 | Cookie: sessionCookie + " " + digi4sCookie, 28 | }, 29 | }, 30 | async (err, res) => { 31 | if (err) return callback(null); 32 | request( 33 | { 34 | url: "https://kat.digi4school.at/lti", 35 | method: "POST", 36 | headers: { 37 | Cookie: sessionCookie + " " + digi4sCookie, 38 | }, 39 | formData: D4SAuthHelper.getFormData(res.body), 40 | }, 41 | async (err, res) => { 42 | if (err) return callback(null); 43 | 44 | request( 45 | { 46 | url: "https://a.digi4school.at/lti", 47 | method: "POST", 48 | headers: { 49 | Cookie: sessionCookie + " " + digi4sCookie, 50 | }, 51 | formData: D4SAuthHelper.getFormData(res.body), 52 | }, 53 | async (err, res) => { 54 | if (err) return callback(null); 55 | const cookies = res.headers["set-cookie"]; 56 | cookies.forEach((cookie) => { 57 | if (cookie.split("=")[0] == "digi4b") { 58 | const digi4bCookie = cookie.split(";")[0] + ";"; 59 | const cookies = sessionCookie + " " + digi4sCookie + " " + digi4bCookie; 60 | callback(cookies); 61 | } 62 | }); 63 | } 64 | ); 65 | } 66 | ); 67 | } 68 | ); 69 | } 70 | ); 71 | } 72 | 73 | static getFormData(html: string) { 74 | let formData = {}; 75 | const form = new JSDOM(html); 76 | const inputArray = form.window.document.getElementsByTagName("input"); 77 | for (var i = 0; i < inputArray.length; i++) { 78 | const inputField = inputArray.item(i); 79 | formData[inputField.getAttribute("name")] = inputField.getAttribute("value"); 80 | } 81 | return formData; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/D4SDownloader.ts: -------------------------------------------------------------------------------- 1 | import BeautifulDom from "beautiful-dom"; 2 | import request from "request"; 3 | import PDFDocument from "pdfkit"; 4 | import * as fs from "fs"; 5 | import SVGtoPDF from "svg-to-pdfkit"; 6 | import { JSDOM } from "jsdom"; 7 | import Axios from "axios"; 8 | import { D4SLog } from "./D4SLog"; 9 | import { D4SDwlHandler } from "./D4SDwlHandler"; 10 | import { D4SBookSettings } from "./D4SBookSettings"; 11 | import merge from "easy-pdf-merge"; 12 | import { D4SBookProperties } from "./D4SBookProperties"; 13 | import readline from "readline"; 14 | import { D4SAuthHelper } from "./D4SAuthHelper"; 15 | import { D4SVersionChecker } from "./D4SVersionChecker"; 16 | 17 | export class D4SDownlodaer { 18 | bookSettings: D4SBookSettings; 19 | dwlHandler: D4SDwlHandler = new D4SDwlHandler(); 20 | 21 | async startDownload() { 22 | D4SLog.welcome(); 23 | 24 | const rl = readline.createInterface({ 25 | input: process.stdin, 26 | output: process.stdout, 27 | }); 28 | 29 | rl.question("Paste the URL of your book: ", (bookUrl) => { 30 | rl.question("Username/Email: ", (email) => { 31 | rl.question("Password: ", (password) => { 32 | rl.write("\n"); 33 | this.bookSettings = new D4SBookSettings(bookUrl, email, password); 34 | rl.close(); 35 | this.download(); 36 | }); 37 | }); 38 | }); 39 | } 40 | 41 | async download() { 42 | if ( 43 | this.bookSettings.bookUrl.length < 30 || 44 | this.bookSettings.email.length <= 0 || 45 | this.bookSettings.password.length <= 0 46 | ) { 47 | return D4SLog.invalidProperties(); 48 | } 49 | 50 | D4SAuthHelper.getCookies( 51 | this.bookSettings.email, 52 | this.bookSettings.password, 53 | this.bookSettings.bookUrl, 54 | (cookies) => { 55 | if (!cookies) return D4SLog.invalidProperties(); 56 | this.dwlHandler.cookies = cookies; 57 | 58 | D4SBookProperties.getBookProperties( 59 | this.dwlHandler.cookies, 60 | this.bookSettings.bookUrl, 61 | (bookId: string, bookIndex: string, bookSize: number[], bookName: string) => { 62 | this.dwlHandler.bookId = bookId; 63 | this.dwlHandler.bookIndex = bookIndex; 64 | this.dwlHandler.bookSize = bookSize; 65 | this.dwlHandler.bookName = bookName; 66 | 67 | let bookUrl: string = ""; 68 | if (this.dwlHandler.bookIndex) { 69 | if (this.dwlHandler.bookIndex.length != 0) { 70 | bookUrl = 71 | "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/" + this.dwlHandler.bookIndex + "/"; 72 | } else { 73 | bookUrl = "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/"; 74 | } 75 | } else { 76 | bookUrl = "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/"; 77 | } 78 | 79 | D4SVersionChecker.checkVersion(bookUrl, this.dwlHandler.cookies, (isNewVersion: boolean) => { 80 | this.dwlHandler.isNewVersion = isNewVersion; 81 | 82 | this.dwlFsSetup(); 83 | this.dwlPages(false); 84 | }); 85 | } 86 | ); 87 | } 88 | ); 89 | } 90 | 91 | dwlPages(checked: boolean) { 92 | D4SLog.downloadPage(this.dwlHandler.page); 93 | let dwlUrl: string; 94 | 95 | if (this.dwlHandler.isNewVersion) { 96 | if (this.dwlHandler.bookIndex) { 97 | if (this.dwlHandler.bookIndex.length != 0) { 98 | dwlUrl = 99 | "https://a.digi4school.at/ebook/" + 100 | this.dwlHandler.bookId + 101 | "/" + 102 | this.dwlHandler.bookIndex + 103 | "/" + 104 | this.dwlHandler.page + 105 | ".svg"; 106 | } else { 107 | dwlUrl = "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/" + this.dwlHandler.page + ".svg"; 108 | } 109 | } else { 110 | dwlUrl = "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/" + this.dwlHandler.page + ".svg"; 111 | } 112 | } else { 113 | if (this.dwlHandler.bookIndex) { 114 | if (this.dwlHandler.bookIndex.length != 0) { 115 | dwlUrl = 116 | "https://a.digi4school.at/ebook/" + 117 | this.dwlHandler.bookId + 118 | "/" + 119 | this.dwlHandler.bookIndex + 120 | "/" + 121 | this.dwlHandler.page + 122 | "/" + 123 | this.dwlHandler.page + 124 | ".svg"; 125 | } else { 126 | dwlUrl = 127 | "https://a.digi4school.at/ebook/" + 128 | this.dwlHandler.bookId + 129 | "/" + 130 | this.dwlHandler.page + 131 | "/" + 132 | this.dwlHandler.page + 133 | ".svg"; 134 | } 135 | } else { 136 | dwlUrl = 137 | "https://a.digi4school.at/ebook/" + 138 | this.dwlHandler.bookId + 139 | "/" + 140 | this.dwlHandler.page + 141 | "/" + 142 | this.dwlHandler.page + 143 | ".svg"; 144 | } 145 | } 146 | 147 | request( 148 | { 149 | url: dwlUrl, 150 | method: "GET", 151 | headers: { 152 | Cookie: this.dwlHandler.cookies, 153 | }, 154 | }, 155 | async (err, res) => { 156 | if (err) D4SLog.error(); 157 | const html = new BeautifulDom(res.body); 158 | if (html.getElementsByTagName("svg").length <= 0) { 159 | this.dwlHandler.isDoneDownloading = true; 160 | this.dwlDone(this.dwlHandler.dwlSvgs); 161 | return; 162 | } 163 | if (this.dwlHandler.page % 50 == 0 && !checked) { 164 | this.dwlDone(this.dwlHandler.dwlSvgs); 165 | } else { 166 | const svg: JSDOM = await this.dwlImages(html, this.dwlHandler.page); 167 | this.dwlHandler.dwlSvgs.push(svg); 168 | this.dwlHandler.page++; 169 | this.dwlPages(false); 170 | } 171 | } 172 | ); 173 | } 174 | 175 | async dwlImages(html: BeautifulDom, page: number) { 176 | const dwlSvg: string = html.getElementsByTagName("svg")[0].outerHTML.toString(); 177 | let svg = new JSDOM(dwlSvg); 178 | 179 | let imageNodes = svg.window.document.getElementsByTagName("image"); 180 | for (var i = 0; i < imageNodes.length; i++) { 181 | const ogHref: string = imageNodes.item(i).getAttribute("xlink:href"); 182 | let imageUrl: string; 183 | if (this.dwlHandler.isNewVersion) { 184 | if (this.dwlHandler.bookIndex) { 185 | imageUrl = 186 | "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/" + this.dwlHandler.bookIndex + "/" + ogHref; 187 | } else { 188 | imageUrl = "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/" + ogHref; 189 | } 190 | } else { 191 | if (this.dwlHandler.bookIndex) { 192 | imageUrl = 193 | "https://a.digi4school.at/ebook/" + 194 | this.dwlHandler.bookId + 195 | "/" + 196 | this.dwlHandler.bookIndex + 197 | "/" + 198 | page + 199 | "/" + 200 | ogHref; 201 | } else { 202 | imageUrl = "https://a.digi4school.at/ebook/" + this.dwlHandler.bookId + "/" + page + "/" + ogHref; 203 | } 204 | } 205 | 206 | let imageFileSystemPath: string = ""; 207 | if (this.dwlHandler.isNewVersion) { 208 | imageFileSystemPath = "book/book_images/" + this.dwlHandler.bookId + "/" + ogHref; 209 | } else { 210 | imageFileSystemPath = "book/book_images/" + this.dwlHandler.bookId + "/" + page + "/" + ogHref; 211 | } 212 | 213 | D4SLog.downloadImage(ogHref); 214 | fs.mkdirSync("book/book_images/" + this.dwlHandler.bookId + "/" + page + "/img/", { recursive: true }); 215 | fs.mkdirSync("book/book_images/" + this.dwlHandler.bookId + "/" + page + "/shade/", { recursive: true }); 216 | await this.dwlImage(imageUrl, imageFileSystemPath); 217 | 218 | imageNodes.item(i).setAttribute("xlink:href", imageFileSystemPath); 219 | } 220 | 221 | return svg; 222 | } 223 | 224 | async dwlImage(imageUrl: string, fileSystemPath: string) { 225 | const writer = fs.createWriteStream(fileSystemPath); 226 | 227 | const response = await Axios(imageUrl, { 228 | method: "GET", 229 | responseType: "stream", 230 | headers: { 231 | Cookie: this.dwlHandler.cookies, 232 | }, 233 | }); 234 | 235 | response.data.pipe(writer); 236 | 237 | return new Promise((resolve, reject) => { 238 | writer.on("finish", resolve); 239 | writer.on("error", reject); 240 | }); 241 | } 242 | 243 | dwlFsSetup() { 244 | if (!fs.existsSync("book/")) fs.mkdirSync("book"); 245 | if (fs.existsSync("book/book_images/")) fs.rmdirSync("book/book_images/", { recursive: true }); 246 | if (fs.existsSync("book/book_pdfs/")) fs.rmdirSync("book/book_pdfs/", { recursive: true }); 247 | fs.mkdirSync("book/book_images/" + this.dwlHandler.bookId, { recursive: true }); 248 | fs.mkdirSync("book/book_pdfs/" + this.dwlHandler.bookId, { recursive: true }); 249 | } 250 | 251 | dwlDone(svgs: JSDOM[]) { 252 | D4SLog.startGeneratingPages(this.dwlHandler.page - 49, this.dwlHandler.page - 1); 253 | 254 | const doc = new PDFDocument({ 255 | size: this.dwlHandler.bookSize, 256 | }); 257 | 258 | const pdfFilePath: string = `book/book_pdfs/${this.dwlHandler.bookId}/${this.dwlHandler.bookId}_${ 259 | this.dwlHandler.pdfMergeNames.length + 1 260 | }.pdf`; 261 | doc.pipe(fs.createWriteStream(pdfFilePath)); 262 | this.dwlHandler.pdfMergeNames.push(pdfFilePath); 263 | 264 | for (var i = 0; i < svgs.length; i++) { 265 | D4SLog.generatingPage(this.dwlHandler.page - 50 + (i + 1)); 266 | let svg = svgs[i].window.document.getElementsByTagName("svg")[0]; 267 | 268 | svg.setAttribute("viewBox", `0 0 ${this.dwlHandler.bookSize[0]} ${this.dwlHandler.bookSize[1]}`); 269 | try { 270 | SVGtoPDF(doc, svg.outerHTML, 0, 0); 271 | } catch {} 272 | 273 | if (i + 1 != svgs.length) doc.addPage(); 274 | } 275 | doc.end(); 276 | 277 | if (!this.dwlHandler.isDoneDownloading) { 278 | this.dwlHandler.dwlSvgs = []; 279 | this.dwlPages(true); 280 | } else { 281 | this.mergePdfs(); 282 | } 283 | } 284 | 285 | mergePdfs() { 286 | D4SLog.mergingPdfs(); 287 | 288 | const pdfFileName: string = this.dwlHandler.bookName ? this.dwlHandler.bookName + ".pdf" : "book.pdf"; 289 | const pdtFilePath: string = "book/" + pdfFileName; 290 | 291 | this.dwlHandler.pdfMergeNames.forEach((pdf) => { 292 | D4SLog.mergingPdf(pdf); 293 | }); 294 | 295 | merge(this.dwlHandler.pdfMergeNames, pdtFilePath, (err) => { 296 | if (err) console.log(err); 297 | this.clearProject(); 298 | }); 299 | } 300 | 301 | clearProject() { 302 | D4SLog.cleaningProject(); 303 | 304 | D4SLog.cleaningDir("book/book_pdfs"); 305 | fs.rmdirSync("book/book_pdfs", { recursive: true }); 306 | 307 | D4SLog.cleaningDir("book/book_images"); 308 | fs.rmdirSync("book/book_images", { recursive: true }); 309 | 310 | if (this.dwlHandler.bookName) { 311 | D4SLog.downloadDone(this.dwlHandler.bookName + ".pdf"); 312 | } else { 313 | D4SLog.downloadDone("book.pdf"); 314 | } 315 | } 316 | } 317 | --------------------------------------------------------------------------------