[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Süleyman Yasir KULA\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "OLD_VERSION_PHP/README.md",
    "content": "These PHP files are not used anymore (by http://yasirkula.net/drive/downloadlinkgenerator/). This version used the PHP client of Drive API, whereas the new version uses Javascript client."
  },
  {
    "path": "OLD_VERSION_PHP/create.php",
    "content": "<?php\r\nrequire_once '../../../google-api-php-client-2.0.3/vendor/autoload.php';\r\n\r\nsession_start();\r\n\r\n$valid = 1;\r\n\r\nif( isset($_GET[\"state\"]) && $_GET[\"state\"] )\r\n{\r\n\t$state = json_decode($_GET[\"state\"]);\r\n\t\r\n\tif( $state->action != 'create' )\r\n\t{\r\n\t\techo 'Invalid state';\r\n\t\t$valid = 0;\r\n\t}\r\n\telse\r\n\t{\r\n\t\t$_SESSION['downloadlinkgenerator_createid'] = $state->folderId;\r\n\t}\r\n}\r\n\r\nif( $valid == 1 )\r\n{\r\n\t$client = new Google_Client();\r\n\t$client->setAuthConfig('../../../google-api-php-client-2.0.3/client_id.json');\r\n\t$client->addScope('https://www.googleapis.com/auth/drive.install');\r\n\t$client->addScope(Google_Service_Drive::DRIVE_METADATA_READONLY);\r\n\t\r\n\tif (isset($_SESSION['downloadlinkgenerator_accesstoken']) && $_SESSION['downloadlinkgenerator_accesstoken']) \r\n\t{\r\n\t\tif( !isset($_SESSION['downloadlinkgenerator_createid']) || !isset($_SESSION['downloadlinkgenerator_createid']) )\r\n\t\t{\r\n\t\t\techo 'Use the service with \\'Create\\' button in Drive UI';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t$client->setAccessToken($_SESSION['downloadlinkgenerator_accesstoken']);\r\n\t\t\t$drive_service = new Google_Service_Drive($client);\r\n\t\t\t$result = array();\r\n\t\t\t\r\n\t\t\tGetFilesRecursively( $_SESSION['downloadlinkgenerator_createid'], '' );\r\n\t\t\t\r\n\t\t\techo '<pre>';\r\n\t\t\tforeach( $result as $fileInfo )\r\n\t\t\t{\r\n\t\t\t\techo $fileInfo['path'] . ' ' . $fileInfo['link'] . \"\\r\\n\";\r\n\t\t\t}\r\n\t\t\techo '</pre>';\r\n\t\t}\r\n\t}\r\n\telse \r\n\t{\r\n\t\t$_SESSION['downloadlinkgenerator_op'] = 'create';\r\n\t\t$redirect_uri = 'http://yasirkula.net/drive/downloadlinkgenerator/oauth2callback.php';\r\n\t\theader('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));\r\n\t}\r\n}\r\n\r\nfunction GetFilesRecursively( $folderId, $relativePath )\r\n{\r\n\tglobal $result;\r\n\tglobal $drive_service;\r\n\t\r\n\ttry\r\n\t{\r\n\t\t$pageToken = NULL;\r\n\t\t\r\n\t\tdo\r\n\t\t{\r\n\t\t\t$parameters = array(\r\n\t\t\t\t\t'q' => \"trashed=false and '\" . $folderId . \"' in parents and mimeType != 'application/vnd.google-apps.folder'\",\r\n\t\t\t\t\t'pageSize' => '1000',\r\n\t\t\t\t\t'fields' => 'nextPageToken, files(name, webContentLink)' );\r\n\t\t\t\t\t\r\n\t\t\tif ($pageToken) \r\n\t\t\t{\r\n\t\t\t\t$parameters['pageToken'] = $pageToken;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t$files = $drive_service->files->listFiles($parameters);\r\n\t\t\t\r\n\t\t\t$fileMetas = $files->getFiles();\r\n\t\t\tforeach( $fileMetas as $meta )\r\n\t\t\t{\r\n\t\t\t\tif( $meta->webContentLink )\r\n\t\t\t\t\tarray_push( $result, [ \"path\" => $relativePath . $meta->name, \"link\" => $meta->webContentLink ] );\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t$pageToken = $files->getNextPageToken();\r\n\t\t} while( $pageToken );\r\n\t\t\r\n\t\t$pageToken = NULL;\r\n\t\t\r\n\t\tdo\r\n\t\t{\r\n\t\t\t$parameters = array(\r\n\t\t\t\t\t'q' => \"trashed=false and '\" . $folderId . \"' in parents and mimeType = 'application/vnd.google-apps.folder'\",\r\n\t\t\t\t\t'pageSize' => '1000',\r\n\t\t\t\t\t'fields' => 'nextPageToken, files(id, name)' );\r\n\t\t\t\t\t\r\n\t\t\tif ($pageToken) \r\n\t\t\t{\r\n\t\t\t\t$parameters['pageToken'] = $pageToken;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t$folders = $drive_service->files->listFiles($parameters);\r\n\t\t\t\r\n\t\t\t$folderMetas = $folders->getFiles();\r\n\t\t\tforeach( $folderMetas as $meta )\r\n\t\t\t{\r\n\t\t\t\tGetFilesRecursively( $meta->id, $relativePath . $meta -> name . '\\\\' );\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t$pageToken = $folders->getNextPageToken();\r\n\t\t} while( $pageToken );\r\n\t}\r\n\tcatch (Google_Service_Exception $e) \r\n\t{\r\n\t\tif ($e->getCode() == 401)\r\n\t\t{\r\n\t\t\tunset($_SESSION['downloadlinkgenerator_accesstoken']);\r\n\t\t\t$_SESSION['downloadlinkgenerator_op'] = 'create';\r\n\t\t\t$redirect_uri = 'http://yasirkula.net/drive/downloadlinkgenerator/oauth2callback.php';\r\n\t\t\theader('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));\r\n\t\t}\r\n\t\telse if ($e->getCode() == 403)\r\n\t\t{\r\n\t\t\tif( ($e->getErrors()[0][\"reason\"] == \"rateLimitExceeded\"\r\n\t\t\t|| $e->getErrors()[0][\"reason\"] == \"userRateLimitExceeded\")) \r\n\t\t\t{\r\n\t\t\t\techo 'Try again in a minute.';\r\n\t\t\t}\r\n\t\t}\r\n\t\telse \r\n\t\t{\r\n\t\t\techo 'ERROR: ' . $e->getMessage();\r\n\t\t}\r\n\t}\r\n}\r\n?>"
  },
  {
    "path": "OLD_VERSION_PHP/oauth2callback.php",
    "content": "<?php\r\nrequire_once '../../../google-api-php-client-2.0.3/vendor/autoload.php';\r\n\r\nsession_start();\r\n\r\n$client = new Google_Client();\r\n$client->setAuthConfig('../../../google-api-php-client-2.0.3/client_id.json');\r\n$client->setRedirectUri('http://yasirkula.net/drive/downloadlinkgenerator/oauth2callback.php');\r\n$client->addScope('https://www.googleapis.com/auth/drive.install');\r\n$client->addScope(Google_Service_Drive::DRIVE_METADATA_READONLY);\r\n\r\nif (! isset($_GET['code'])) {\r\n  $auth_url = $client->createAuthUrl();\r\n  header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));\r\n} else {\r\n  $client->authenticate($_GET['code']);\r\n  $_SESSION['downloadlinkgenerator_accesstoken'] = $client->getAccessToken();\r\n  if (isset($_SESSION['downloadlinkgenerator_op']) && $_SESSION['downloadlinkgenerator_op'] == 'open') \r\n\t$redirect_uri = 'http://yasirkula.net/drive/downloadlinkgenerator/open.php';\r\n  else\r\n\t$redirect_uri = 'http://yasirkula.net/drive/downloadlinkgenerator/create.php';\r\n  header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));\r\n}\r\n?>"
  },
  {
    "path": "OLD_VERSION_PHP/open.php",
    "content": "<?php\r\nrequire_once '../../../google-api-php-client-2.0.3/vendor/autoload.php';\r\n\r\nsession_start();\r\n\r\n$valid = 1;\r\n\r\nif( isset($_GET[\"state\"]) && $_GET[\"state\"] )\r\n{\r\n\t$state = json_decode($_GET[\"state\"]);\r\n\t\r\n\tif( $state->action != 'open' )\r\n\t{\r\n\t\techo 'Invalid state';\r\n\t\t$valid = 0;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif( $state->ids )\r\n\t\t\t$_SESSION['downloadlinkgenerator_openid'] = $state->ids[0];\r\n\t\telse\r\n\t\t{\r\n\t\t\techo 'A document created in Drive does not support direct download. You should first convert it to a downloadable format.';\r\n\t\t\t$valid = 0;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nif( $valid == 1 )\r\n{\r\n\t$client = new Google_Client();\r\n\t$client->setAuthConfig('../../../google-api-php-client-2.0.3/client_id.json');\r\n\t$client->addScope('https://www.googleapis.com/auth/drive.install');\r\n\t$client->addScope(Google_Service_Drive::DRIVE_METADATA_READONLY);\r\n\t\r\n\tif (isset($_SESSION['downloadlinkgenerator_accesstoken']) && $_SESSION['downloadlinkgenerator_accesstoken']) \r\n\t{\r\n\t\tif( !isset($_SESSION['downloadlinkgenerator_openid']) || !isset($_SESSION['downloadlinkgenerator_openid']) )\r\n\t\t{\r\n\t\t\techo 'Use the service with \\'Open\\' button in Drive UI';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t$client->setAccessToken($_SESSION['downloadlinkgenerator_accesstoken']);\r\n\t\t\t$drive_service = new Google_Service_Drive($client);\r\n\t\t\t$result = array();\r\n\t\t\t\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\t$fileMeta = $drive_service->files->get($_SESSION['downloadlinkgenerator_openid'], array( 'fields' => 'mimeType,webContentLink' ));\r\n\t\t\t\tif( $fileMeta->mimeType == 'application/vnd.google-apps.folder' )\r\n\t\t\t\t{\r\n\t\t\t\t\t$_SESSION['downloadlinkgenerator_createid'] = $_SESSION['downloadlinkgenerator_openid'];\r\n\t\t\t\t\t$redirect_uri = 'http://yasirkula.net/drive/downloadlinkgenerator/create.php';\r\n\t\t\t\t\theader('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif( $fileMeta -> webContentLink )\r\n\t\t\t\t\t\techo $fileMeta -> webContentLink;\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\techo 'File is not shared';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tcatch (Google_Service_Exception $e) \r\n\t\t\t{\r\n\t\t\t\tif ($e->getCode() == 401)\r\n\t\t\t\t{\r\n\t\t\t\t\tunset($_SESSION['downloadlinkgenerator_accesstoken']);\r\n\t\t\t\t\t$_SESSION['downloadlinkgenerator_op'] = 'open';\r\n\t\t\t\t\t$redirect_uri = 'http://yasirkula.net/drive/downloadlinkgenerator/oauth2callback.php';\r\n\t\t\t\t\theader('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));\r\n\t\t\t\t}\r\n\t\t\t\telse if ($e->getCode() == 403)\r\n\t\t\t\t{\r\n\t\t\t\t\tif( ($e->getErrors()[0][\"reason\"] == \"rateLimitExceeded\"\r\n\t\t\t\t\t|| $e->getErrors()[0][\"reason\"] == \"userRateLimitExceeded\")) \r\n\t\t\t\t\t{\r\n\t\t\t\t\t\techo 'Try again in a minute.';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse \r\n\t\t\t\t{\r\n\t\t\t\t\techo 'ERROR: ' . $e->getMessage();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse \r\n\t{\r\n\t\t$_SESSION['downloadlinkgenerator_op'] = 'open';\r\n\t\t$redirect_uri = 'http://yasirkula.net/drive/downloadlinkgenerator/oauth2callback.php';\r\n\t\theader('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));\r\n\t}\r\n}\r\n?>"
  },
  {
    "path": "README.md",
    "content": "# Download Link Generator For Google Drive™\n\nThis is the source code of *Download Link Generator For Drive™* extension: https://gsuite.google.com/marketplace/app/download_link_generator_for_drive/631283629814\n\nIt lets users generate a list of their files with their download links in a Google Drive™ folder. Feel free to use this repository as a reference if you are creating your own Google Drive™ extension with similar functionality.\n\n**[GitHub Sponsors ☕](https://github.com/sponsors/yasirkula)**\n\n## How does the extension work?\n\nUsing the [Google Drive API](https://developers.google.com/drive/api/v3/about-sdk), after user authenticates the extension, a number of queries are made as follows:\n\n- If user opened a file with this extension, download link of that file is returned\n- If user opened a folder with this extension, download links for all the files in that folder and any folders underneath it (recursive) are returned\n\n## Why would I want to use this extension?\n\nSay you have a large number of files on Google Drive™ and you want to get a download link for each of these files. The thing is, you don't want to spend so much time sharing each file one by one and copying their download links manually.\n\nInstead, you can open the Drive™ folder that contains your files with this extension and the extension will generate a list of the download links in the following format (one file per line): `{File relative path} {File's download url}`\n\nFor these download links to work everywhere, it is sufficient to make the folder that contains your files public (to do this, you can right click the folder, select \"*Get link*\" and change visibility from \"*Restricted*\" to \"*Anyone with the link*\").\n\nBe aware that downloading big files using a direct download link will prompt a Drive™ dialog stating that \"*The file exceeds the maximum size that Google can scan.*\" and the user must click the \"*Download anyway*\" button to proceed. In C#, you can skip this step using the following WebClient implementation: https://gist.github.com/yasirkula/d0ec0c07b138748e5feaecbd93b6223c\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<meta name=\"Cross-Origin-Opener-Policy\" content=\"same-origin\">\n\t<title>Download Link Generator for Drive™</title>\n\t<link rel=\"icon\" href=\"favicon.png\">\n\t<style>\n\ttable, td\n\t{\n\t\tborder: 1px solid #888;\n\t\tborder-collapse: collapse;\n\t}\n\ttd\n\t{\n\t\tpadding: 0px 4px 0px 4px;\n\t\tborder-left: none;\n\t\tborder-right: none;\n\t}\n\ttd > pre\n\t{\n\t\tmargin: 7px 0px 7px 0px;\n\t}\n\tpre.wordwrapped /* Source: https://stackoverflow.com/a/248013/2373034 */\n\t{\n\t\twhite-space: pre-wrap;       /* Since CSS 2.1 */\n\t\twhite-space: -moz-pre-wrap;  /* Mozilla, since 1999 */\n\t\twhite-space: -pre-wrap;      /* Opera 4-6 */\n\t\twhite-space: -o-pre-wrap;    /* Opera 7 */\n\t\tword-wrap: break-word;       /* Internet Explorer 5.5+ */\n\t}\n\t</style>\n</head>\n<body style=\"font-family: Helvetica, sans-serif; margin-bottom: 60px;\">\n\n\t<div style=\"max-width:680px; margin:0 auto; padding-top: 10px; line-height: 130%;\">\n\t\n\t<h3 style=\"text-align:center;\">Download Link Generator for Drive™</h3>\n\t\n\t<p style=\"text-align:center;\"><a href=\"https://github.com/yasirkula/DownloadLinkGeneratorForGoogleDrive\">Source Code</a> | <a href=\"https://gsuite.google.com/marketplace/app/download_link_generator_for_drive/631283629814\">Marketplace</a></p>\n\t\n\t<p>This Drive™ extension/add-on lets you generate direct download links for the files in your Drive™ storage. Simply right click the file/folder in your Drive™ and select <i>Open with-&gt;Download Link Generator</i>. When a folder is selected, download links for all the files in that folder are generated. If <i>Download Link Generator</i> button isn't present, then you may first need to authorize this extension by clicking the <i>Authorize</i> button below.</p>\n\t\n\t<p>For the generated download links to work everywhere, you need to make the file/folder public. To do this, you can right click the file/folder, select <i>Get link</i> and change visibility from <i>Restricted</i> to <i>Anyone with the link</i>.</p>\n\t\n\t<details><summary><b>Privacy Notice <i>(click to expand)</i></b></summary>\n\t\n\t<p>This extension accesses the metadata of the selected file/folder and reads its download link from that metadata. All communications with the Drive™ servers is handled via the official <i>Drive™ Javascript API</i> and your Drive™ data is not stored in any way in our databases. This extension's use of information received from Google APIs adheres to <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\">Google API Services User Data Policy</a>, including the Limited Use requirements.</p>\n\t\n\t<p>Regarding the \"<i>See information about your Google Drive files</i>\" permission asked during the authorization:</p>\n\t\n\t<ul>\n\t\t<li><b>The titles and descriptions of your files:</b> names and the download links of the files that you've opened with this extension will be accessed</li>\n\t\t<li><b>The names and email addresses of people you share files with:</b> this extension doesn't access this info, this is just a generic text explaining the extents of the permission that this extension asks for. It doesn't necessarily mean that the extension accesses that data</li>\n\t\t<li><b>Your folders and how files are organized:</b> when a folder is opened with this extension, names and download links of all files inside that directory will be read by this extension</li>\n\t</ul>\n\t\n\t<p>This extension is hosted at <i>yasirkula.net</i> website and is subject to its <a href=\"https://yasirkula.net/privacy-policy/\">Privacy Policy</a>.</p>\n\t\n\t</details>\n\t\n\t<p><hr /></p>\n\t\n\t<div id=\"authorize-div\" style=\"display:none\">\n\t\t<b>You need to authorize access to Drive first:</b> <button id=\"authorize-button\" onclick=\"handleAuthClick()\">Authorize</button>\n\t</div>\n\t\n\t<p id=\"pre-check-error\" style=\"color:red; display:none;\"></p>\n\t\n\t<p id=\"result-filters\" style=\"display:none\">\n\t\tDisplay results for: \n\t\t<select onchange=\"onResultsTypeChanged(this.selectedIndex)\">\n\t\t\t<option selected>Download links</option>\n\t\t\t<option>Preview links</option>\n\t\t\t<option>Image embed links</option>\n\t\t</select><br />\n\t\t\n\t\t<input type=\"checkbox\" id=\"result-table-toggle-input\" onchange=\"onResultsDisplayMethodChanged(this)\">\n\t\t<label for=\"result-table-toggle-input\">Display results as table</label>\n\t</p>\n\t\n\t</div>\n\t\n\t<pre id=\"status\" style=\"max-width:680px; margin:0 auto;\" class=\"wordwrapped\"><b>Status: <span style=\"color:blue;\">connecting to Drive™ servers, please wait... <i>(If nothing happens, please make sure that pop-ups are enabled and try again since authorization is handled via a pop-up)</i></span></b></pre><br />\n\t\n\t<div id=\"result-download-links-holder\" style=\"display:block\">\n\t\t<pre id=\"result-download-links\" style=\"display:table; margin:0 auto;\"></pre>\n\t\t<table id=\"result-download-links-table\" style=\"display:none; margin:0 auto;\"></table><br />\n\t</div>\n\t\n\t<div id=\"result-preview-links-holder\" style=\"display:none\">\n\t\t<pre id=\"result-preview-links\" style=\"display:table; margin:0 auto;\"></pre>\n\t\t<table id=\"result-preview-links-table\" style=\"display:none; margin:0 auto;\"></table><br />\n\t</div>\n\t\n\t<div id=\"result-embed-links-holder\" style=\"display:none\">\n\t\t<pre id=\"result-embed-links\" style=\"display:table; margin:0 auto;\"></pre>\n\t\t<table id=\"result-embed-links-table\" style=\"display:none; margin:0 auto;\"></table><br />\n\t</div>\n\t\n\t<pre id=\"error\" style=\"color: red; max-width:680px; margin:0 auto;\" class=\"wordwrapped\"></pre>\n\t\n\t<script>\n\tvar CLIENT_ID = 'YOUR_APP_CLIENT_ID';\n\tvar API_KEY = 'YOUR_APP_API_KEY';\n\tvar DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';\n\tvar SCOPES = 'https://www.googleapis.com/auth/drive.install https://www.googleapis.com/auth/drive.metadata.readonly';\n\n\tvar statusText = document.getElementById( 'status' );\n\tvar errorText = document.getElementById( 'error' );\n\tvar preCheckErrorText = document.getElementById( 'pre-check-error' );\n\tvar authorizeButton = document.getElementById( 'authorize-div' );\n\tvar resultFilters = document.getElementById( 'result-filters' );\n\t\n\tvar resultDownloadLinksHolder = document.getElementById('result-download-links-holder');\n\tvar resultDownloadLinksText = document.getElementById('result-download-links');\n\tvar resultDownloadLinksTable = document.getElementById('result-download-links-table');\n\t\n\tvar resultPreviewLinksHolder = document.getElementById('result-preview-links-holder');\n\tvar resultPreviewLinksText = document.getElementById('result-preview-links');\n\tvar resultPreviewLinksTable = document.getElementById('result-preview-links-table');\n\t\n\tvar resultEmbedLinksHolder = document.getElementById('result-embed-links-holder');\n\tvar resultEmbedLinksText = document.getElementById('result-embed-links');\n\tvar resultEmbedLinksTable = document.getElementById('result-embed-links-table');\n\t\n\tvar userId = \"\";\n\tvar preCheckError = \"You need to right click a file/folder in Drive and select 'Open with->Download Link Generator'.\";\n\t\n\tvar gapiInited = false;\n\tvar gisInited = false;\n\tvar tokenClient;\n\t\n\tvar openedMultipleFilesAtOnce = false;\n\tvar openedFileIdsStack = [];\n\tvar waitingFoldersStack = [];\n\t\n\tfunction gapiLoaded()\n\t{\n\t\tgapi.load( 'client', initializeGapiClient );\n\t}\n\t\n\tasync function initializeGapiClient()\n\t{\n\t\tawait gapi.client.init( {\n\t\t\tapiKey: API_KEY,\n\t\t\tdiscoveryDocs: [DISCOVERY_DOC]\n\t\t} );\n\n\t\tgapiInited = true;\n\t\tcheckAuth();\n\t}\n\t\n\tfunction gisLoaded()\n\t{\n\t\ttokenClient = google.accounts.oauth2.initTokenClient( {\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: SCOPES,\n\t\t\tcallback: ''\n\t\t} );\n\t\t\n\t\tgisInited = true;\n\t\tcheckAuth();\n\t}\n\t\n\tfunction checkAuth()\n\t{\n\t\tif( !gapiInited || !gisInited )\n\t\t\treturn;\n\t\t\n\t\tvar parameters = getQueryVariable( 'state' );\n\t\tif( parameters && parameters.length > 0 )\n\t\t{\n\t\t\tvar parametersJSON = JSON.parse( parameters );\n\t\t\tif( parametersJSON )\n\t\t\t{\n\t\t\t\tif( parametersJSON.action == \"open\" )\n\t\t\t\t{\n\t\t\t\t\tif( parametersJSON.ids )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( parametersJSON.ids.length > 0 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfor( var i = 0; i < parametersJSON.ids.length; i++ ) \n\t\t\t\t\t\t\t\topenedFileIdsStack.push( parametersJSON.ids[i] );\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\topenedMultipleFilesAtOnce = ( openedFileIdsStack.length > 1 );\n\t\t\t\t\t\t\tpreCheckError = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tpreCheckError = \"Download links can't be generated for documents created in Drive. You should first convert the document to a downloadable format (e.g. .doc, .docx, .ppt, .xls).\";\n\t\t\t\t}\n\t\t\t\telse if( parametersJSON.action == \"create\" && parametersJSON.folderId )\n\t\t\t\t{\n\t\t\t\t\topenedFileIdsStack.push( parametersJSON.folderId );\n\t\t\t\t\tpreCheckError = \"\";\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tpreCheckError = \"Invalid state, please try again.\";\n\t\t\t\t\n\t\t\t\tif( parametersJSON.userId )\n\t\t\t\t\tuserId = parametersJSON.userId;\n\t\t\t}\n\t\t}\n\t\t\n\t\ttokenClient.callback = handleAuthResult;\n\t\t\n\t\ttokenClient.requestAccessToken( {\n\t\t\tprompt: 'none',\n\t\t\thint: userId\n\t\t} );\n\t}\n\t\n\tfunction handleAuthClick()\n\t{\n\t\ttokenClient.requestAccessToken( {\n\t\t\tprompt: 'select_account',\n\t\t\thint: userId\n\t\t} );\n\t\t\n\t\treturn false;\n\t}\n\n\tfunction handleAuthResult( authResult )\n\t{\n\t\tif( authResult && !authResult.error && google.accounts.oauth2.hasGrantedAllScopes( authResult, 'https://www.googleapis.com/auth/drive.metadata.readonly' ) )\n\t\t{\n\t\t\tauthorizeButton.style.display = 'none';\n\t\t\t\n\t\t\tif( preCheckError.length > 0 )\n\t\t\t{\n\t\t\t\tstatusText.innerHTML = \"\";\n\t\t\t\tpreCheckErrorText.style.display = 'block';\n\t\t\t\tpreCheckErrorText.innerHTML = \"<b>\" + preCheckError + \"</b>\";\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tresultFilters.style.display = 'block';\n\t\t\t\tstatusText.innerHTML = \"<b>Status: <span style=\\\"color:blue;\\\">generating download link(s), please wait...</span></b>\";\n\t\t\t\t\n\t\t\t\tgetDownloadLinkForOpenedFileOrFolder( openedFileIdsStack.shift() );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tauthorizeButton.style.display = 'block';\n\t\t\tpreCheckErrorText.style.display = 'none';\n\t\t\tstatusText.innerHTML = \"\";\n\t\t}\n\t}\n\t\n\tfunction getDownloadLinkForOpenedFileOrFolder( fileId )\n\t{\n\t\tvar request = gapi.client.drive.files.get({\n\t\t\t'fileId': fileId,\n\t\t\t'fields': \"mimeType, name, webContentLink, webViewLink\",\n\t\t\t'supportsAllDrives': \"true\"\n\t\t});\n\t\t\n\t\trequest.execute( function( resp )\n\t\t{\n\t\t\tif( !resp.error )\n\t\t\t{\n\t\t\t\tif( resp.mimeType == 'application/vnd.google-apps.folder' )\n\t\t\t\t{\n\t\t\t\t\t// If a single folder is opened, don't start the returned relative paths with this folder's name\n\t\t\t\t\t// Otherwise, start the relative paths with the folder's name to distinguish between all opened files and folders\n\t\t\t\t\tvar relativePath = openedMultipleFilesAtOnce ? (resp.name + \"/\") : \"\";\n\t\t\t\t\tgetDownloadLinksInFolderRecursively( fileId, relativePath );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tresp.id = fileId;\n\t\t\t\t\tonFileRetrieved(resp.name, resp, true);\n\t\t\t\t\tdownloadLinksForOpenedFileOrFolderAreGenerated();\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\thandleError( resp.error );\n\t\t});\n\t}\n\t\n\tfunction downloadLinksForOpenedFileOrFolderAreGenerated()\n\t{\n\t\tif( openedFileIdsStack.length > 0 )\n\t\t\tgetDownloadLinkForOpenedFileOrFolder( openedFileIdsStack.shift() );\n\t\telse\n\t\t\tstatusText.innerHTML = \"<b>Status: <span style=\\\"color:green;\\\">finished</span></b>\";\n\t}\n\t\n\tfunction getDownloadLinksInFolderRecursively( folderId, relativePath )\n\t{\n\t\tvar getFiles = function(request) \n\t\t{\n\t\t\trequest.execute(function(resp) \n\t\t\t{\n\t\t\t\tif( !resp.error )\n\t\t\t\t{\n\t\t\t\t\tvar files = resp.files;\n\t\t\t\t\tif (files && files.length > 0) \n\t\t\t\t\t{\n\t\t\t\t\t\tfor (var i = 0; i < files.length; i++) \n\t\t\t\t\t\t\tonFileRetrieved(relativePath + files[i].name, files[i], false);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (resp.nextPageToken)\n\t\t\t\t\t\tgetFiles(getListFilesRequest(folderId, false, resp.nextPageToken));\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tif( waitingFoldersStack.length > 0 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar folderToEnter = waitingFoldersStack.shift();\n\t\t\t\t\t\t\tconsole.log( \"Entering folder: \" + folderToEnter._relativePath );\n\t\t\t\t\t\t\tgetDownloadLinksInFolderRecursively( folderToEnter._id, folderToEnter._relativePath );\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tdownloadLinksForOpenedFileOrFolderAreGenerated();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\thandleError( resp.error );\n\t\t\t});\n\t\t};\n\t\t\n\t\tvar getFolders = function(request) \n\t\t{\n\t\t\trequest.execute(function(resp) \n\t\t\t{\n\t\t\t\tif( !resp.error )\n\t\t\t\t{\n\t\t\t\t\tvar folders = resp.files;\n\t\t\t\t\tif (folders && folders.length > 0) \n\t\t\t\t\t{\n\t\t\t\t\t\tfor (var i = 0; i < folders.length; i++) \n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar folder = folders[i];\n\t\t\t\t\t\t\tif (folder.webViewLink)\n\t\t\t\t\t\t\t\tonPreviewLinkRetrieved(relativePath + folder.name + \"/\", folder.webViewLink);\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\twaitingFoldersStack.push( { _id: folder.id, _relativePath: relativePath + folder.name + \"/\" } );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (resp.nextPageToken)\n\t\t\t\t\t\tgetFolders(getListFilesRequest(folderId, true, resp.nextPageToken));\n\t\t\t\t\telse\n\t\t\t\t\t\tgetFiles(getListFilesRequest(folderId, false, null));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\thandleError( resp.error );\n\t\t\t});\n\t\t};\n\t\t\n\t\tgetFolders(getListFilesRequest(folderId, true, null));\n\t}\n\t\n\tfunction getListFilesRequest(folderId, listFolders, nextPageToken)\n\t{\n\t\tvar query =\n\t\t{\n\t\t\t'q': \"trashed=false and '\" + folderId + \"' in parents and mimeType \" + (listFolders ? \"=\" : \"!=\") + \" 'application/vnd.google-apps.folder'\",\n\t\t\t'pageSize': 1000,\n\t\t\t'fields': \"nextPageToken, files(id, name, webViewLink\" + (listFolders ? \"\" : \", webContentLink\") + \")\",\n\t\t\t'includeItemsFromAllDrives': \"true\",\n\t\t\t'supportsAllDrives': \"true\"\n\t\t};\n\t\t\n\t\tif (nextPageToken != null)\n\t\t\tquery.pageToken = nextPageToken;\n\t\t\n\t\treturn gapi.client.drive.files.list(query);\n\t}\n\t\n\tfunction handleError( err )\n\t{\n\t\tconsole.log( \"ERROR: \" + JSON.stringify( err ) );\n\t\t\n\t\tvar reason = \"\";\n\t\tvar msg = \"\";\n\t\t\n\t\tif( err.errors && err.errors.length > 0 )\n\t\t{\n\t\t\treason = err.errors[0].reason;\n\t\t\tmsg = err.errors[0].message;\n\t\t}\n\t\telse if( err.data && err.data.length > 0 )\n\t\t{\n\t\t\treason = err.data[0].reason;\n\t\t\tmsg = err.data[0].message;\n\t\t}\n\t\t\n\t\tif( err.code == 401 )\n\t\t{\n\t\t\thandleAuthClick();\n\t\t}\n\t\telse if( err.code == 403 )\n\t\t{\n\t\t\tif( reason == \"rateLimitExceeded\" || reason == \"userRateLimitExceeded\" )\n\t\t\t\terrorText.innerHTML = \"Too many requests; try again in a minute.\";\n\t\t\telse if( reason == \"dailyLimitExceeded\" )\n\t\t\t\terrorText.innerHTML = \"App reached daily limit (just wow O_O ); service will be available tomorrow.\";\n\t\t\telse\n\t\t\t\terrorText.innerHTML = err.code + \": \" + err.message + \"(\" + reason + \": \" + msg + \")\\r\\n\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\terrorText.innerHTML = err.code + \": \" + err.message + \" (\" + reason + \": \" + msg + \")\\r\\n\";\n\t\t}\n\t\t\n\t\tstatusText.innerHTML = \"<b>Status: <span style=\\\"color:red;\\\">see error log below</span></b>\";\n\t}\n\t\n\tfunction onFileRetrieved(relativePath, file, showWarningForUnavailableFiles)\n\t{\n\t\tif (file.webContentLink || showWarningForUnavailableFiles)\n\t\t\tonDownloadLinkRetrieved(relativePath, file.webContentLink);\n\t\tif (file.webViewLink || showWarningForUnavailableFiles)\n\t\t\tonPreviewLinkRetrieved(relativePath, file.webViewLink);\n\t\tif (file.id || showWarningForUnavailableFiles)\n\t\t\tonEmbedLinkRetrieved(relativePath, file.id ? (\"https://lh3.googleusercontent.com/d/\" + file.id) : null); // Credit: https://stackoverflow.com/a/70143719/2373034\n\t}\n\t\n\tfunction onDownloadLinkRetrieved(relativePath, link)\n\t{\n\t\tupdateResult(relativePath, link, resultDownloadLinksText, resultDownloadLinksTable);\n\t}\n\t\n\tfunction onPreviewLinkRetrieved(relativePath, link)\n\t{\n\t\tupdateResult(relativePath, link, resultPreviewLinksText, resultPreviewLinksTable);\n\t}\n\t\n\tfunction onEmbedLinkRetrieved(relativePath, link)\n\t{\n\t\tupdateResult(relativePath, link, resultEmbedLinksText, resultEmbedLinksTable);\n\t}\n\t\n\tfunction updateResult(relativePath, link, resultText, resultTable)\n\t{\n\t\tvar newRow = resultTable.insertRow(-1);\n\t\tnewRow.insertCell(0).innerHTML = \"<pre>\" + relativePath + \"</pre>\";\n\t\tif (link != null)\n\t\t{\n\t\t\tresultText.innerHTML += relativePath + \" <a href=\\\"\" + link + \"\\\">\" + link + \"</a>\\r\\n\";\n\t\t\tnewRow.insertCell(1).innerHTML = \"<pre><a href=\\\"\" + link + \"\\\">\" + link + \"</a></pre>\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\tresultText.innerHTML += relativePath + \" Link couldn't be fetched\\r\\n\";\n\t\t\tnewRow.insertCell(1).innerHTML = \"<pre>Link couldn't be fetched</pre>\";\n\t\t}\n\t}\n\t\n\tfunction onResultsDisplayMethodChanged(checkbox)\n\t{\n\t\tresultDownloadLinksText.style.display = resultPreviewLinksText.style.display = resultEmbedLinksText.style.display = checkbox.checked ? 'none' : 'table';\n\t\tresultDownloadLinksTable.style.display = resultPreviewLinksTable.style.display = resultEmbedLinksTable.style.display = checkbox.checked ? 'table' : 'none';\n\t}\n\t\n\tfunction onResultsTypeChanged(resultsType)\n\t{\n\t\tresultDownloadLinksHolder.style.display = (resultsType == \"0\") ? 'block' : 'none';\n\t\tresultPreviewLinksHolder.style.display = (resultsType == \"1\") ? 'block' : 'none';\n\t\tresultEmbedLinksHolder.style.display = (resultsType == \"2\") ? 'block' : 'none';\n\t}\n\t\n\t// Credit: https://css-tricks.com/snippets/javascript/get-url-variables/\n\tfunction getQueryVariable( variable )\n\t{\n\t\tvar query = window.location.search;\n\t\tif( !query || query.length === 0 )\n\t\t\treturn \"\";\n\t\t\n\t\tvar vars = query.substring( 1 ).split( \"&\" );\n\t\tfor( var i = 0; i < vars.length; i++ )\n\t\t{\n\t\t\tvar pair = vars[i].split( \"=\" );\n\t\t\tif( pair[0] == variable )\n\t\t\t\treturn decodeURIComponent( pair[1].replace( /\\+/g, ' ' ) );\n\t\t}\n\t\t\n\t\treturn \"\";\n\t}\n\t</script>\n\t<script async defer src=\"https://apis.google.com/js/api.js\"\n\t\tonload=\"this.onload=function(){};gapiLoaded()\"\n\t\tonreadystatechange=\"if (this.readyState === 'complete') this.onload()\">\n\t</script>\n\t<script async defer src=\"https://accounts.google.com/gsi/client\"\n\t\tonload=\"this.onload=function(){};gisLoaded()\"\n\t\tonreadystatechange=\"if (this.readyState === 'complete') this.onload()\">\n\t</script>\n</body>\n</html>\n"
  }
]