[{"sbox-com-downloader": {"hash": "18d9d75f9a122c1cd3d92011699d0f292e97bf60b79169fa19fd240eb3043368", "text": "/**\n * Copyright 2022 Jacob K\n * Copyright 2022 Wojtek Kosior \n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * As additional permission under GNU GPL version 3 section 7, you\n * may distribute forms of that code without the copy of the GNU\n * GPL normally required by section 4, provided you include this\n * license notice and, in case of non-source distribution, a URL\n * through which recipients can access the Corresponding Source.\n * If you modify file(s) with this exception, you may extend this\n * exception to your version of the file(s), but you are not\n * obligated to do so. If you do not wish to do so, delete this\n * exception statement from your version.\n *\n * As a special exception to the GPL, any HTML file which merely\n * makes function calls to this code, and for that purpose\n * includes it by reference shall be deemed a separate work for\n * copyright law purposes. If you modify this code, you may extend\n * this exception to your version of the code, but you are not\n * obligated to do so. If you do not wish to do so, delete this\n * exception statement from your version.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n *\n * I, Wojtek Kosior, thereby promise not to sue for violation of this file's\n * license. Although I request that you do not make use of this code in a\n * proprietary program, I am not going to enforce this in court.\n */\n\n// meta: match should be https://***.app.box.com/s/* (*** instead of * for the first section because otherwise plain app.box.com URLs won't work)\n// meta: some test cases (mostly found at https://old.reddit.com/search?q=\"box.com\"&include_over_18=on&sort=new)\n\t// https://uwmadison.app.box.com/s/ydht2incbdmw1lhpjg5t40adguc0fm14\n\t\t// umadison's enrollment report\n\t\t// pdf\n\t// https://app.box.com/s/gc4ygloi4qtimeh98dq9mmydyuydawcn\n\t\t// password-protected 7z file (nsfw)\n\t// https://app.box.com/shared/static/su6xx6zx50cd68zdtbm3wfxhh9kwke8x.zip\n\t\t// a soundtrack in a zip file\n\t\t// This is a static download, so it works without this script.\n\t// https://app.box.com/s/vysdh2u78yih3c8leetgq82il954a3g3\n\t\t// some gambling ad\n\t\t// pptx\n\t// https://app.box.com/s/nnlplkmjhimau404qohh9my10pwmo8es\n\t\t// a list of books(?)\n\t\t// txt\n\t// https://ucla.app.box.com/s/mv32q624ojihohzh8d0mhhj0b3xluzbz\n\t\t// \"COVID-19 Pivot Plan Decision Matrix\"\n\t\t// If you load the proprietary scripts on this page, you'll see that there is no download button\n\t// TODO: find a public folder link (the private links I have seem to work)\n\t// TODO: find a (preferably public) link with a folder inside a folder, as these may need to be handled differently\n\n/* Extract data from a script that sets multiple variables. */ // from here: https://api-demo.hachette-hydrilla.org/content/sgoogle_sheets_download/google_sheets_download.js\n\nlet prefetchedData = null; // This variable isn't actually used.\nfor (const script of document.scripts) {\n const match = /Box.prefetchedData = ({([^;]|[^}];)+})/.exec(script.textContent); // looks for \"Box.prefetchedData = \" in the script files and then grabs the json text after that.\n if (!match)\n\tcontinue;\n prefetchedData = JSON.parse(match[1]);\n}\n\nlet config = null;\nfor (const script of document.scripts) {\n const match = /Box.config = ({([^;]|[^}];)+})/.exec(script.textContent); // looks for \"Box.config = \" in the script files and then grabs the json text after that.\n if (!match)\n\tcontinue;\n config = JSON.parse(match[1]);\n}\n\nlet postStreamData = null;\nfor (const script of document.scripts) {\n const match = /Box.postStreamData = ({([^;]|[^}];)+})/.exec(script.textContent); // looks for \"Box.postStreamData = \" in the script files and then grabs the json text after that.\n if (!match)\n\tcontinue;\n postStreamData = JSON.parse(match[1]);\n}\n\n// get domain from URL\nconst domain = document.location.href.split(\"/\")[2];\n\n/* Replace current page contents. */\nconst replacement_html = `\\\n\n\n \n \n \n \n

loading...

\n

error occured :(

\n

\n \n \n\n`;\n\n/*\n * We could instead use document.write(), but browser's debugging tools would\n * not see the new page contents.\n */\nconst parser = new DOMParser();\nconst alt_doc = parser.parseFromString(replacement_html, \"text/html\");\ndocument.documentElement.replaceWith(alt_doc.documentElement);\n\nconst nodes = {};\ndocument.querySelectorAll('[id]').forEach(node => nodes[node.id] = node);\n\nfunction show_error() {\n nodes.loading.classList.add(\"hide\");\n nodes.error.classList.remove(\"hide\");\n}\n\nfunction show_title(text) {\n nodes.title.innerText = text;\n nodes.loading.classList.add(\"hide\");\n nodes.title.classList.remove(\"hide\");\n}\n\nasync function hack_file() {\n nodes.loading.classList.remove(\"hide\");\n\n const tokens_url = \"/app-api/enduserapp/elements/tokens\";\n const file_nr = postStreamData[\"/app-api/enduserapp/shared-item\"].itemID;\n const file_id = `file_${file_nr}`;\n const shared_name = postStreamData[\"/app-api/enduserapp/shared-item\"].sharedName;\n\n /*\n * We need to perform a POST to obtain a token that will be used later to\n * authenticate against Box's API endpoint.\n */\n const tokens_response = await fetch(tokens_url, {\n\tmethod: \"POST\",\n\theaders: {\n\t \"Accept\": \"application/json\",\n\t \"Content-Type\": \"application/json\",\n\t \"Request-Token\": config.requestToken,\n\t \"X-Box-Client-Name\": \"enduserapp\",\n\t \"X-Box-Client-Version\": \"20.712.2\",\n\t \"X-Box-EndUser-API\": `sharedName=${shared_name}`,\n\t \"X-Request-Token\": config.requestToken\n\t},\n\tbody: JSON.stringify({\"fileIDs\": [file_id]})\n });\n console.log(\"tokens_response\", tokens_response);\n\n const access_token = (await tokens_response.json())[file_id].read;\n console.log(\"access_token\", access_token);\n\n const fields = [\n\t\"permissions\", \"shared_link\", \"sha1\", \"file_version\", \"name\", \"size\",\n\t\"extension\", \"representations\", \"watermark_info\",\n\t\"authenticated_download_url\", \"is_download_available\",\n\t\"content_created_at\", \"content_modified_at\", \"created_at\", \"created_by\",\n\t\"modified_at\", \"modified_by\", \"owned_by\", \"description\",\n\t\"metadata.global.boxSkillsCards\", \"expires_at\", \"version_limit\",\n\t\"version_number\", \"is_externally_owned\", \"restored_from\",\n\t\"uploader_display_name\"\n ];\n\n const file_info_url =\n\t `https://api.box.com/2.0/files/${file_nr}?fields=${fields.join()}`;\n\n /*\n * We need to perform a GET to obtain file metadata. The fields we curently\n * make use of are \"authenticated_download_url\" and \"file_version\", but in\n * the request we also include names of other fields that the original Box\n * client would include. The metadata is then dumped as JSON on the page, so\n * the user, if curious, can look at it.\n */\n const file_info_response = await fetch(file_info_url, {\n\theaders: {\n\t \"Accept\": \"application/json\",\n\t \"Authorization\": `Bearer ${access_token}`,\n\t \"BoxApi\": `shared_link=${document.URL}`,\n\t \"X-Box-Client-Name\": \"ContentPreview\",\n\t \"X-Rep-Hints\": \"[3d][pdf][text][mp3][json][jpg?dimensions=1024x1024&paged=false][jpg?dimensions=2048x2048,png?dimensions=2048x2048][dash,mp4][filmstrip]\"\n\t},\n });\n console.log(\"file_info_response\", file_info_response);\n\n const file_info = await file_info_response.json();\n console.log(\"file_info\", file_info);\n\n const params = new URLSearchParams();\n params.set(\"preview\", true);\n params.set(\"version\", file_info.file_version.id);\n params.set(\"access_token\", access_token);\n params.set(\"shared_link\", document.URL);\n params.set(\"box_client_name\", \"box-content-preview\");\n params.set(\"box_client_version\", \"2.82.0\");\n params.set(\"encoding\", \"gzip\");\n\n /* We use file metadata from earlier requests to construct the link. */\n const download_url =\n\t `${file_info.authenticated_download_url}?${params.toString()}`;\n console.log(\"download_url\", download_url);\n\n show_title(file_info.name);\n\n nodes.download_button.href = download_url;\n if (!file_info.permissions.can_download)\n\tnodes.download_button.classList.add(\"unofficial\");\n nodes.file_info.innerText = JSON.stringify(file_info);\n nodes.single_file_section.classList.remove(\"hide\");\n}\n\nif (postStreamData[\"/app-api/enduserapp/shared-item\"].itemType == \"file\") {\n /*\n * We call hack_file and in case it asynchronously throws an exception, we\n * make an error message appear.\n */\n hack_file().then(() => {}, show_error);\n} else if (postStreamData[\"/app-api/enduserapp/shared-item\"].itemType == \"folder\") {\n show_title(postStreamData[\"/app-api/enduserapp/shared-folder\"].currentFolderName);\n\n // TODO: implement a download folder button (included in proprietary app)\n /*\n The original download folder button sends a GET request that gets 2 URLs\n in the response. 1 of those URLs downloads the file, and a POST request\n is sent after (or maybe while in some cases?) a file is downloaded, to\n let the server know how much is downloaded.\n */\n // for each item in the folder, show a button with a link to download it\n postStreamData[\"/app-api/enduserapp/shared-folder\"].items.forEach(function(item) {\n\tconsole.log(\"item\", item);\n\n\tconst file_direct_url = \"https://\"+domain+\"/index.php?rm=box_download_shared_file&shared_name=\"+postStreamData[\"/app-api/enduserapp/shared-item\"].sharedName+\"&file_id=\"+item.typedID;\n\n\tconst folderButton = nodes.download_button.cloneNode(false);\n\tfolderButton.removeAttribute(\"id\");\n\n\tif (item.type == \"file\") {\n\t folderButton.href = file_direct_url;\n\t folderButton.innerText = item.name; // show the name of the file\n\t} else if (item.type == \"folder\") {\n\t folderButton.innerText = \"[folders inside folders not yet supported]\";\n\t} else {\n\t folderButton.innerText = \"[this item type is not supported]\";\n\t}\n\n\tdocument.body.appendChild(folderButton);\n });\n} else {\n\tconsole.log('expected \"folder\" or \"file\" as the item type (postStreamData[\"/app-api/enduserapp/shared-item\"].itemType) but got ' + postStreamData[\"/app-api/enduserapp/shared-item\"].itemType + ' instead; this item type is not implemented');\n\tshow_error();\n}\n"}}, {"phttps://***.app.box.com/s/*": {"components": ["s", "box-com-downloader"]}}]