├── .prettierrc.json ├── .vscode ├── settings.json └── extensions.json ├── .gitignore ├── documentation └── img │ └── examples-ss-1.png ├── examples ├── web │ ├── README.md │ ├── common.js │ ├── common.css │ ├── 06.html │ ├── 02.html │ ├── 01.html │ ├── 03.html │ ├── 04.html │ ├── 05.html │ ├── water-2-light.css │ └── ia.browser.js └── node │ ├── 08-utils.js │ ├── 02-login.js │ ├── 04-metadata-patch.js │ ├── 01-hello.js │ ├── 05-wayback.js │ ├── 03-reviews.js │ ├── 06-favorites.js │ ├── 07-s3.js │ └── common.js ├── LICENSE ├── package.json ├── lib └── cors-proxy.js ├── rollup.config.js ├── README.md └── src └── main.js /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist 4 | # examples/web/ia.browser.js 5 | test-config.json -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode" 4 | ] 5 | } -------------------------------------------------------------------------------- /documentation/img/examples-ss-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchrd2/iajs/HEAD/documentation/img/examples-ss-1.png -------------------------------------------------------------------------------- /examples/web/README.md: -------------------------------------------------------------------------------- 1 | Build the JS first, open 01.html in a web browser to get started. 2 | 3 | To build the JS 4 | 5 | ``` 6 | npm i 7 | npm run build 8 | ``` 9 | -------------------------------------------------------------------------------- /examples/node/08-utils.js: -------------------------------------------------------------------------------- 1 | const { ZipFileAPI } = require("../.."); 2 | 3 | const log = console.log; 4 | 5 | (async () => { 6 | log("Read contents of a zip file."); 7 | log( 8 | await ZipFileAPI.ls( 9 | "goodytwoshoes00newyiala", 10 | "goodytwoshoes00newyiala_jp2.zip" 11 | ) 12 | ); 13 | })(); 14 | -------------------------------------------------------------------------------- /examples/web/common.js: -------------------------------------------------------------------------------- 1 | var codeEl = document.getElementById("code"); 2 | var sourceEl = document.getElementById("sourcecode"); 3 | if (codeEl && sourceEl) { 4 | var exampleCode = sourceEl.innerText.trim(); 5 | document.getElementById("code").value = exampleCode; 6 | codeEl.setAttribute("style", "height:" + codeEl.scrollHeight + "px;"); 7 | document.getElementById("run").onclick = function () { 8 | eval(document.getElementById("code").value); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /examples/node/02-login.js: -------------------------------------------------------------------------------- 1 | const { Auth } = require("../.."); 2 | const readline = require("readline"); 3 | const { getTestAuth } = require("./common"); 4 | const log = console.log; 5 | 6 | (async () => { 7 | let auth; 8 | auth = await getTestAuth(); 9 | 10 | console.log("Auth object:"); 11 | log(auth); 12 | 13 | console.log("Demo creating an auth object from cookies:"); 14 | let authFromCookies = await Auth.fromCookies( 15 | auth.values.cookies["logged-in-sig"].split(";")[0], 16 | auth.values.cookies["logged-in-user"].split(";")[0] 17 | ); 18 | log(authFromCookies); 19 | })(); 20 | -------------------------------------------------------------------------------- /examples/node/04-metadata-patch.js: -------------------------------------------------------------------------------- 1 | const { Auth, MetadataAPI } = require("../.."); 2 | const fetch = require("node-fetch"); 3 | const readline = require("readline"); 4 | const { getTestAuth } = require("./common"); 5 | 6 | const log = console.log; 7 | const enc = encodeURIComponent; 8 | 9 | (async () => { 10 | let auth = await getTestAuth(); 11 | 12 | // Modify the title (note you should change this to an item you own) 13 | const identifier = "iajs-example-reviews"; 14 | const patch = { 15 | op: "add", 16 | path: "/title", 17 | value: `The date is ${new Date().toISOString()}`, 18 | }; 19 | log(await MetadataAPI.patch({ identifier, patch, auth })); 20 | })(); 21 | -------------------------------------------------------------------------------- /examples/node/01-hello.js: -------------------------------------------------------------------------------- 1 | const { GifcitiesAPI, MetadataAPI, RelatedAPI, SearchAPI } = require("../.."); 2 | 3 | const log = console.log; 4 | 5 | (async () => { 6 | const identifier = "example-png"; 7 | const searchTerm = "nintendo"; 8 | 9 | log(await MetadataAPI.get({ identifier, path: "metadata" })); 10 | log(await MetadataAPI.get({ identifier, path: "metadata/identifier" })); 11 | log(await RelatedAPI.get({ identifier })); 12 | let searchResults = await SearchAPI.get({ 13 | q: searchTerm, 14 | fields: ["identifier"], 15 | page: 2, 16 | }); 17 | log( 18 | searchResults["response"]["docs"] 19 | .map((r) => `https://archive.org/details/${r["identifier"]}`) 20 | .join("\n") 21 | ); 22 | log((await GifcitiesAPI.search(searchTerm)).slice(0, 10)); 23 | })(); 24 | -------------------------------------------------------------------------------- /examples/node/05-wayback.js: -------------------------------------------------------------------------------- 1 | const { WaybackAPI, Auth } = require("../.."); 2 | const { getTestAuth, yesno, promptStr } = require("./common"); 3 | const log = console.log; 4 | 5 | (async () => { 6 | // query the available api 7 | log( 8 | await WaybackAPI.available({ 9 | url: "archive.org", 10 | timestamp: "202011050000", 11 | }) 12 | ); 13 | 14 | // query cdx api 15 | log( 16 | await WaybackAPI.cdx({ 17 | url: "archive.org", 18 | matchType: "host", 19 | limit: 2, 20 | }) 21 | ); 22 | 23 | // prompt for user/pass for save page now 24 | let choice = await yesno({ 25 | question: "Would you like to save a page now? (y/n)", 26 | }); 27 | if (choice) { 28 | let auth = await getTestAuth(); 29 | let url = await promptStr("Enter a URL: "); 30 | log(await WaybackAPI.savePageNow({ url, auth })); 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /examples/node/03-reviews.js: -------------------------------------------------------------------------------- 1 | const { Auth, ReviewsAPI } = require("../.."); 2 | const fetch = require("node-fetch"); 3 | const readline = require("readline"); 4 | const { getTestAuth } = require("./common"); 5 | 6 | const log = console.log; 7 | const enc = encodeURIComponent; 8 | 9 | (async () => { 10 | let auth = await getTestAuth(); 11 | 12 | const identifier = "iajs-example-reviews"; 13 | const title = "Hello, World!"; 14 | const body = `The date is ${new Date().toISOString()}`; 15 | const stars = 5; 16 | 17 | log(`Adding a review to ${identifier} from user ${auth.values.itemname}.`); 18 | await ReviewsAPI.add({ identifier, title, body, stars, auth }); 19 | log(`https://archive.org/details/${identifier}`); 20 | 21 | log("Pausing for a second to let it process."); 22 | await new Promise((resolve) => setTimeout(resolve, 1000)); 23 | 24 | log(`Listing reviews of the item ${auth.values.itemname}.`); 25 | log(await ReviewsAPI.get({ identifier })); 26 | })(); 27 | -------------------------------------------------------------------------------- /examples/web/common.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | body { 6 | max-width: initial; 7 | } 8 | #code { 9 | font-size: 16px; 10 | font-family: monospace; 11 | color: #333; 12 | /* word-wrap: none; */ 13 | white-space: pre; 14 | overflow-wrap: normal; 15 | overflow-x: scroll; 16 | } 17 | #out { 18 | background: #eee; 19 | padding: 10px; 20 | } 21 | #next { 22 | float: right; 23 | } 24 | pre { 25 | max-width: 1000px; 26 | font-size: 1.2rem; 27 | padding: 15px; 28 | } 29 | .shell { 30 | height: 100%; 31 | display: flex; 32 | flex-direction: column; 33 | } 34 | .shell main { 35 | height: 100%; 36 | flex: auto; 37 | display: flex; 38 | flex-direction: row; 39 | } 40 | .shell main section { 41 | flex: auto; 42 | /* height: 100%; */ 43 | width: 50%; 44 | overflow: auto; 45 | } 46 | .shell main section + section { 47 | margin-left: 10px; 48 | } 49 | .shell header { 50 | display: flex; 51 | flex-direction: row; 52 | justify-items: space-between; 53 | align-content: center; 54 | padding-bottom: 20px; 55 | } 56 | .shell header div.left { 57 | flex: auto; 58 | } 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Richard Caceres 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/node/06-favorites.js: -------------------------------------------------------------------------------- 1 | const { FavoritesAPI, Auth } = require("../.."); 2 | const { getTestAuth, yesno, promptStr } = require("./common"); 3 | const log = console.log; 4 | 5 | (async () => { 6 | log(await FavoritesAPI.get({ screenname: "r2_t5" })); 7 | 8 | let auth = await getTestAuth(); 9 | log(await FavoritesAPI.get({ auth })); 10 | 11 | const identifier = "iajs-example-reviews"; 12 | if ( 13 | await yesno({ 14 | question: `Would you like to favorite the item: ${identifier} (y/n)?`, 15 | }) 16 | ) { 17 | await FavoritesAPI.add({ identifier, auth }); 18 | log("Pausing for a second to let it process."); 19 | await new Promise((resolve) => setTimeout(resolve, 1000)); 20 | log("Listing your favorites:"); 21 | log(await FavoritesAPI.get({ auth })); 22 | } 23 | 24 | if ( 25 | await yesno({ 26 | question: `Would you like to remove your favorite for: ${identifier} (y/n)?`, 27 | }) 28 | ) { 29 | await FavoritesAPI.remove({ identifier, auth }); 30 | log("Pausing for a second to let it process."); 31 | await new Promise((resolve) => setTimeout(resolve, 1000)); 32 | } 33 | log("Listing your favorites:"); 34 | log(await FavoritesAPI.get({ auth })); 35 | })(); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iajs", 3 | "version": "0.2.0", 4 | "description": "Internet Archive JavaScript Client", 5 | "main": "dist/ia.cjs.js", 6 | "module": "dist/ia.esm.js", 7 | "browser": "dist/ia.browser.js", 8 | "scripts": { 9 | "test": "node ./examples/web/test.js", 10 | "dev": "cd examples/web; npm run dev", 11 | "build": "rollup -c", 12 | "watch": "rollup -c -w", 13 | "pretest": "npm run build" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/rchrd2/iajs.git" 18 | }, 19 | "keywords": [ 20 | "Internet", 21 | "Archive", 22 | "Client" 23 | ], 24 | "author": "Richard Caceres", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/rchrd2/iajs/issues" 28 | }, 29 | "homepage": "https://github.com/rchrd2/iajs#readme", 30 | "dependencies": { 31 | "fetch-jsonp": "^1.1.3", 32 | "node-fetch": "^2.6.1", 33 | "xmldom": "^0.4.0" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.12.3", 37 | "@babel/preset-env": "^7.12.1", 38 | "@rollup/plugin-babel": "^5.2.1", 39 | "@rollup/plugin-commonjs": "^11.0.1", 40 | "@rollup/plugin-node-resolve": "^7.0.0", 41 | "babel-plugin-async-to-promises": "^1.0.5", 42 | "prettier": "2.1.2", 43 | "rollup": "^1.29.0" 44 | }, 45 | "files": [ 46 | "dist" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /examples/node/07-s3.js: -------------------------------------------------------------------------------- 1 | const { S3API, Auth } = require("../.."); 2 | const { getTestAuth, yesno, promptStr } = require("./common"); 3 | const log = console.log; 4 | 5 | (async () => { 6 | let auth = await getTestAuth(); 7 | 8 | // You can change this identifier to a new unclaimed item 9 | const identifier = "iajs-example-s3"; 10 | log(`Listing s3 contents of identifier: ${identifier}...`); 11 | log(await S3API.ls({ identifier })); 12 | 13 | if ( 14 | await yesno({ 15 | question: `Would you like to upload to item: ${identifier} (y/n)?`, 16 | }) 17 | ) { 18 | let key = await promptStr("filename: "); 19 | const body = `The date is ${new Date().toISOString()}`; 20 | const metadata = { title: "s3 PUT test item" }; 21 | log( 22 | await S3API.upload({ 23 | identifier, 24 | key, 25 | body, 26 | metadata, 27 | autocreate: true, 28 | auth, 29 | }) 30 | ); 31 | } 32 | 33 | const newIdentifier = `${identifier}-${Date.now()}`; 34 | if ( 35 | await yesno({ 36 | question: `Would you like to create a new empty test item?: ${newIdentifier} (y/n)?`, 37 | }) 38 | ) { 39 | const metadata = { 40 | title: "S3 PUT empty test item", 41 | description: `The date is ${new Date().toISOString()}`, 42 | }; 43 | log( 44 | await S3API.createEmptyItem({ 45 | identifier: newIdentifier, 46 | metadata, 47 | testItem: true, 48 | auth, 49 | wait: false, 50 | }) 51 | ); 52 | log(`https://archive.org/details/${newIdentifier}`); 53 | log(`https://s3.us.archive.org/${newIdentifier}`); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /lib/cors-proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * General purpose CORS proxy. 3 | * forked from: https://github.com/chebyrash/cors 4 | * Can be deployed to Cloudflare workers 5 | */ 6 | addEventListener("fetch", (event) => { 7 | event.respondWith(handleRequest(event.request)); 8 | }); 9 | 10 | async function handleRequest(request) { 11 | try { 12 | const url = new URL(request.url); 13 | 14 | if (url.pathname === "/") { 15 | return new Response(`{"usage": "${url.origin}/"}`); 16 | } 17 | 18 | function addResponseHeaders(response) { 19 | response.headers.set("Access-Control-Allow-Origin", "*"); 20 | response.headers.set("Access-Control-Allow-Credentials", "true"); 21 | response.headers.set( 22 | "Access-Control-Allow-Methods", 23 | "GET,HEAD,OPTIONS,POST,PUT" 24 | ); 25 | response.headers.set( 26 | "Access-Control-Allow-Headers", 27 | "Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Cookie-Cors" 28 | ); 29 | } 30 | 31 | let response; 32 | if (request.method == "OPTIONS") { 33 | response = new Response(""); 34 | addResponseHeaders(response); 35 | return response; 36 | } 37 | 38 | const modifiedHeaders = { ...request.headers }; 39 | if (request.headers.get("X-Cookie-Cors")) { 40 | modifiedHeaders["Cookie"] = request.headers.get("X-Cookie-Cors"); 41 | delete modifiedHeaders["X-Cookie-Cors"]; 42 | } 43 | 44 | response = await fetch(request.url.slice(url.origin.length + 1), { 45 | method: request.method, 46 | headers: modifiedHeaders, 47 | redirect: "follow", 48 | body: request.body, 49 | }); 50 | 51 | response = new Response(response.body, response); 52 | addResponseHeaders(response); 53 | return response; 54 | } catch (e) { 55 | return new Response(e.stack || e, { status: 500 }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/web/06.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 |
10 |

11 | Wayback Machine (https://web.archive.org) API examples.
12 | Check if a webpage is archived and retrieve a list of archived versions. 13 | < Previous example 14 |

15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 29 |
30 | 31 | 32 | 52 | 53 | 54 | 55 | 62 | 63 | -------------------------------------------------------------------------------- /examples/web/02.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |

9 | Get gifs from gifcities.org. 10 |
Note the code is editable and can be run with the "Run" button 11 | < Previous example | Next example > 12 |

13 |
14 |
15 |
16 |
17 | 18 | 19 |
20 |
21 |
22 |
Loading...
23 |
24 |
25 |
26 | 29 |
30 | 31 | 32 | 44 | 45 | 46 | 47 | 58 | 59 | -------------------------------------------------------------------------------- /examples/web/01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |

7 | Fetch data from the Internet Archive Item Metadata API. 8 |
Note the code is editable and can be run with the "Run" button 9 | Next example > 10 |

11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 |
Loading...
21 |
22 |
23 |
24 | 27 |
28 | 29 | 30 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import babel, { getBabelOutputPlugin } from "@rollup/plugin-babel"; 4 | import pkg from "./package.json"; 5 | 6 | function browser(name) { 7 | return { 8 | input: "src/main.js", 9 | external: ["node-fetch", "xmldom"], 10 | output: [ 11 | { 12 | name: "ia", 13 | file: name, 14 | format: "umd", 15 | plugins: [ 16 | getBabelOutputPlugin({ 17 | presets: [["@babel/env", { modules: "umd" }]], 18 | allowAllFormats: true, 19 | }), 20 | ], 21 | }, 22 | ], 23 | plugins: [ 24 | resolve(), 25 | commonjs(), 26 | babel({ 27 | babelHelpers: "bundled", 28 | plugins: ["babel-plugin-async-to-promises"], 29 | }), 30 | ], 31 | }; 32 | } 33 | 34 | function modern(name) { 35 | return { 36 | input: "src/main.js", 37 | external: ["node-fetch", "xmldom"], 38 | output: [ 39 | { 40 | name: "ia", 41 | file: name, 42 | format: "umd", 43 | plugins: [ 44 | getBabelOutputPlugin({ 45 | presets: [ 46 | [ 47 | "@babel/env", 48 | { 49 | modules: "umd", 50 | targets: [ 51 | "last 2 Chrome versions", 52 | "last 2 iOS major versions", 53 | ], 54 | }, 55 | ], 56 | ], 57 | allowAllFormats: true, 58 | }), 59 | ], 60 | }, 61 | ], 62 | plugins: [resolve(), commonjs()], 63 | }; 64 | } 65 | 66 | export default [ 67 | browser(pkg.browser), 68 | browser("examples/web/ia.browser.js"), 69 | modern("dist/ia.modern.js"), 70 | { 71 | input: "src/main.js", 72 | external: [], 73 | output: [ 74 | { file: pkg.main, format: "cjs" }, 75 | { file: pkg.module, format: "es" }, 76 | ], 77 | plugins: [commonjs()], 78 | }, 79 | ]; 80 | -------------------------------------------------------------------------------- /examples/web/03.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |

7 | Search the Internet Archive Collections
8 | Note choice of fields argument and lucene syntax. See Advanced Search. 9 | < Previous example | Next example > 10 |

11 |
12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 |
Loading...
21 |
22 |
23 |
24 | 27 |
28 | 29 | 30 | 46 | 47 | 48 | 49 | 60 | 61 | -------------------------------------------------------------------------------- /examples/web/04.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |

7 | Login to the Internet Archive 8 | < Previous example | Next example > 9 |

10 |
11 |
12 |
13 |
14 |
15 |

Type in email and password to login

16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 |
24 |
25 |
26 |
27 |
28 | 31 |
32 | 33 | 34 | 63 | 64 | 65 | 66 | 68 | 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iajs 2 | 3 | **Internet Archive JavaScript Client which supports reading and writing data in NodeJS and the Browser** 4 | 5 | The Internet Archive is a non-profit open platform for archiving the world's free websites, books, movies, software, music, and more. 6 | 7 | This JavaScript library enables reading and writing to the Internet Archive APIs in NodeJS **and** in the browser. To learn more about the Internet Archive visit . 8 | 9 | It's lightweight with very little dependencies. 10 | 11 | The major APIs are documented here , but this library supports additional APIs to enable more functionality. It does not abstract much from the APIs, but rather aggregates access to them all in a single multi-purpose library. 12 | 13 | This library contains enough functionality to create powerful experiments and new experiences using the Internet Archive's platform and data. 14 | 15 | ## Installation 16 | 17 | Through npm 18 | ``` 19 | npm i iajs 20 | ``` 21 | 22 | Load from CDN in browser 23 | ``` 24 | 25 | ``` 26 | 27 | ## Live demos 28 | 29 | 30 | 31 | 32 | ## Usage Examples 33 | 34 | ``` 35 | 36 | 48 | ``` 49 | 50 | ## Running NodeJS examples 51 | 52 | ``` 53 | npm i 54 | node examples/node/01-hello.js 55 | 56 | # this will ask you to sign in and create a login config file for other examples 57 | node examples/node/02-login.js 58 | 59 | node examples/node/03-reviews.js 60 | 61 | # and so on... 62 | ``` 63 | 64 | ## Planned features 65 | 66 | - ✅ Read item metadata and list of files (Metadata API) 67 | - ✅ Update item metadata (Metadata API) 68 | - ✅ Search (Search API) 69 | - ✅ Search gifcities.org (GifCities API) 70 | - ✅ Query related item API (Related Items API) 71 | - ✅ Sign in with user/pass (Xauthn API) 72 | - ✅ Sign in with s3 tokens 73 | - ✅ Sign in from archive.org session cookies 74 | - ✅ Add reviews (Reviews API) 75 | - ✅ Add page to Wayback (Save Page Now API) 76 | - ✅ Query the Wayback Machine (CDX and Available APIs) 77 | - ✅ Add/remove/list favorites (bookmarks.php API) 78 | - ✅ Create items (S3 API) 79 | - ✅ Upload item files (S3 API) 80 | - OpenLibrary.org APIs 81 | - BookReaderJSIA aka manifest API 82 | - Book IIIF API 83 | - TV 84 | - Radio 85 | - List reviews by user 86 | - Generate embed codes for books/videos/music files in item 87 | - Include a JSON diff library 88 | - more tbd 89 | 90 | 91 | ## Misc 92 | 93 | Note: 94 | I wanted to build this while I worked at Internet Archive, but did not have the bandwidth. Now I'm working on this in my free time. 95 | 96 | See Also: 97 | 98 | - Official Internet Archive Python Client - https://github.com/jjjake/internetarchive 99 | 100 | --- 101 | 102 | Screenshot of web usage example 103 | 104 | ![screenshot](./documentation/img/examples-ss-1.png) 105 | -------------------------------------------------------------------------------- /examples/node/common.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const readline = require("readline"); 3 | const path = require("path"); 4 | const testConfigPath = path.resolve(__dirname, "../../test-config.json"); 5 | const { Auth } = require("../.."); 6 | 7 | async function promptStr(prompt) { 8 | return new Promise((resolve, reject) => { 9 | const rl = readline.createInterface({ 10 | input: process.stdin, 11 | output: process.stdout, 12 | }); 13 | rl.question(prompt, (answer) => { 14 | resolve(answer); 15 | rl.close(); 16 | }); 17 | }); 18 | } 19 | 20 | async function promptPw(prompt) { 21 | return new Promise((resolve, reject) => { 22 | const rl = readline.createInterface({ 23 | input: process.stdin, 24 | output: process.stdout, 25 | }); 26 | rl.stdoutMuted = true; 27 | 28 | rl.question(prompt, (answer) => { 29 | resolve(answer); 30 | rl.close(); 31 | }); 32 | 33 | rl._writeToOutput = function _writeToOutput(stringToWrite) { 34 | if (rl.stdoutMuted) rl.output.write("*"); 35 | else rl.output.write(stringToWrite); 36 | }; 37 | }); 38 | } 39 | 40 | async function getTestAuth() { 41 | console.log(`Reading: ${testConfigPath}`); 42 | 43 | if (fs.existsSync(testConfigPath)) { 44 | let fileAuth = require(testConfigPath); 45 | if (fileAuth.success) { 46 | return fileAuth; 47 | } 48 | } 49 | 50 | console.log("Test config not found."); 51 | console.log("Log in with a test account to create one"); 52 | let email = await promptStr("email: "); 53 | let password = await promptPw("password (will not be saved): "); 54 | let auth = await Auth.login(email, password); 55 | fs.writeFileSync(testConfigPath, JSON.stringify(auth, null, 2)); 56 | console.log(`Wrote config to ${testConfigPath}`); 57 | return auth; 58 | } 59 | 60 | // https://github.com/tcql/node-yesno/blob/master/yesno.js 61 | 62 | function defaultInvalidHandler({ 63 | question, 64 | defaultValue, 65 | yesValues, 66 | noValues, 67 | }) { 68 | process.stdout.write("\nInvalid Response.\n"); 69 | process.stdout.write("Answer either yes : (" + yesValues.join(", ") + ") \n"); 70 | process.stdout.write("Or no: (" + noValues.join(", ") + ") \n\n"); 71 | } 72 | 73 | async function yesno({ question, defaultValue, yesValues, noValues, invalid }) { 74 | if (!invalid || typeof invalid !== "function") 75 | invalid = defaultInvalidHandler; 76 | 77 | const options = { 78 | yes: ["yes", "y"], 79 | no: ["no", "n"], 80 | }; 81 | yesValues = (yesValues || options.yes).map((v) => v.toLowerCase()); 82 | noValues = (noValues || options.no).map((v) => v.toLowerCase()); 83 | 84 | const rl = readline.createInterface({ 85 | input: process.stdin, 86 | output: process.stdout, 87 | }); 88 | 89 | return new Promise(function (resolve, reject) { 90 | rl.question(question + " ", async function (answer) { 91 | rl.close(); 92 | const cleaned = answer.trim().toLowerCase(); 93 | if (cleaned == "" && defaultValue != null) return resolve(defaultValue); 94 | if (yesValues.indexOf(cleaned) >= 0) return resolve(true); 95 | if (noValues.indexOf(cleaned) >= 0) return resolve(false); 96 | invalid({ question, defaultValue, yesValues, noValues }); 97 | const result = await yesno({ 98 | question, 99 | defaultValue, 100 | yesValues, 101 | noValues, 102 | invalid, 103 | }); 104 | resolve(result); 105 | }); 106 | }); 107 | } 108 | 109 | module.exports = { 110 | getTestAuth, 111 | yesno, 112 | promptStr, 113 | }; 114 | -------------------------------------------------------------------------------- /examples/web/05.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 |
10 |

11 | This advanced example shows how to render an Archive.org Item by making multiple API calls.
12 | The Metadata API provides most of the data, but the RelatedItem API is also used, and the SearchAPI is used to fetch additional fields for the item's collections. And the ViewsAPI is used for the view count.
13 | The embed and images are rendered using a URL pattern of archive.org/embed/{identifier} and archive.org/services/img/{identifier}. 14 | < Previous example | Next example > 15 |

16 |
17 |
18 |
19 |
20 |
21 |
22 |

Metadata

23 |
24 |

Files

25 |
26 |

Collections

27 |
28 |

Similar items

29 | 30 |

Reviews

31 |
32 |
33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |

Source code for this page

43 | 44 | 45 |
46 |
47 | 50 |
51 | 52 | 53 | 186 | 187 | 188 | 189 | 212 | 213 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import fetchJsonp from "fetch-jsonp"; 3 | import { DOMParser } from "xmldom"; 4 | 5 | let CORS_PROXY = "https://iajs-cors.rchrd2.workers.dev"; 6 | 7 | const log = console.log; 8 | const enc = encodeURIComponent; 9 | const paramify = (obj) => new URLSearchParams(obj).toString(); 10 | const str2arr = (v) => (Array.isArray(v) ? v : [v]); 11 | 12 | const isInBrowser = () => { 13 | return !(typeof window === "undefined"); 14 | }; 15 | 16 | const corsWorkAround = (url) => { 17 | if (isInBrowser()) { 18 | return `${CORS_PROXY}/${url}`; 19 | } else { 20 | return url; 21 | } 22 | }; 23 | 24 | const fetchJson = async function (url, options) { 25 | const res = await fetch(url, options); 26 | return await res.json(); 27 | }; 28 | 29 | const authToHeaderS3 = function (auth) { 30 | return auth.values.s3.access && auth.values.s3.secret 31 | ? { 32 | Authorization: `LOW ${auth.values.s3.access}:${auth.values.s3.secret}`, 33 | } 34 | : {}; 35 | }; 36 | const authToHeaderCookies = function (auth) { 37 | if ( 38 | auth.values.cookies["logged-in-sig"] && 39 | auth.values.cookies["logged-in-user"] 40 | ) { 41 | let cookieStr = `logged-in-sig=${auth.values.cookies["logged-in-sig"]};`; 42 | cookieStr += ` logged-in-user=${auth.values.cookies["logged-in-user"]}`; 43 | const headers = { Cookie: cookieStr }; 44 | if (isInBrowser()) { 45 | headers["X-Cookie-Cors"] = cookieStr; 46 | } 47 | return headers; 48 | } else { 49 | return {}; 50 | } 51 | }; 52 | 53 | const newEmptyAuth = function () { 54 | return JSON.parse( 55 | JSON.stringify({ 56 | success: false, 57 | values: { 58 | cookies: { "logged-in-sig": null, "logged-in-user": null }, 59 | email: null, 60 | itemname: null, 61 | s3: { access: null, secret: null }, 62 | screenname: null, 63 | }, 64 | version: 1, 65 | }) 66 | ); 67 | }; 68 | 69 | class Auth { 70 | constructor() { 71 | this.XAUTH_BASE = corsWorkAround("https://archive.org/services/xauthn/"); 72 | } 73 | async login(email, password) { 74 | try { 75 | const fetchOptions = { 76 | method: "POST", 77 | body: `email=${enc(email)}&password=${enc(password)}`, 78 | headers: { 79 | "Content-Type": "application/x-www-form-urlencoded", 80 | }, 81 | }; 82 | const response = await fetch(`${this.XAUTH_BASE}?op=login`, fetchOptions); 83 | const data = await response.json(); 84 | if (!data.success) { 85 | data.values = { ...data.values, ...newEmptyAuth().values }; 86 | } 87 | return data; 88 | } catch (e) { 89 | // TODO figure out syntax for catching error response 90 | return newEmptyAuth(); 91 | } 92 | } 93 | async fromS3(access, secret, newAuth = newEmptyAuth()) { 94 | newAuth.success = 1; 95 | newAuth.values.s3.access = access; 96 | newAuth.values.s3.secret = secret; 97 | const info = await fetchJson("https://s3.us.archive.org?check_auth=1", { 98 | headers: authToHeaderS3(newAuth), 99 | }); 100 | newAuth.values.email = info.username; 101 | newAuth.values.itemname = info.itemname; 102 | newAuth.values.screenname = info.screenname; 103 | // Note the auth object is missing cookie fields. 104 | // It is still TBD if those are needed 105 | return newAuth; 106 | } 107 | async fromCookies(loggedInSig, loggedInUser, newAuth = newEmptyAuth()) { 108 | newAuth.values.cookies["logged-in-sig"] = loggedInSig; 109 | newAuth.values.cookies["logged-in-user"] = loggedInUser; 110 | const s3response = await fetch( 111 | corsWorkAround("https://archive.org/account/s3.php?output_json=1"), 112 | { 113 | headers: authToHeaderCookies(newAuth), 114 | } 115 | ); 116 | const s3 = await s3response.json(); 117 | if (!s3.success) { 118 | throw new Error(); 119 | } 120 | return await this.fromS3(s3.key.s3accesskey, s3.key.s3secretkey, newAuth); 121 | } 122 | } 123 | 124 | class BookReaderAPI {} 125 | 126 | class FavoritesAPI { 127 | constructor() { 128 | this.API_BASE = corsWorkAround("https://archive.org/bookmarks.php"); 129 | // TODO support this non-json explore endpoint 130 | this.EXPLORE_API_BASE = "https://archive.org/bookmarks-explore.php"; 131 | } 132 | async get({ screenname = null, auth = newEmptyAuth() }) { 133 | if (!screenname && auth.values.screenname) { 134 | screenname = auth.values.screenname; 135 | } 136 | if (screenname) { 137 | let params = { output: "json", screenname }; 138 | return await fetchJson(`${this.API_BASE}?${paramify(params)}`); 139 | } else { 140 | throw new Error( 141 | "Neither screenname or auth provided for bookmarks lookup" 142 | ); 143 | } 144 | } 145 | async add({ identifier = null, comments = "", auth = newEmptyAuth() } = {}) { 146 | return await this.modify({ identifier, add_bookmark: 1 }, auth); 147 | } 148 | async remove({ identifier = null, auth = null } = {}) { 149 | return await this.modify({ identifier, del_bookmark: identifier }, auth); 150 | } 151 | async modify(params, auth) { 152 | try { 153 | let mdResponse = await iajs.MetadataAPI.get({ 154 | identifier: params.identifier, 155 | path: "/metadata", 156 | }); 157 | params.title = str2arr(mdResponse.result.title).join(", "); 158 | params.mediatype = mdResponse.result.mediatype; 159 | } catch (e) { 160 | throw new Error(`Metadata lookup failed for: ${params.identifier}`); 161 | } 162 | params.output = "json"; 163 | const response = await fetch(`${this.API_BASE}?${paramify(params)}`, { 164 | method: "POST", 165 | headers: authToHeaderCookies(auth), 166 | }); 167 | return await response.json().catch((e) => { 168 | return { error: e }; 169 | }); 170 | } 171 | } 172 | 173 | class GifcitiesAPI { 174 | constructor() { 175 | this.API_BASE = "https://gifcities.archive.org/api/v1/gifsearch"; 176 | } 177 | async get({ q = null } = {}) { 178 | if (q === null) return []; 179 | return fetchJson(`${this.API_BASE}?q=${enc(q)}`); 180 | } 181 | async search(q) { 182 | return this.get({ q }); 183 | } 184 | } 185 | 186 | class MetadataAPI { 187 | constructor() { 188 | this.READ_API_BASE = "https://archive.org/metadata"; 189 | this.WRITE_API_BASE = corsWorkAround("https://archive.org/metadata"); 190 | } 191 | async get({ identifier = null, path = "", auth = newEmptyAuth() } = {}) { 192 | const options = {}; 193 | options.headers = authToHeaderS3(auth); 194 | return fetchJson(`${this.READ_API_BASE}/${identifier}/${path}`, options); 195 | } 196 | async patch({ 197 | identifier = null, 198 | target = "metadata", 199 | priority = -5, 200 | patch = {}, 201 | auth = newEmptyAuth(), 202 | } = {}) { 203 | // https://archive.org/services/docs/api/metadata.html#targets 204 | const reqParams = { 205 | "-target": target, 206 | "-patch": JSON.stringify(patch), 207 | priority, 208 | secret: auth.values.s3.secret, 209 | access: auth.values.s3.access, 210 | }; 211 | const url = `${this.WRITE_API_BASE}/${identifier}`; 212 | const body = paramify(reqParams); 213 | const response = await fetch(url, { 214 | method: "POST", 215 | body, 216 | headers: { 217 | "Content-Type": "application/x-www-form-urlencoded", 218 | }, 219 | }); 220 | return await response.json(); 221 | } 222 | } 223 | 224 | class RelatedAPI { 225 | constructor() { 226 | this.API_BASE = "https://be-api.us.archive.org/mds/v1"; 227 | } 228 | async get({ identifier = null } = {}) { 229 | return fetchJson(`${this.API_BASE}/get_related/all/${identifier}`); 230 | } 231 | } 232 | 233 | class ReviewsAPI { 234 | constructor() { 235 | this.WRITE_API_BASE = corsWorkAround( 236 | "https://archive.org/services/reviews.php?identifier=" 237 | ); 238 | this.READ_API_BASE = "https://archive.org/metadata"; 239 | } 240 | async get({ identifier = null } = {}) { 241 | return fetchJson(`${this.READ_API_BASE}/${identifier}/reviews`); 242 | } 243 | async add({ 244 | identifier = null, 245 | title = null, 246 | body = null, 247 | stars = null, 248 | auth = newEmptyAuth(), 249 | } = {}) { 250 | const url = `${this.WRITE_API_BASE}${identifier}`; 251 | const response = await fetch(url, { 252 | method: "POST", 253 | body: JSON.stringify({ title, body, stars }), 254 | headers: { 255 | "Content-Type": "application/json", 256 | ...authToHeaderS3(auth), 257 | }, 258 | }); 259 | return await response.json(); 260 | } 261 | } 262 | 263 | class S3API { 264 | constructor() { 265 | this.API_BASE = "https://s3.us.archive.org"; 266 | } 267 | async ls({ identifier = null, auth = newEmptyAuth() } = {}) { 268 | // throw new Error("TODO parse that XML"); 269 | if (!identifier) { 270 | throw new Error("Missing required args"); 271 | } 272 | return await (await fetch(`${this.API_BASE}/${identifier}`)).text(); 273 | } 274 | async createEmptyItem({ 275 | identifier = null, 276 | testItem = false, 277 | metadata = {}, 278 | headers = {}, 279 | wait = true, 280 | auth = newEmptyAuth(), 281 | } = {}) { 282 | return await this.upload({ 283 | identifier, 284 | testItem, 285 | metadata, 286 | headers, 287 | wait, 288 | auth, 289 | autocreate: true, 290 | }); 291 | } 292 | async upload({ 293 | identifier = null, 294 | key = null, 295 | body = "", 296 | autocreate = false, 297 | skipDerive = false, 298 | testItem = false, 299 | keepOldVersions = true, 300 | metadata = {}, 301 | headers = {}, 302 | wait = true, 303 | auth = newEmptyAuth(), 304 | }) { 305 | if (!identifier) { 306 | throw new Error("Missing required args"); 307 | } 308 | 309 | if (testItem) { 310 | metadata["collection"] = "test_collection"; 311 | } 312 | 313 | const requestHeaders = {}; 314 | Object.keys(metadata).forEach((k) => { 315 | str2arr(metadata[k]).forEach((v, idx) => { 316 | k = k.replace(/_/g, "--"); 317 | let headerKey = `x-archive-meta${idx}-${k}`; 318 | requestHeaders[headerKey] = v; 319 | }); 320 | }); 321 | 322 | Object.assign(requestHeaders, headers, authToHeaderS3(auth)); 323 | 324 | if (autocreate) { 325 | requestHeaders["x-archive-auto-make-bucket"] = 1; 326 | } 327 | if (skipDerive) { 328 | requestHeaders["x-archive-queue-derive"] = 0; 329 | } 330 | requestHeaders["x-archive-keep-old-version"] = keepOldVersions ? 1 : 0; 331 | 332 | const requestUrl = key 333 | ? `${this.API_BASE}/${identifier}/${key}` 334 | : `${this.API_BASE}/${identifier}`; 335 | 336 | const response = await fetch(requestUrl, { 337 | method: "PUT", 338 | headers: requestHeaders, 339 | body, 340 | }); 341 | 342 | if (response.status !== 200) { 343 | // NOTE this may not be the right thing to check. 344 | // Maybe different codes are okay 345 | throw new Error(`Response: ${response.status}`); 346 | } 347 | 348 | if (!wait) { 349 | return response; 350 | } 351 | // The finished response seems to be empty 352 | return await response.text(); 353 | } 354 | } 355 | 356 | class SearchAPI { 357 | constructor() { 358 | this.API_BASE = "https://archive.org/advancedsearch.php"; 359 | } 360 | async get({ q = null, page = 1, fields = ["identifier"], ...options } = {}) { 361 | if (!q) { 362 | throw new Error("Missing required arg 'q'"); 363 | } 364 | if (typeof q == "object") { 365 | q = this.buildQueryFromObject(q); 366 | } 367 | const reqParams = { 368 | q, 369 | page, 370 | fl: fields, 371 | ...options, 372 | output: "json", 373 | }; 374 | const encodedParams = paramify(reqParams); 375 | const url = `${this.API_BASE}?${encodedParams}`; 376 | return fetchJson(url); 377 | } 378 | async search(q) { 379 | return await this.get({ q }); 380 | } 381 | buildQueryFromObject(qObject) { 382 | // Map dictionary to a key=val search query 383 | return Object.keys(qObject) 384 | .map((key) => { 385 | if (Array.isArray(qObject[key])) { 386 | return `${key}:( ${qObject[key].map((v) => `"${v}"`).join(" OR ")} )`; 387 | } else { 388 | return `${key}:"${qObject[key]}"`; 389 | } 390 | }) 391 | .join(" AND "); 392 | } 393 | } 394 | 395 | class SearchTextAPI {} 396 | 397 | class ViewsAPI { 398 | constructor() { 399 | // https://be-api.us.archive.org/views/v1/short/[,,...] 400 | this.API_BASE = "https://be-api.us.archive.org/views/v1/short"; 401 | } 402 | async get({ identifier = null } = {}) { 403 | identifier = Array.isArray(identifier) ? identifier.join(",") : identifier; 404 | return fetchJson(`${this.API_BASE}/${identifier}`); 405 | } 406 | } 407 | 408 | class WaybackAPI { 409 | constructor() { 410 | this.AVAILABLE_API_BASE = "https://archive.org/wayback/available"; 411 | this.CDX_API_BASE = corsWorkAround("https://web.archive.org/cdx/search/"); 412 | this.SAVE_API_BASE = corsWorkAround("https://web.archive.org/save/"); 413 | } 414 | /** 415 | * @see https://archive.org/help/wayback_api.php 416 | */ 417 | async available({ url = null, timestamp = null } = {}) { 418 | const params = { url }; 419 | if (timestamp !== null) { 420 | params.timestamp = timestamp; 421 | } 422 | const searchParams = paramify(params); 423 | const fetchFunction = isInBrowser() ? fetchJsonp : fetch; 424 | const response = await fetchFunction( 425 | `${this.AVAILABLE_API_BASE}?${searchParams}` 426 | ); 427 | return await response.json(); 428 | } 429 | /** 430 | * @see https://github.com/internetarchive/wayback/tree/master/wayback-cdx-server 431 | */ 432 | async cdx(options = {}) { 433 | options.output = "json"; 434 | const searchParams = paramify(options); 435 | const response = await fetch(`${this.CDX_API_BASE}?${searchParams}`); 436 | const raw = await response.text(); 437 | let json; 438 | try { 439 | json = JSON.parse(raw); 440 | } catch (e) { 441 | json = { error: raw.trim() }; 442 | } 443 | return json; 444 | } 445 | /** 446 | * @see https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA/edit 447 | */ 448 | async savePageNow({ 449 | url = null, 450 | captureOutlinks = 0, 451 | captureAll = true, 452 | captureScreenshot = false, 453 | skipFirstArchive = true, 454 | ifNotArchivedWithin = null, 455 | auth = newEmptyAuth(), 456 | } = {}) { 457 | url = url.replace(/^https?\:\/\//, ""); 458 | const params = { 459 | url, 460 | capture_outlinks: captureOutlinks, 461 | capture_all: captureAll ? "1" : "0", 462 | capture_screenshot: captureScreenshot ? "1" : "0", 463 | skip_first_archive: skipFirstArchive ? "1" : "0", 464 | }; 465 | if (ifNotArchivedWithin) { 466 | params.if_not_archived_within = ifNotArchivedWithin; 467 | } 468 | const response = await fetch(this.SAVE_API_BASE, { 469 | credentials: "omit", 470 | method: "POST", 471 | body: paramify(params), 472 | headers: { 473 | Accept: "application/json", 474 | "Content-Type": "application/x-www-form-urlencoded", 475 | ...authToHeaderS3(auth), 476 | }, 477 | }); 478 | return await response.json(); 479 | } 480 | } 481 | 482 | class ZipFileAPI { 483 | /** 484 | * List the contents of a zip file in an item 485 | * Eg: https://archive.org/download/goodytwoshoes00newyiala/goodytwoshoes00newyiala_jp2.zip/ 486 | */ 487 | async ls(identifier, zipPath, auth = newEmptyAuth()) { 488 | if (!zipPath.match(/\.(7z|cbr|cbz|cdr|iso|rar|tar|zip)$/)) { 489 | throw new Error("Invalid zip type"); 490 | } 491 | const requestUrl = corsWorkAround( 492 | `https://archive.org/download/${identifier}/${enc(zipPath)}/` 493 | ); 494 | const response = await fetch(requestUrl, { 495 | headers: authToHeaderCookies(auth), 496 | }); 497 | if (response.status != 200) { 498 | throw Error({ error: "not found" }); 499 | } 500 | 501 | const html = await response.text(); 502 | 503 | // This page has 's without closing el tags (took a while to 504 | // figure this out). This breaks the DOMparser, so I added a workaround 505 | // to add closing tags 506 | let tableHtml = html.match(/([\w\W]*<\/table>)/g)[0]; 507 | tableHtml = tableHtml.replace( 508 | /(]*>[\w\W]*?)(?=<(?:td|\/tr))/g, 509 | "$1" 510 | ); 511 | 512 | let table = new DOMParser().parseFromString(tableHtml); 513 | const rows = table.getElementsByTagName("tr"); 514 | const results = []; 515 | for (let i = 0; i < rows.length; i++) { 516 | let cells = rows.item(i).getElementsByTagName("td"); 517 | if (cells.length != 4) continue; 518 | try { 519 | let a = cells.item(0).getElementsByTagName("a").item(0); 520 | results.push({ 521 | key: a.textContent, 522 | href: "https:" + a.getAttribute("href"), 523 | jpegUrl: (() => { 524 | try { 525 | return ( 526 | "https:" + 527 | cells 528 | .item(1) 529 | .getElementsByTagName("a") 530 | .item(0) 531 | .getAttribute("href") 532 | ); 533 | } catch (e) { 534 | return null; 535 | } 536 | })(), 537 | timestamp: cells.item(2).textContent, 538 | size: cells.item(3).textContent, 539 | }); 540 | } catch (e) {} 541 | } 542 | return results; 543 | } 544 | } 545 | 546 | const iajs = { 547 | Auth: new Auth(), 548 | BookReaderAPI: new BookReaderAPI(), 549 | GifcitiesAPI: new GifcitiesAPI(), 550 | FavoritesAPI: new FavoritesAPI(), 551 | MetadataAPI: new MetadataAPI(), 552 | RelatedAPI: new RelatedAPI(), 553 | ReviewsAPI: new ReviewsAPI(), 554 | SearchAPI: new SearchAPI(), 555 | SearchTextAPI: new SearchTextAPI(), 556 | S3API: new S3API(), 557 | ViewsAPI: new ViewsAPI(), 558 | WaybackAPI: new WaybackAPI(), 559 | ZipFileAPI: new ZipFileAPI(), 560 | }; 561 | 562 | export default iajs; 563 | -------------------------------------------------------------------------------- /examples/web/water-2-light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Forced light theme version 3 | */ 4 | 5 | :root { 6 | --background-body: #fff; 7 | --background: #efefef; 8 | --background-alt: #f7f7f7; 9 | --selection: #9e9e9e; 10 | --text-main: #363636; 11 | --text-bright: #000; 12 | --text-muted: #70777f; 13 | --links: #0076d1; 14 | --focus: #0096bfab; 15 | --border: #dbdbdb; 16 | --code: #000; 17 | --animation-duration: 0.1s; 18 | --button-hover: #ddd; 19 | --scrollbar-thumb: rgb(213, 213, 213); 20 | --scrollbar-thumb-hover: rgb(196, 196, 196); 21 | --form-placeholder: #949494; 22 | --form-text: #000; 23 | --variable: #39a33c; 24 | --highlight: #ff0; 25 | --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); 26 | } 27 | 28 | html { 29 | scrollbar-color: rgb(213, 213, 213) #fff; 30 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 31 | scrollbar-width: thin; 32 | } 33 | 34 | body { 35 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif; 36 | line-height: 1.4; 37 | max-width: 800px; 38 | margin: 20px auto; 39 | padding: 0 10px; 40 | word-wrap: break-word; 41 | color: #363636; 42 | color: var(--text-main); 43 | background: #fff; 44 | background: var(--background-body); 45 | text-rendering: optimizeLegibility; 46 | } 47 | 48 | button { 49 | transition: 50 | background-color 0.1s linear, 51 | border-color 0.1s linear, 52 | color 0.1s linear, 53 | box-shadow 0.1s linear, 54 | transform 0.1s ease; 55 | transition: 56 | background-color var(--animation-duration) linear, 57 | border-color var(--animation-duration) linear, 58 | color var(--animation-duration) linear, 59 | box-shadow var(--animation-duration) linear, 60 | transform var(--animation-duration) ease; 61 | } 62 | 63 | input { 64 | transition: 65 | background-color 0.1s linear, 66 | border-color 0.1s linear, 67 | color 0.1s linear, 68 | box-shadow 0.1s linear, 69 | transform 0.1s ease; 70 | transition: 71 | background-color var(--animation-duration) linear, 72 | border-color var(--animation-duration) linear, 73 | color var(--animation-duration) linear, 74 | box-shadow var(--animation-duration) linear, 75 | transform var(--animation-duration) ease; 76 | } 77 | 78 | textarea { 79 | transition: 80 | background-color 0.1s linear, 81 | border-color 0.1s linear, 82 | color 0.1s linear, 83 | box-shadow 0.1s linear, 84 | transform 0.1s ease; 85 | transition: 86 | background-color var(--animation-duration) linear, 87 | border-color var(--animation-duration) linear, 88 | color var(--animation-duration) linear, 89 | box-shadow var(--animation-duration) linear, 90 | transform var(--animation-duration) ease; 91 | } 92 | 93 | h1 { 94 | font-size: 2.2em; 95 | margin-top: 0; 96 | } 97 | 98 | h1, 99 | h2, 100 | h3, 101 | h4, 102 | h5, 103 | h6 { 104 | margin-bottom: 12px; 105 | margin-top: 24px; 106 | } 107 | 108 | h1 { 109 | color: #000; 110 | color: var(--text-bright); 111 | } 112 | 113 | h2 { 114 | color: #000; 115 | color: var(--text-bright); 116 | } 117 | 118 | h3 { 119 | color: #000; 120 | color: var(--text-bright); 121 | } 122 | 123 | h4 { 124 | color: #000; 125 | color: var(--text-bright); 126 | } 127 | 128 | h5 { 129 | color: #000; 130 | color: var(--text-bright); 131 | } 132 | 133 | h6 { 134 | color: #000; 135 | color: var(--text-bright); 136 | } 137 | 138 | strong { 139 | color: #000; 140 | color: var(--text-bright); 141 | } 142 | 143 | h1, 144 | h2, 145 | h3, 146 | h4, 147 | h5, 148 | h6, 149 | b, 150 | strong, 151 | th { 152 | font-weight: 600; 153 | } 154 | 155 | q::before { 156 | content: none; 157 | } 158 | 159 | q::after { 160 | content: none; 161 | } 162 | 163 | blockquote { 164 | border-left: 4px solid #0096bfab; 165 | border-left: 4px solid var(--focus); 166 | margin: 1.5em 0; 167 | padding: 0.5em 1em; 168 | font-style: italic; 169 | } 170 | 171 | q { 172 | border-left: 4px solid #0096bfab; 173 | border-left: 4px solid var(--focus); 174 | margin: 1.5em 0; 175 | padding: 0.5em 1em; 176 | font-style: italic; 177 | } 178 | 179 | blockquote > footer { 180 | font-style: normal; 181 | border: 0; 182 | } 183 | 184 | blockquote cite { 185 | font-style: normal; 186 | } 187 | 188 | address { 189 | font-style: normal; 190 | } 191 | 192 | a[href^='mailto\:']::before { 193 | content: '📧 '; 194 | } 195 | 196 | a[href^='tel\:']::before { 197 | content: '📞 '; 198 | } 199 | 200 | a[href^='sms\:']::before { 201 | content: '💬 '; 202 | } 203 | 204 | mark { 205 | background-color: #ff0; 206 | background-color: var(--highlight); 207 | border-radius: 2px; 208 | padding: 0 2px 0 2px; 209 | color: #000; 210 | } 211 | 212 | button, 213 | select, 214 | input[type='submit'], 215 | input[type='button'], 216 | input[type='checkbox'], 217 | input[type='range'], 218 | input[type='radio'] { 219 | cursor: pointer; 220 | } 221 | 222 | input:not([type='checkbox']):not([type='radio']), 223 | select { 224 | display: block; 225 | } 226 | 227 | input { 228 | color: #000; 229 | color: var(--form-text); 230 | background-color: #efefef; 231 | background-color: var(--background); 232 | font-family: inherit; 233 | font-size: inherit; 234 | margin-right: 6px; 235 | margin-bottom: 6px; 236 | padding: 10px; 237 | border: none; 238 | border-radius: 6px; 239 | outline: none; 240 | } 241 | 242 | button { 243 | color: #000; 244 | color: var(--form-text); 245 | background-color: #efefef; 246 | background-color: var(--background); 247 | font-family: inherit; 248 | font-size: inherit; 249 | margin-right: 6px; 250 | margin-bottom: 6px; 251 | padding: 10px; 252 | border: none; 253 | border-radius: 6px; 254 | outline: none; 255 | } 256 | 257 | textarea { 258 | color: #000; 259 | color: var(--form-text); 260 | background-color: #efefef; 261 | background-color: var(--background); 262 | font-family: inherit; 263 | font-size: inherit; 264 | margin-right: 6px; 265 | margin-bottom: 6px; 266 | padding: 10px; 267 | border: none; 268 | border-radius: 6px; 269 | outline: none; 270 | } 271 | 272 | select { 273 | color: #000; 274 | color: var(--form-text); 275 | background-color: #efefef; 276 | background-color: var(--background); 277 | font-family: inherit; 278 | font-size: inherit; 279 | margin-right: 6px; 280 | margin-bottom: 6px; 281 | padding: 10px; 282 | border: none; 283 | border-radius: 6px; 284 | outline: none; 285 | } 286 | 287 | input[type='checkbox'], 288 | input[type='radio'] { 289 | height: 1em; 290 | width: 1em; 291 | } 292 | 293 | input[type='radio'] { 294 | border-radius: 100%; 295 | } 296 | 297 | input { 298 | vertical-align: top; 299 | } 300 | 301 | label { 302 | vertical-align: middle; 303 | margin-bottom: 4px; 304 | display: inline-block; 305 | } 306 | 307 | input:not([type='checkbox']):not([type='radio']), 308 | input[type='range'], 309 | select, 310 | button, 311 | textarea { 312 | -webkit-appearance: none; 313 | } 314 | 315 | textarea { 316 | display: block; 317 | margin-right: 0; 318 | box-sizing: border-box; 319 | resize: vertical; 320 | } 321 | 322 | textarea:not([cols]) { 323 | width: 100%; 324 | } 325 | 326 | textarea:not([rows]) { 327 | min-height: 40px; 328 | height: 140px; 329 | } 330 | 331 | select { 332 | background: #efefef url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; 333 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; 334 | padding-right: 35px; 335 | } 336 | 337 | select::-ms-expand { 338 | display: none; 339 | } 340 | 341 | select[multiple] { 342 | padding-right: 10px; 343 | background-image: none; 344 | overflow-y: auto; 345 | } 346 | 347 | button, 348 | input[type='submit'], 349 | input[type='button'] { 350 | padding-right: 30px; 351 | padding-left: 30px; 352 | } 353 | 354 | button:hover { 355 | background: #ddd; 356 | background: var(--button-hover); 357 | } 358 | 359 | input[type='submit']:hover { 360 | background: #ddd; 361 | background: var(--button-hover); 362 | } 363 | 364 | input[type='button']:hover { 365 | background: #ddd; 366 | background: var(--button-hover); 367 | } 368 | 369 | input:focus { 370 | box-shadow: 0 0 0 2px #0096bfab; 371 | box-shadow: 0 0 0 2px var(--focus); 372 | } 373 | 374 | select:focus { 375 | box-shadow: 0 0 0 2px #0096bfab; 376 | box-shadow: 0 0 0 2px var(--focus); 377 | } 378 | 379 | button:focus { 380 | box-shadow: 0 0 0 2px #0096bfab; 381 | box-shadow: 0 0 0 2px var(--focus); 382 | } 383 | 384 | textarea:focus { 385 | box-shadow: 0 0 0 2px #0096bfab; 386 | box-shadow: 0 0 0 2px var(--focus); 387 | } 388 | 389 | input[type='checkbox']:active, 390 | input[type='radio']:active, 391 | input[type='submit']:active, 392 | input[type='button']:active, 393 | input[type='range']:active, 394 | button:active { 395 | transform: translateY(2px); 396 | } 397 | 398 | input:disabled, 399 | select:disabled, 400 | button:disabled, 401 | textarea:disabled { 402 | cursor: not-allowed; 403 | opacity: 0.5; 404 | } 405 | 406 | ::-moz-placeholder { 407 | color: #949494; 408 | color: var(--form-placeholder); 409 | } 410 | 411 | :-ms-input-placeholder { 412 | color: #949494; 413 | color: var(--form-placeholder); 414 | } 415 | 416 | ::-ms-input-placeholder { 417 | color: #949494; 418 | color: var(--form-placeholder); 419 | } 420 | 421 | ::placeholder { 422 | color: #949494; 423 | color: var(--form-placeholder); 424 | } 425 | 426 | fieldset { 427 | border: 1px #0096bfab solid; 428 | border: 1px var(--focus) solid; 429 | border-radius: 6px; 430 | margin: 0; 431 | margin-bottom: 12px; 432 | padding: 10px; 433 | } 434 | 435 | legend { 436 | font-size: 0.9em; 437 | font-weight: 600; 438 | } 439 | 440 | input[type='range'] { 441 | margin: 10px 0; 442 | padding: 10px 0; 443 | background: transparent; 444 | } 445 | 446 | input[type='range']:focus { 447 | outline: none; 448 | } 449 | 450 | input[type='range']::-webkit-slider-runnable-track { 451 | width: 100%; 452 | height: 9.5px; 453 | -webkit-transition: 0.2s; 454 | transition: 0.2s; 455 | background: #efefef; 456 | background: var(--background); 457 | border-radius: 3px; 458 | } 459 | 460 | input[type='range']::-webkit-slider-thumb { 461 | box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d; 462 | height: 20px; 463 | width: 20px; 464 | border-radius: 50%; 465 | background: #dbdbdb; 466 | background: var(--border); 467 | -webkit-appearance: none; 468 | margin-top: -7px; 469 | } 470 | 471 | input[type='range']:focus::-webkit-slider-runnable-track { 472 | background: #efefef; 473 | background: var(--background); 474 | } 475 | 476 | input[type='range']::-moz-range-track { 477 | width: 100%; 478 | height: 9.5px; 479 | -moz-transition: 0.2s; 480 | transition: 0.2s; 481 | background: #efefef; 482 | background: var(--background); 483 | border-radius: 3px; 484 | } 485 | 486 | input[type='range']::-moz-range-thumb { 487 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 488 | height: 20px; 489 | width: 20px; 490 | border-radius: 50%; 491 | background: #dbdbdb; 492 | background: var(--border); 493 | } 494 | 495 | input[type='range']::-ms-track { 496 | width: 100%; 497 | height: 9.5px; 498 | background: transparent; 499 | border-color: transparent; 500 | border-width: 16px 0; 501 | color: transparent; 502 | } 503 | 504 | input[type='range']::-ms-fill-lower { 505 | background: #efefef; 506 | background: var(--background); 507 | border: 0.2px solid #010101; 508 | border-radius: 3px; 509 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 510 | } 511 | 512 | input[type='range']::-ms-fill-upper { 513 | background: #efefef; 514 | background: var(--background); 515 | border: 0.2px solid #010101; 516 | border-radius: 3px; 517 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 518 | } 519 | 520 | input[type='range']::-ms-thumb { 521 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 522 | border: 1px solid #000; 523 | height: 20px; 524 | width: 20px; 525 | border-radius: 50%; 526 | background: #dbdbdb; 527 | background: var(--border); 528 | } 529 | 530 | input[type='range']:focus::-ms-fill-lower { 531 | background: #efefef; 532 | background: var(--background); 533 | } 534 | 535 | input[type='range']:focus::-ms-fill-upper { 536 | background: #efefef; 537 | background: var(--background); 538 | } 539 | 540 | a { 541 | text-decoration: none; 542 | color: #0076d1; 543 | color: var(--links); 544 | } 545 | 546 | a:hover { 547 | text-decoration: underline; 548 | } 549 | 550 | code { 551 | background: #efefef; 552 | background: var(--background); 553 | color: #000; 554 | color: var(--code); 555 | padding: 2.5px 5px; 556 | border-radius: 6px; 557 | font-size: 1em; 558 | } 559 | 560 | samp { 561 | background: #efefef; 562 | background: var(--background); 563 | color: #000; 564 | color: var(--code); 565 | padding: 2.5px 5px; 566 | border-radius: 6px; 567 | font-size: 1em; 568 | } 569 | 570 | time { 571 | background: #efefef; 572 | background: var(--background); 573 | color: #000; 574 | color: var(--code); 575 | padding: 2.5px 5px; 576 | border-radius: 6px; 577 | font-size: 1em; 578 | } 579 | 580 | pre > code { 581 | padding: 10px; 582 | display: block; 583 | overflow-x: auto; 584 | } 585 | 586 | var { 587 | color: #39a33c; 588 | color: var(--variable); 589 | font-style: normal; 590 | font-family: monospace; 591 | } 592 | 593 | kbd { 594 | background: #efefef; 595 | background: var(--background); 596 | border: 1px solid #dbdbdb; 597 | border: 1px solid var(--border); 598 | border-radius: 2px; 599 | color: #363636; 600 | color: var(--text-main); 601 | padding: 2px 4px 2px 4px; 602 | } 603 | 604 | img, 605 | video { 606 | max-width: 100%; 607 | height: auto; 608 | } 609 | 610 | hr { 611 | border: none; 612 | border-top: 1px solid #dbdbdb; 613 | border-top: 1px solid var(--border); 614 | } 615 | 616 | table { 617 | border-collapse: collapse; 618 | margin-bottom: 10px; 619 | width: 100%; 620 | table-layout: fixed; 621 | } 622 | 623 | table caption { 624 | text-align: left; 625 | } 626 | 627 | td, 628 | th { 629 | padding: 6px; 630 | text-align: left; 631 | vertical-align: top; 632 | word-wrap: break-word; 633 | } 634 | 635 | thead { 636 | border-bottom: 1px solid #dbdbdb; 637 | border-bottom: 1px solid var(--border); 638 | } 639 | 640 | tfoot { 641 | border-top: 1px solid #dbdbdb; 642 | border-top: 1px solid var(--border); 643 | } 644 | 645 | tbody tr:nth-child(even) { 646 | background-color: #f7f7f7; 647 | background-color: var(--background-alt); 648 | } 649 | 650 | ::-webkit-scrollbar { 651 | height: 10px; 652 | width: 10px; 653 | } 654 | 655 | ::-webkit-scrollbar-track { 656 | background: #efefef; 657 | background: var(--background); 658 | border-radius: 6px; 659 | } 660 | 661 | ::-webkit-scrollbar-thumb { 662 | background: rgb(213, 213, 213); 663 | background: var(--scrollbar-thumb); 664 | border-radius: 6px; 665 | } 666 | 667 | ::-webkit-scrollbar-thumb:hover { 668 | background: rgb(196, 196, 196); 669 | background: var(--scrollbar-thumb-hover); 670 | } 671 | 672 | ::-moz-selection { 673 | background-color: #9e9e9e; 674 | background-color: var(--selection); 675 | color: #000; 676 | color: var(--text-bright); 677 | } 678 | 679 | ::selection { 680 | background-color: #9e9e9e; 681 | background-color: var(--selection); 682 | color: #000; 683 | color: var(--text-bright); 684 | } 685 | 686 | details { 687 | display: flex; 688 | flex-direction: column; 689 | align-items: flex-start; 690 | background-color: #f7f7f7; 691 | background-color: var(--background-alt); 692 | padding: 10px 10px 0; 693 | margin: 1em 0; 694 | border-radius: 6px; 695 | overflow: hidden; 696 | } 697 | 698 | details[open] { 699 | padding: 10px; 700 | } 701 | 702 | details > :last-child { 703 | margin-bottom: 0; 704 | } 705 | 706 | details[open] summary { 707 | margin-bottom: 10px; 708 | } 709 | 710 | summary { 711 | display: list-item; 712 | background-color: #efefef; 713 | background-color: var(--background); 714 | padding: 10px; 715 | margin: -10px -10px 0; 716 | cursor: pointer; 717 | outline: none; 718 | } 719 | 720 | summary:hover, 721 | summary:focus { 722 | text-decoration: underline; 723 | } 724 | 725 | details > :not(summary) { 726 | margin-top: 0; 727 | } 728 | 729 | summary::-webkit-details-marker { 730 | color: #363636; 731 | color: var(--text-main); 732 | } 733 | 734 | footer { 735 | border-top: 1px solid #dbdbdb; 736 | border-top: 1px solid var(--border); 737 | padding-top: 10px; 738 | color: #70777f; 739 | color: var(--text-muted); 740 | } 741 | 742 | body > footer { 743 | margin-top: 40px; 744 | } 745 | 746 | @media print { 747 | body, 748 | pre, 749 | code, 750 | summary, 751 | details, 752 | button, 753 | input, 754 | textarea { 755 | background-color: #fff; 756 | } 757 | 758 | button, 759 | input, 760 | textarea { 761 | border: 1px solid #000; 762 | } 763 | 764 | body, 765 | h1, 766 | h2, 767 | h3, 768 | h4, 769 | h5, 770 | h6, 771 | pre, 772 | code, 773 | button, 774 | input, 775 | textarea, 776 | footer, 777 | summary, 778 | strong { 779 | color: #000; 780 | } 781 | 782 | summary::marker { 783 | color: #000; 784 | } 785 | 786 | summary::-webkit-details-marker { 787 | color: #000; 788 | } 789 | 790 | tbody tr:nth-child(even) { 791 | background-color: #f2f2f2; 792 | } 793 | 794 | a { 795 | color: #00f; 796 | text-decoration: underline; 797 | } 798 | } 799 | 800 | /*# sourceMappingURL=light.css.map */ 801 | -------------------------------------------------------------------------------- /examples/web/ia.browser.js: -------------------------------------------------------------------------------- 1 | function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 2 | 3 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 4 | 5 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } 6 | 7 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 8 | 9 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 12 | 13 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 14 | 15 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 16 | 17 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 18 | 19 | (function (global, factory) { 20 | (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('node-fetch'), require('xmldom')) : typeof define === 'function' && define.amd ? define(['node-fetch', 'xmldom'], factory) : (global = global || self, global.ia = factory(global.fetch, global.xmldom)); 21 | })(this, function (fetch, xmldom) { 22 | 'use strict'; 23 | 24 | fetch = fetch && Object.prototype.hasOwnProperty.call(fetch, 'default') ? fetch['default'] : fetch; 25 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 26 | 27 | function createCommonjsModule(fn, module) { 28 | return module = { 29 | exports: {} 30 | }, fn(module, module.exports), module.exports; 31 | } 32 | 33 | var fetchJsonp = createCommonjsModule(function (module, exports) { 34 | (function (global, factory) { 35 | { 36 | factory(exports, module); 37 | } 38 | })(commonjsGlobal, function (exports, module) { 39 | var defaultOptions = { 40 | timeout: 5000, 41 | jsonpCallback: 'callback', 42 | jsonpCallbackFunction: null 43 | }; 44 | 45 | function generateCallbackFunction() { 46 | return 'jsonp_' + Date.now() + '_' + Math.ceil(Math.random() * 100000); 47 | } 48 | 49 | function clearFunction(functionName) { 50 | // IE8 throws an exception when you try to delete a property on window 51 | // http://stackoverflow.com/a/1824228/751089 52 | try { 53 | delete window[functionName]; 54 | } catch (e) { 55 | window[functionName] = undefined; 56 | } 57 | } 58 | 59 | function removeScript(scriptId) { 60 | var script = document.getElementById(scriptId); 61 | 62 | if (script) { 63 | document.getElementsByTagName('head')[0].removeChild(script); 64 | } 65 | } 66 | 67 | function fetchJsonp(_url) { 68 | var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; // to avoid param reassign 69 | 70 | var url = _url; 71 | var timeout = options.timeout || defaultOptions.timeout; 72 | var jsonpCallback = options.jsonpCallback || defaultOptions.jsonpCallback; 73 | var timeoutId = undefined; 74 | return new Promise(function (resolve, reject) { 75 | var callbackFunction = options.jsonpCallbackFunction || generateCallbackFunction(); 76 | var scriptId = jsonpCallback + '_' + callbackFunction; 77 | 78 | window[callbackFunction] = function (response) { 79 | resolve({ 80 | ok: true, 81 | // keep consistent with fetch API 82 | json: function json() { 83 | return Promise.resolve(response); 84 | } 85 | }); 86 | if (timeoutId) clearTimeout(timeoutId); 87 | removeScript(scriptId); 88 | clearFunction(callbackFunction); 89 | }; // Check if the user set their own params, and if not add a ? to start a list of params 90 | 91 | 92 | url += url.indexOf('?') === -1 ? '?' : '&'; 93 | var jsonpScript = document.createElement('script'); 94 | jsonpScript.setAttribute('src', '' + url + jsonpCallback + '=' + callbackFunction); 95 | 96 | if (options.charset) { 97 | jsonpScript.setAttribute('charset', options.charset); 98 | } 99 | 100 | jsonpScript.id = scriptId; 101 | document.getElementsByTagName('head')[0].appendChild(jsonpScript); 102 | timeoutId = setTimeout(function () { 103 | reject(new Error('JSONP request to ' + _url + ' timed out')); 104 | clearFunction(callbackFunction); 105 | removeScript(scriptId); 106 | 107 | window[callbackFunction] = function () { 108 | clearFunction(callbackFunction); 109 | }; 110 | }, timeout); // Caught if got 404/500 111 | 112 | jsonpScript.onerror = function () { 113 | reject(new Error('JSONP request to ' + _url + ' failed')); 114 | clearFunction(callbackFunction); 115 | removeScript(scriptId); 116 | if (timeoutId) clearTimeout(timeoutId); 117 | }; 118 | }); 119 | } // export as global function 120 | 121 | /* 122 | let local; 123 | if (typeof global !== 'undefined') { 124 | local = global; 125 | } else if (typeof self !== 'undefined') { 126 | local = self; 127 | } else { 128 | try { 129 | local = Function('return this')(); 130 | } catch (e) { 131 | throw new Error('polyfill failed because global object is unavailable in this environment'); 132 | } 133 | } 134 | local.fetchJsonp = fetchJsonp; 135 | */ 136 | 137 | 138 | module.exports = fetchJsonp; 139 | }); 140 | }); 141 | var CORS_PROXY = "https://iajs-cors.rchrd2.workers.dev"; 142 | var enc = encodeURIComponent; 143 | 144 | var paramify = function paramify(obj) { 145 | return new URLSearchParams(obj).toString(); 146 | }; 147 | 148 | var str2arr = function str2arr(v) { 149 | return Array.isArray(v) ? v : [v]; 150 | }; 151 | 152 | var isInBrowser = function isInBrowser() { 153 | return !(typeof window === "undefined"); 154 | }; 155 | 156 | var corsWorkAround = function corsWorkAround(url) { 157 | if (isInBrowser()) { 158 | return "".concat(CORS_PROXY, "/").concat(url); 159 | } else { 160 | return url; 161 | } 162 | }; 163 | 164 | var fetchJson = function fetchJson(url, options) { 165 | return Promise.resolve().then(function () { 166 | return fetch(url, options); 167 | }).then(function (_resp) { 168 | var res = _resp; 169 | return res.json(); 170 | }); 171 | }; 172 | 173 | var authToHeaderS3 = function authToHeaderS3(auth) { 174 | return auth.values.s3.access && auth.values.s3.secret ? { 175 | Authorization: "LOW ".concat(auth.values.s3.access, ":").concat(auth.values.s3.secret) 176 | } : {}; 177 | }; 178 | 179 | var authToHeaderCookies = function authToHeaderCookies(auth) { 180 | if (auth.values.cookies["logged-in-sig"] && auth.values.cookies["logged-in-user"]) { 181 | var cookieStr = "logged-in-sig=".concat(auth.values.cookies["logged-in-sig"], ";"); 182 | cookieStr += " logged-in-user=".concat(auth.values.cookies["logged-in-user"]); 183 | var headers = { 184 | Cookie: cookieStr 185 | }; 186 | 187 | if (isInBrowser()) { 188 | headers["X-Cookie-Cors"] = cookieStr; 189 | } 190 | 191 | return headers; 192 | } else { 193 | return {}; 194 | } 195 | }; 196 | 197 | var newEmptyAuth = function newEmptyAuth() { 198 | return JSON.parse(JSON.stringify({ 199 | success: false, 200 | values: { 201 | cookies: { 202 | "logged-in-sig": null, 203 | "logged-in-user": null 204 | }, 205 | email: null, 206 | itemname: null, 207 | s3: { 208 | access: null, 209 | secret: null 210 | }, 211 | screenname: null 212 | }, 213 | version: 1 214 | })); 215 | }; 216 | 217 | var Auth = /*#__PURE__*/function () { 218 | function Auth() { 219 | _classCallCheck(this, Auth); 220 | 221 | this.XAUTH_BASE = corsWorkAround("https://archive.org/services/xauthn/"); 222 | } 223 | 224 | _createClass(Auth, [{ 225 | key: "login", 226 | value: function login(email, password) { 227 | var _this2 = this; 228 | 229 | return Promise.resolve().then(function () { 230 | return Promise.resolve().then(function () { 231 | var fetchOptions = { 232 | method: "POST", 233 | body: "email=".concat(enc(email), "&password=").concat(enc(password)), 234 | headers: { 235 | "Content-Type": "application/x-www-form-urlencoded" 236 | } 237 | }; 238 | return fetch("".concat(_this2.XAUTH_BASE, "?op=login"), fetchOptions); 239 | }).then(function (_resp) { 240 | var response = _resp; 241 | return response.json(); 242 | }).then(function (_resp) { 243 | var data = _resp; 244 | 245 | if (!data.success) { 246 | data.values = _objectSpread(_objectSpread({}, data.values), newEmptyAuth().values); 247 | } 248 | 249 | return data; 250 | })["catch"](function (e) { 251 | // TODO figure out syntax for catching error response 252 | return newEmptyAuth(); 253 | }); 254 | }).then(function () {}); 255 | } 256 | }, { 257 | key: "fromS3", 258 | value: function fromS3(access, secret) { 259 | var newAuth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : newEmptyAuth(); 260 | return Promise.resolve().then(function () { 261 | newAuth.success = 1; 262 | newAuth.values.s3.access = access; 263 | newAuth.values.s3.secret = secret; 264 | return fetchJson("https://s3.us.archive.org?check_auth=1", { 265 | headers: authToHeaderS3(newAuth) 266 | }); 267 | }).then(function (_resp) { 268 | var info = _resp; 269 | newAuth.values.email = info.username; 270 | newAuth.values.itemname = info.itemname; 271 | newAuth.values.screenname = info.screenname; // Note the auth object is missing cookie fields. 272 | // It is still TBD if those are needed 273 | 274 | return newAuth; 275 | }); 276 | } 277 | }, { 278 | key: "fromCookies", 279 | value: function fromCookies(loggedInSig, loggedInUser) { 280 | var newAuth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : newEmptyAuth(); 281 | 282 | var _this4 = this; 283 | 284 | return Promise.resolve().then(function () { 285 | newAuth.values.cookies["logged-in-sig"] = loggedInSig; 286 | newAuth.values.cookies["logged-in-user"] = loggedInUser; 287 | return fetch(corsWorkAround("https://archive.org/account/s3.php?output_json=1"), { 288 | headers: authToHeaderCookies(newAuth) 289 | }); 290 | }).then(function (_resp) { 291 | var s3response = _resp; 292 | return s3response.json(); 293 | }).then(function (_resp) { 294 | var s3 = _resp; 295 | 296 | if (!s3.success) { 297 | throw new Error(); 298 | } 299 | 300 | return _this4.fromS3(s3.key.s3accesskey, s3.key.s3secretkey, newAuth); 301 | }); 302 | } 303 | }]); 304 | 305 | return Auth; 306 | }(); 307 | 308 | var BookReaderAPI = function BookReaderAPI() { 309 | _classCallCheck(this, BookReaderAPI); 310 | }; 311 | 312 | var FavoritesAPI = /*#__PURE__*/function () { 313 | function FavoritesAPI() { 314 | _classCallCheck(this, FavoritesAPI); 315 | 316 | this.API_BASE = corsWorkAround("https://archive.org/bookmarks.php"); // TODO support this non-json explore endpoint 317 | 318 | this.EXPLORE_API_BASE = "https://archive.org/bookmarks-explore.php"; 319 | } 320 | 321 | _createClass(FavoritesAPI, [{ 322 | key: "get", 323 | value: function get(_ref) { 324 | var _ref$screenname = _ref.screenname, 325 | screenname = _ref$screenname === void 0 ? null : _ref$screenname, 326 | _ref$auth = _ref.auth, 327 | auth = _ref$auth === void 0 ? newEmptyAuth() : _ref$auth; 328 | 329 | var _this5 = this; 330 | 331 | return Promise.resolve().then(function () { 332 | if (!screenname && auth.values.screenname) { 333 | screenname = auth.values.screenname; 334 | } 335 | 336 | if (screenname) { 337 | var params = { 338 | output: "json", 339 | screenname: screenname 340 | }; 341 | return fetchJson("".concat(_this5.API_BASE, "?").concat(paramify(params))); 342 | } else { 343 | throw new Error("Neither screenname or auth provided for bookmarks lookup"); 344 | } 345 | }); 346 | } 347 | }, { 348 | key: "add", 349 | value: function add() { 350 | var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 351 | _ref2$identifier = _ref2.identifier, 352 | identifier = _ref2$identifier === void 0 ? null : _ref2$identifier, 353 | _ref2$comments = _ref2.comments, 354 | comments = _ref2$comments === void 0 ? "" : _ref2$comments, 355 | _ref2$auth = _ref2.auth, 356 | auth = _ref2$auth === void 0 ? newEmptyAuth() : _ref2$auth; 357 | 358 | var _this6 = this; 359 | 360 | return Promise.resolve().then(function () { 361 | return _this6.modify({ 362 | identifier: identifier, 363 | add_bookmark: 1 364 | }, auth); 365 | }); 366 | } 367 | }, { 368 | key: "remove", 369 | value: function remove() { 370 | var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 371 | _ref3$identifier = _ref3.identifier, 372 | identifier = _ref3$identifier === void 0 ? null : _ref3$identifier, 373 | _ref3$auth = _ref3.auth, 374 | auth = _ref3$auth === void 0 ? null : _ref3$auth; 375 | 376 | var _this7 = this; 377 | 378 | return Promise.resolve().then(function () { 379 | return _this7.modify({ 380 | identifier: identifier, 381 | del_bookmark: identifier 382 | }, auth); 383 | }); 384 | } 385 | }, { 386 | key: "modify", 387 | value: function modify(params, auth) { 388 | var _this8 = this; 389 | 390 | return Promise.resolve().then(function () { 391 | return Promise.resolve().then(function () { 392 | return iajs.MetadataAPI.get({ 393 | identifier: params.identifier, 394 | path: "/metadata" 395 | }); 396 | }).then(function (_resp) { 397 | var mdResponse = _resp; 398 | params.title = str2arr(mdResponse.result.title).join(", "); 399 | params.mediatype = mdResponse.result.mediatype; 400 | })["catch"](function (e) { 401 | throw new Error("Metadata lookup failed for: ".concat(params.identifier)); 402 | }); 403 | }).then(function () { 404 | params.output = "json"; 405 | return fetch("".concat(_this8.API_BASE, "?").concat(paramify(params)), { 406 | method: "POST", 407 | headers: authToHeaderCookies(auth) 408 | }); 409 | }).then(function (_resp) { 410 | var response = _resp; 411 | return response.json()["catch"](function (e) { 412 | return { 413 | error: e 414 | }; 415 | }); 416 | }); 417 | } 418 | }]); 419 | 420 | return FavoritesAPI; 421 | }(); 422 | 423 | var GifcitiesAPI = /*#__PURE__*/function () { 424 | function GifcitiesAPI() { 425 | _classCallCheck(this, GifcitiesAPI); 426 | 427 | this.API_BASE = "https://gifcities.archive.org/api/v1/gifsearch"; 428 | } 429 | 430 | _createClass(GifcitiesAPI, [{ 431 | key: "get", 432 | value: function get() { 433 | var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 434 | _ref4$q = _ref4.q, 435 | q = _ref4$q === void 0 ? null : _ref4$q; 436 | 437 | var _this9 = this; 438 | 439 | return Promise.resolve().then(function () { 440 | if (q === null) { 441 | return []; 442 | } else { 443 | return fetchJson("".concat(_this9.API_BASE, "?q=").concat(enc(q))); 444 | } 445 | }); 446 | } 447 | }, { 448 | key: "search", 449 | value: function search(q) { 450 | var _this10 = this; 451 | 452 | return Promise.resolve().then(function () { 453 | return _this10.get({ 454 | q: q 455 | }); 456 | }); 457 | } 458 | }]); 459 | 460 | return GifcitiesAPI; 461 | }(); 462 | 463 | var MetadataAPI = /*#__PURE__*/function () { 464 | function MetadataAPI() { 465 | _classCallCheck(this, MetadataAPI); 466 | 467 | this.READ_API_BASE = "https://archive.org/metadata"; 468 | this.WRITE_API_BASE = corsWorkAround("https://archive.org/metadata"); 469 | } 470 | 471 | _createClass(MetadataAPI, [{ 472 | key: "get", 473 | value: function get() { 474 | var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 475 | _ref5$identifier = _ref5.identifier, 476 | identifier = _ref5$identifier === void 0 ? null : _ref5$identifier, 477 | _ref5$path = _ref5.path, 478 | path = _ref5$path === void 0 ? "" : _ref5$path, 479 | _ref5$auth = _ref5.auth, 480 | auth = _ref5$auth === void 0 ? newEmptyAuth() : _ref5$auth; 481 | 482 | var _this11 = this; 483 | 484 | return Promise.resolve().then(function () { 485 | var options = {}; 486 | options.headers = authToHeaderS3(auth); 487 | return fetchJson("".concat(_this11.READ_API_BASE, "/").concat(identifier, "/").concat(path), options); 488 | }); 489 | } 490 | }, { 491 | key: "patch", 492 | value: function patch() { 493 | var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 494 | _ref6$identifier = _ref6.identifier, 495 | identifier = _ref6$identifier === void 0 ? null : _ref6$identifier, 496 | _ref6$target = _ref6.target, 497 | target = _ref6$target === void 0 ? "metadata" : _ref6$target, 498 | _ref6$priority = _ref6.priority, 499 | priority = _ref6$priority === void 0 ? -5 : _ref6$priority, 500 | _ref6$patch = _ref6.patch, 501 | _patch = _ref6$patch === void 0 ? {} : _ref6$patch, 502 | _ref6$auth = _ref6.auth, 503 | auth = _ref6$auth === void 0 ? newEmptyAuth() : _ref6$auth; 504 | 505 | var _this12 = this; 506 | 507 | return Promise.resolve().then(function () { 508 | // https://archive.org/services/docs/api/metadata.html#targets 509 | var reqParams = { 510 | "-target": target, 511 | "-patch": JSON.stringify(_patch), 512 | priority: priority, 513 | secret: auth.values.s3.secret, 514 | access: auth.values.s3.access 515 | }; 516 | var url = "".concat(_this12.WRITE_API_BASE, "/").concat(identifier); 517 | var body = paramify(reqParams); 518 | return fetch(url, { 519 | method: "POST", 520 | body: body, 521 | headers: { 522 | "Content-Type": "application/x-www-form-urlencoded" 523 | } 524 | }); 525 | }).then(function (_resp) { 526 | var response = _resp; 527 | return response.json(); 528 | }); 529 | } 530 | }]); 531 | 532 | return MetadataAPI; 533 | }(); 534 | 535 | var RelatedAPI = /*#__PURE__*/function () { 536 | function RelatedAPI() { 537 | _classCallCheck(this, RelatedAPI); 538 | 539 | this.API_BASE = "https://be-api.us.archive.org/mds/v1"; 540 | } 541 | 542 | _createClass(RelatedAPI, [{ 543 | key: "get", 544 | value: function get() { 545 | var _ref7 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 546 | _ref7$identifier = _ref7.identifier, 547 | identifier = _ref7$identifier === void 0 ? null : _ref7$identifier; 548 | 549 | var _this13 = this; 550 | 551 | return Promise.resolve().then(function () { 552 | return fetchJson("".concat(_this13.API_BASE, "/get_related/all/").concat(identifier)); 553 | }); 554 | } 555 | }]); 556 | 557 | return RelatedAPI; 558 | }(); 559 | 560 | var ReviewsAPI = /*#__PURE__*/function () { 561 | function ReviewsAPI() { 562 | _classCallCheck(this, ReviewsAPI); 563 | 564 | this.WRITE_API_BASE = corsWorkAround("https://archive.org/services/reviews.php?identifier="); 565 | this.READ_API_BASE = "https://archive.org/metadata"; 566 | } 567 | 568 | _createClass(ReviewsAPI, [{ 569 | key: "get", 570 | value: function get() { 571 | var _ref8 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 572 | _ref8$identifier = _ref8.identifier, 573 | identifier = _ref8$identifier === void 0 ? null : _ref8$identifier; 574 | 575 | var _this14 = this; 576 | 577 | return Promise.resolve().then(function () { 578 | return fetchJson("".concat(_this14.READ_API_BASE, "/").concat(identifier, "/reviews")); 579 | }); 580 | } 581 | }, { 582 | key: "add", 583 | value: function add() { 584 | var _ref9 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 585 | _ref9$identifier = _ref9.identifier, 586 | identifier = _ref9$identifier === void 0 ? null : _ref9$identifier, 587 | _ref9$title = _ref9.title, 588 | title = _ref9$title === void 0 ? null : _ref9$title, 589 | _ref9$body = _ref9.body, 590 | body = _ref9$body === void 0 ? null : _ref9$body, 591 | _ref9$stars = _ref9.stars, 592 | stars = _ref9$stars === void 0 ? null : _ref9$stars, 593 | _ref9$auth = _ref9.auth, 594 | auth = _ref9$auth === void 0 ? newEmptyAuth() : _ref9$auth; 595 | 596 | var _this15 = this; 597 | 598 | return Promise.resolve().then(function () { 599 | var url = "".concat(_this15.WRITE_API_BASE).concat(identifier); 600 | return fetch(url, { 601 | method: "POST", 602 | body: JSON.stringify({ 603 | title: title, 604 | body: body, 605 | stars: stars 606 | }), 607 | headers: _objectSpread({ 608 | "Content-Type": "application/json" 609 | }, authToHeaderS3(auth)) 610 | }); 611 | }).then(function (_resp) { 612 | var response = _resp; 613 | return response.json(); 614 | }); 615 | } 616 | }]); 617 | 618 | return ReviewsAPI; 619 | }(); 620 | 621 | var S3API = /*#__PURE__*/function () { 622 | function S3API() { 623 | _classCallCheck(this, S3API); 624 | 625 | this.API_BASE = "https://s3.us.archive.org"; 626 | } 627 | 628 | _createClass(S3API, [{ 629 | key: "ls", 630 | value: function ls() { 631 | var _ref10 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 632 | _ref10$identifier = _ref10.identifier, 633 | identifier = _ref10$identifier === void 0 ? null : _ref10$identifier, 634 | _ref10$auth = _ref10.auth, 635 | auth = _ref10$auth === void 0 ? newEmptyAuth() : _ref10$auth; 636 | 637 | var _this16 = this; 638 | 639 | return Promise.resolve().then(function () { 640 | // throw new Error("TODO parse that XML"); 641 | if (!identifier) { 642 | throw new Error("Missing required args"); 643 | } 644 | 645 | return fetch("".concat(_this16.API_BASE, "/").concat(identifier)); 646 | }).then(function (_resp) { 647 | return _resp.text(); 648 | }); 649 | } 650 | }, { 651 | key: "createEmptyItem", 652 | value: function createEmptyItem() { 653 | var _ref11 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 654 | _ref11$identifier = _ref11.identifier, 655 | identifier = _ref11$identifier === void 0 ? null : _ref11$identifier, 656 | _ref11$testItem = _ref11.testItem, 657 | testItem = _ref11$testItem === void 0 ? false : _ref11$testItem, 658 | _ref11$metadata = _ref11.metadata, 659 | metadata = _ref11$metadata === void 0 ? {} : _ref11$metadata, 660 | _ref11$headers = _ref11.headers, 661 | headers = _ref11$headers === void 0 ? {} : _ref11$headers, 662 | _ref11$wait = _ref11.wait, 663 | wait = _ref11$wait === void 0 ? true : _ref11$wait, 664 | _ref11$auth = _ref11.auth, 665 | auth = _ref11$auth === void 0 ? newEmptyAuth() : _ref11$auth; 666 | 667 | var _this17 = this; 668 | 669 | return Promise.resolve().then(function () { 670 | return _this17.upload({ 671 | identifier: identifier, 672 | testItem: testItem, 673 | metadata: metadata, 674 | headers: headers, 675 | wait: wait, 676 | auth: auth, 677 | autocreate: true 678 | }); 679 | }); 680 | } 681 | }, { 682 | key: "upload", 683 | value: function upload(_ref12) { 684 | var _ref12$identifier = _ref12.identifier, 685 | identifier = _ref12$identifier === void 0 ? null : _ref12$identifier, 686 | _ref12$key = _ref12.key, 687 | key = _ref12$key === void 0 ? null : _ref12$key, 688 | _ref12$body = _ref12.body, 689 | body = _ref12$body === void 0 ? "" : _ref12$body, 690 | _ref12$autocreate = _ref12.autocreate, 691 | autocreate = _ref12$autocreate === void 0 ? false : _ref12$autocreate, 692 | _ref12$skipDerive = _ref12.skipDerive, 693 | skipDerive = _ref12$skipDerive === void 0 ? false : _ref12$skipDerive, 694 | _ref12$testItem = _ref12.testItem, 695 | testItem = _ref12$testItem === void 0 ? false : _ref12$testItem, 696 | _ref12$keepOldVersion = _ref12.keepOldVersions, 697 | keepOldVersions = _ref12$keepOldVersion === void 0 ? true : _ref12$keepOldVersion, 698 | _ref12$metadata = _ref12.metadata, 699 | metadata = _ref12$metadata === void 0 ? {} : _ref12$metadata, 700 | _ref12$headers = _ref12.headers, 701 | headers = _ref12$headers === void 0 ? {} : _ref12$headers, 702 | _ref12$wait = _ref12.wait, 703 | wait = _ref12$wait === void 0 ? true : _ref12$wait, 704 | _ref12$auth = _ref12.auth, 705 | auth = _ref12$auth === void 0 ? newEmptyAuth() : _ref12$auth; 706 | 707 | var _this18 = this; 708 | 709 | return Promise.resolve().then(function () { 710 | if (!identifier) { 711 | throw new Error("Missing required args"); 712 | } 713 | 714 | if (testItem) { 715 | metadata["collection"] = "test_collection"; 716 | } 717 | 718 | var requestHeaders = {}; 719 | Object.keys(metadata).forEach(function (k) { 720 | str2arr(metadata[k]).forEach(function (v, idx) { 721 | k = k.replace(/_/g, "--"); 722 | var headerKey = "x-archive-meta".concat(idx, "-").concat(k); 723 | requestHeaders[headerKey] = v; 724 | }); 725 | }); 726 | Object.assign(requestHeaders, headers, authToHeaderS3(auth)); 727 | 728 | if (autocreate) { 729 | requestHeaders["x-archive-auto-make-bucket"] = 1; 730 | } 731 | 732 | if (skipDerive) { 733 | requestHeaders["x-archive-queue-derive"] = 0; 734 | } 735 | 736 | requestHeaders["x-archive-keep-old-version"] = keepOldVersions ? 1 : 0; 737 | var requestUrl = key ? "".concat(_this18.API_BASE, "/").concat(identifier, "/").concat(key) : "".concat(_this18.API_BASE, "/").concat(identifier); 738 | return fetch(requestUrl, { 739 | method: "PUT", 740 | headers: requestHeaders, 741 | body: body 742 | }); 743 | }).then(function (_resp) { 744 | var response = _resp; 745 | 746 | if (response.status !== 200) { 747 | // NOTE this may not be the right thing to check. 748 | // Maybe different codes are okay 749 | throw new Error("Response: ".concat(response.status)); 750 | } 751 | 752 | if (!wait) { 753 | return response; 754 | } else { 755 | // The finished response seems to be empty 756 | return response.text(); 757 | } 758 | }); 759 | } 760 | }]); 761 | 762 | return S3API; 763 | }(); 764 | 765 | var SearchAPI = /*#__PURE__*/function () { 766 | function SearchAPI() { 767 | _classCallCheck(this, SearchAPI); 768 | 769 | this.API_BASE = "https://archive.org/advancedsearch.php"; 770 | } 771 | 772 | _createClass(SearchAPI, [{ 773 | key: "get", 774 | value: function get() { 775 | var _ref13 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 776 | _ref13$q = _ref13.q, 777 | q = _ref13$q === void 0 ? null : _ref13$q, 778 | _ref13$page = _ref13.page, 779 | page = _ref13$page === void 0 ? 1 : _ref13$page, 780 | _ref13$fields = _ref13.fields, 781 | fields = _ref13$fields === void 0 ? ["identifier"] : _ref13$fields, 782 | options = _objectWithoutProperties(_ref13, ["q", "page", "fields"]); 783 | 784 | var _this19 = this; 785 | 786 | return Promise.resolve().then(function () { 787 | if (!q) { 788 | throw new Error("Missing required arg 'q'"); 789 | } 790 | 791 | if (_typeof(q) == "object") { 792 | q = _this19.buildQueryFromObject(q); 793 | } 794 | 795 | var reqParams = _objectSpread(_objectSpread({ 796 | q: q, 797 | page: page, 798 | fl: fields 799 | }, options), {}, { 800 | output: "json" 801 | }); 802 | 803 | var encodedParams = paramify(reqParams); 804 | var url = "".concat(_this19.API_BASE, "?").concat(encodedParams); 805 | return fetchJson(url); 806 | }); 807 | } 808 | }, { 809 | key: "search", 810 | value: function search(q) { 811 | var _this20 = this; 812 | 813 | return Promise.resolve().then(function () { 814 | return _this20.get({ 815 | q: q 816 | }); 817 | }); 818 | } 819 | }, { 820 | key: "buildQueryFromObject", 821 | value: function buildQueryFromObject(qObject) { 822 | // Map dictionary to a key=val search query 823 | return Object.keys(qObject).map(function (key) { 824 | if (Array.isArray(qObject[key])) { 825 | return "".concat(key, ":( ").concat(qObject[key].map(function (v) { 826 | return "\"".concat(v, "\""); 827 | }).join(" OR "), " )"); 828 | } else { 829 | return "".concat(key, ":\"").concat(qObject[key], "\""); 830 | } 831 | }).join(" AND "); 832 | } 833 | }]); 834 | 835 | return SearchAPI; 836 | }(); 837 | 838 | var SearchTextAPI = function SearchTextAPI() { 839 | _classCallCheck(this, SearchTextAPI); 840 | }; 841 | 842 | var ViewsAPI = /*#__PURE__*/function () { 843 | function ViewsAPI() { 844 | _classCallCheck(this, ViewsAPI); 845 | 846 | // https://be-api.us.archive.org/views/v1/short/[,,...] 847 | this.API_BASE = "https://be-api.us.archive.org/views/v1/short"; 848 | } 849 | 850 | _createClass(ViewsAPI, [{ 851 | key: "get", 852 | value: function get() { 853 | var _ref14 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 854 | _ref14$identifier = _ref14.identifier, 855 | identifier = _ref14$identifier === void 0 ? null : _ref14$identifier; 856 | 857 | var _this21 = this; 858 | 859 | return Promise.resolve().then(function () { 860 | identifier = Array.isArray(identifier) ? identifier.join(",") : identifier; 861 | return fetchJson("".concat(_this21.API_BASE, "/").concat(identifier)); 862 | }); 863 | } 864 | }]); 865 | 866 | return ViewsAPI; 867 | }(); 868 | 869 | var WaybackAPI = /*#__PURE__*/function () { 870 | function WaybackAPI() { 871 | _classCallCheck(this, WaybackAPI); 872 | 873 | this.AVAILABLE_API_BASE = "https://archive.org/wayback/available"; 874 | this.CDX_API_BASE = corsWorkAround("https://web.archive.org/cdx/search/"); 875 | this.SAVE_API_BASE = corsWorkAround("https://web.archive.org/save/"); 876 | } 877 | /** 878 | * @see https://archive.org/help/wayback_api.php 879 | */ 880 | 881 | 882 | _createClass(WaybackAPI, [{ 883 | key: "available", 884 | value: function available() { 885 | var _ref15 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 886 | _ref15$url = _ref15.url, 887 | url = _ref15$url === void 0 ? null : _ref15$url, 888 | _ref15$timestamp = _ref15.timestamp, 889 | timestamp = _ref15$timestamp === void 0 ? null : _ref15$timestamp; 890 | 891 | var _this22 = this; 892 | 893 | return Promise.resolve().then(function () { 894 | var params = { 895 | url: url 896 | }; 897 | 898 | if (timestamp !== null) { 899 | params.timestamp = timestamp; 900 | } 901 | 902 | var searchParams = paramify(params); 903 | var fetchFunction = isInBrowser() ? fetchJsonp : fetch; 904 | return fetchFunction("".concat(_this22.AVAILABLE_API_BASE, "?").concat(searchParams)); 905 | }).then(function (_resp) { 906 | var response = _resp; 907 | return response.json(); 908 | }); 909 | } 910 | /** 911 | * @see https://github.com/internetarchive/wayback/tree/master/wayback-cdx-server 912 | */ 913 | 914 | }, { 915 | key: "cdx", 916 | value: function cdx() { 917 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 918 | 919 | var _this23 = this; 920 | 921 | return Promise.resolve().then(function () { 922 | options.output = "json"; 923 | var searchParams = paramify(options); 924 | return fetch("".concat(_this23.CDX_API_BASE, "?").concat(searchParams)); 925 | }).then(function (_resp) { 926 | var response = _resp; 927 | return response.text(); 928 | }).then(function (_resp) { 929 | var raw = _resp; 930 | var json; 931 | 932 | try { 933 | json = JSON.parse(raw); 934 | } catch (e) { 935 | json = { 936 | error: raw.trim() 937 | }; 938 | } 939 | 940 | return json; 941 | }); 942 | } 943 | /** 944 | * @see https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA/edit 945 | */ 946 | 947 | }, { 948 | key: "savePageNow", 949 | value: function savePageNow() { 950 | var _ref16 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 951 | _ref16$url = _ref16.url, 952 | url = _ref16$url === void 0 ? null : _ref16$url, 953 | _ref16$captureOutlink = _ref16.captureOutlinks, 954 | captureOutlinks = _ref16$captureOutlink === void 0 ? 0 : _ref16$captureOutlink, 955 | _ref16$captureAll = _ref16.captureAll, 956 | captureAll = _ref16$captureAll === void 0 ? true : _ref16$captureAll, 957 | _ref16$captureScreens = _ref16.captureScreenshot, 958 | captureScreenshot = _ref16$captureScreens === void 0 ? false : _ref16$captureScreens, 959 | _ref16$skipFirstArchi = _ref16.skipFirstArchive, 960 | skipFirstArchive = _ref16$skipFirstArchi === void 0 ? true : _ref16$skipFirstArchi, 961 | _ref16$ifNotArchivedW = _ref16.ifNotArchivedWithin, 962 | ifNotArchivedWithin = _ref16$ifNotArchivedW === void 0 ? null : _ref16$ifNotArchivedW, 963 | _ref16$auth = _ref16.auth, 964 | auth = _ref16$auth === void 0 ? newEmptyAuth() : _ref16$auth; 965 | 966 | var _this24 = this; 967 | 968 | return Promise.resolve().then(function () { 969 | url = url.replace(/^https?\:\/\//, ""); 970 | var params = { 971 | url: url, 972 | capture_outlinks: captureOutlinks, 973 | capture_all: captureAll ? "1" : "0", 974 | capture_screenshot: captureScreenshot ? "1" : "0", 975 | skip_first_archive: skipFirstArchive ? "1" : "0" 976 | }; 977 | 978 | if (ifNotArchivedWithin) { 979 | params.if_not_archived_within = ifNotArchivedWithin; 980 | } 981 | 982 | return fetch(_this24.SAVE_API_BASE, { 983 | credentials: "omit", 984 | method: "POST", 985 | body: paramify(params), 986 | headers: _objectSpread({ 987 | Accept: "application/json", 988 | "Content-Type": "application/x-www-form-urlencoded" 989 | }, authToHeaderS3(auth)) 990 | }); 991 | }).then(function (_resp) { 992 | var response = _resp; 993 | return response.json(); 994 | }); 995 | } 996 | }]); 997 | 998 | return WaybackAPI; 999 | }(); 1000 | 1001 | var ZipFileAPI = /*#__PURE__*/function () { 1002 | function ZipFileAPI() { 1003 | _classCallCheck(this, ZipFileAPI); 1004 | } 1005 | 1006 | _createClass(ZipFileAPI, [{ 1007 | key: "ls", 1008 | 1009 | /** 1010 | * List the contents of a zip file in an item 1011 | * Eg: https://archive.org/download/goodytwoshoes00newyiala/goodytwoshoes00newyiala_jp2.zip/ 1012 | */ 1013 | value: function ls(identifier, zipPath) { 1014 | var auth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : newEmptyAuth(); 1015 | return Promise.resolve().then(function () { 1016 | if (!zipPath.match(/\.(7z|cbr|cbz|cdr|iso|rar|tar|zip)$/)) { 1017 | throw new Error("Invalid zip type"); 1018 | } 1019 | 1020 | var requestUrl = corsWorkAround("https://archive.org/download/".concat(identifier, "/").concat(enc(zipPath), "/")); 1021 | return fetch(requestUrl, { 1022 | headers: authToHeaderCookies(auth) 1023 | }); 1024 | }).then(function (_resp) { 1025 | var response = _resp; 1026 | 1027 | if (response.status != 200) { 1028 | throw Error({ 1029 | error: "not found" 1030 | }); 1031 | } 1032 | 1033 | return response.text(); 1034 | }).then(function (_resp) { 1035 | var html = _resp; // This page has
's without closing el tags (took a while to 1036 | // figure this out). This breaks the DOMparser, so I added a workaround 1037 | // to add closing tags 1038 | 1039 | var tableHtml = html.match(/([\w\W]*<\/table>)/g)[0]; 1040 | tableHtml = tableHtml.replace(/(]*>[\w\W]*?)(?=<(?:td|\/tr))/g, "$1"); 1041 | var table = new xmldom.DOMParser().parseFromString(tableHtml); 1042 | var rows = table.getElementsByTagName("tr"); 1043 | var results = []; 1044 | 1045 | var _loop = function _loop(i) { 1046 | var cells = rows.item(i).getElementsByTagName("td"); 1047 | 1048 | if (cells.length != 4) { 1049 | return "continue"; 1050 | } 1051 | 1052 | try { 1053 | var a = cells.item(0).getElementsByTagName("a").item(0); 1054 | results.push({ 1055 | key: a.textContent, 1056 | href: "https:" + a.getAttribute("href"), 1057 | jpegUrl: function () { 1058 | try { 1059 | return "https:" + cells.item(1).getElementsByTagName("a").item(0).getAttribute("href"); 1060 | } catch (e) { 1061 | return null; 1062 | } 1063 | }(), 1064 | timestamp: cells.item(2).textContent, 1065 | size: cells.item(3).textContent 1066 | }); 1067 | } catch (e) {} 1068 | }; 1069 | 1070 | for (var i = 0; i < rows.length; i++) { 1071 | var _ret = _loop(i); 1072 | 1073 | if (_ret === "continue") continue; 1074 | } 1075 | 1076 | return results; 1077 | }); 1078 | } 1079 | }]); 1080 | 1081 | return ZipFileAPI; 1082 | }(); 1083 | 1084 | var iajs = { 1085 | Auth: new Auth(), 1086 | BookReaderAPI: new BookReaderAPI(), 1087 | GifcitiesAPI: new GifcitiesAPI(), 1088 | FavoritesAPI: new FavoritesAPI(), 1089 | MetadataAPI: new MetadataAPI(), 1090 | RelatedAPI: new RelatedAPI(), 1091 | ReviewsAPI: new ReviewsAPI(), 1092 | SearchAPI: new SearchAPI(), 1093 | SearchTextAPI: new SearchTextAPI(), 1094 | S3API: new S3API(), 1095 | ViewsAPI: new ViewsAPI(), 1096 | WaybackAPI: new WaybackAPI(), 1097 | ZipFileAPI: new ZipFileAPI() 1098 | }; 1099 | return iajs; 1100 | }); 1101 | --------------------------------------------------------------------------------