├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1.bug_report.yml │ └── config.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .readme ├── cli.png └── python.png ├── LICENSE ├── Makefile ├── README.md ├── commits.txt ├── gdown ├── __init__.py ├── __main__.py ├── _indent.py ├── cached_download.py ├── download.py ├── download_folder.py ├── exceptions.py ├── extractall.py └── parse_url.py ├── pyproject.toml ├── test.sh └── tests ├── data ├── file_ids.csv ├── file_ids_large.csv ├── folder-page-sample.html └── folder_ids.csv ├── test___main__.py ├── test_cached_download.py ├── test_download.py ├── test_download_folder.py └── test_parse_url.py /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/data/folder-page-sample.html -linguist-detectable 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [wkentaro] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1.bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a bug report 3 | labels: 'bug' 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. 8 | - type: markdown 9 | attributes: 10 | value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions ["Q&A / Help" section](https://github.com/wkentaro/gdown/discussions/categories/q-a-help). 11 | - type: textarea 12 | attributes: 13 | label: Provide environment information 14 | description: Please run `which python; python --version; python -m pip list | grep gdown` in the root directory of your project and paste the results. 15 | validations: 16 | required: true 17 | - type: input 18 | attributes: 19 | label: What OS are you using? 20 | description: 'Please specify the exact version. For example: macOS 12.4, Ubuntu 20.04.4' 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: Describe the Bug 26 | description: A clear and concise description of what the bug is. 27 | validations: 28 | required: true 29 | - type: textarea 30 | attributes: 31 | label: Expected Behavior 32 | description: A clear and concise description of what you expected to happen. 33 | - type: textarea 34 | attributes: 35 | label: To Reproduce 36 | description: Steps to reproduce the behavior, please provide a clear description of how to reproduce the issue, based on the linked minimal reproduction. Screenshots can be provided in the issue body below. If using code blocks, make sure that [syntax highlighting is correct](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) and double check that the rendered preview is not broken. 37 | - type: markdown 38 | attributes: 39 | value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear. 40 | - type: markdown 41 | attributes: 42 | value: Contributors should be able to follow the steps provided in order to reproduce the bug. 43 | - type: markdown 44 | attributes: 45 | value: These steps are used to add integration tests to ensure the same issue does not happen again. Thanks in advance! 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Ideas / Feature request 3 | url: https://github.com/wkentaro/gdown/discussions/categories/ideas-feature-requests 4 | about: Share ideas for new features 5 | - name: Q&A / Help 6 | url: https://github.com/wkentaro/gdown/discussions/categories/q-a-help 7 | about: Ask the community for help 8 | - name: Show and tell 9 | url: https://github.com/wkentaro/gdown/discussions/categories/show-and-tell 10 | about: Show off something you've made 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [macos-latest, ubuntu-latest, windows-latest] 15 | python-version: ["3.8"] 16 | 17 | steps: 18 | - uses: actions/setup-python@v4 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - uses: actions/checkout@v3 22 | 23 | - name: Install Make 24 | run: choco install make 25 | if: matrix.os == 'windows-latest' 26 | 27 | - name: Install main 28 | run: | 29 | pip install .[test] 30 | 31 | - name: Lint 32 | run: | 33 | make lint 34 | if: matrix.os != 'windows-latest' 35 | 36 | - name: Test 37 | run: | 38 | make test 39 | 40 | - name: Install from dist 41 | shell: bash 42 | run: | 43 | make build 44 | pip install dist/gdown-*.tar.gz 45 | pip install dist/gdown-*.whl 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | if: startsWith(github.ref, 'refs/tags/') 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Build Changelog 14 | id: github_release 15 | uses: mikepenz/release-changelog-builder-action@v3 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | with: 19 | configurationJson: | 20 | { 21 | "template": "#{{CHANGELOG}}\n\n
\nUncategorized\n\n#{{UNCATEGORIZED}}\n
\n\n---\n\n- For my daily development update, check [my Twitter/X](https://twitter.com/wkentaro_).\n- If you'd like to support this project, check [my sponsoring page](https://github.com/sponsors/wkentaro).", 22 | "pr_template": "- #{{TITLE}} ##{{NUMBER}}", 23 | "categories": [ 24 | { 25 | "title": "## 🚀 Features", 26 | "labels": ["feature"] 27 | }, 28 | { 29 | "title": "## ✨ Enhancement", 30 | "labels": ["enhancement"] 31 | }, 32 | { 33 | "title": "## 🐛 Fixes", 34 | "labels": ["fix"] 35 | }, 36 | { 37 | "title": "## 💬 Other", 38 | "labels": ["other"] 39 | } 40 | ] 41 | } 42 | 43 | - name: Create Release 44 | uses: mikepenz/action-gh-release@v0.2.0-a03 45 | with: 46 | body: ${{steps.github_release.outputs.changelog}} 47 | draft: true 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cdo] 2 | build/ 3 | dist/ 4 | *.egg-info 5 | -------------------------------------------------------------------------------- /.readme/cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hardcoding-1992/HelperCore/adf57c3337020ccce0f99715a1ff31979a74322d/.readme/cli.png -------------------------------------------------------------------------------- /.readme/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hardcoding-1992/HelperCore/adf57c3337020ccce0f99715a1ff31979a74322d/.readme/python.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Kentaro Wada. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo '## Make commands ##' 3 | @echo 4 | @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | xargs 5 | 6 | lint: 7 | mypy --package gdown 8 | ruff format --check 9 | ruff check 10 | 11 | format: 12 | ruff format 13 | ruff check --fix 14 | 15 | test: 16 | python -m pytest -n auto -v tests 17 | 18 | clean: 19 | rm -rf build dist *.egg-info 20 | 21 | build: clean 22 | python -m build --sdist --wheel 23 | 24 | upload: build 25 | python -m twine upload dist/gdown-* 26 | 27 | publish: build upload 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

gdown

3 |

Google Drive Public File Downloader when Curl/Wget Fails

4 | 5 | 6 |
7 |
8 |
9 | 10 | 11 | *Gdown* downloads a public file/folder from Google Drive. 12 | 13 | *Gdown* provides what curl/wget doesn't for Google Drive: 14 | - **Skip the security notice** allowing you to download large files (curl/wget fails); 15 | - **Recursive download** of files in a folder (maximum 50 files per folder); 16 | - **Specify download file format** for Google Slides/Sheet/Docs like PDF/XML/CSV. 17 | 18 | 19 | ## Installation 20 | 21 | 22 | 23 | 24 | ```bash 25 | pip install gdown 26 | 27 | # to upgrade 28 | pip install --upgrade gdown 29 | ``` 30 | 31 | 32 | ## Usage 33 | 34 | ### via Command Line 35 | 36 | ```bash 37 | $ gdown --help 38 | usage: gdown [-h] [-V] [-O OUTPUT] [-q] [--fuzzy] [--id] [--proxy PROXY] 39 | [--speed SPEED] [--no-cookies] [--no-check-certificate] 40 | [--continue] [--folder] [--remaining-ok] 41 | url_or_id 42 | ... 43 | 44 | $ # a large file (~500MB) 45 | $ gdown https://drive.google.com/uc?id=1l_5RK28JRL19wpT22B-DY9We3TVXnnQQ 46 | $ md5sum fcn8s_from_caffe.npz 47 | 256c2a8235c1c65e62e48d3284fbd384 48 | 49 | $ # same as the above but with the file ID 50 | $ gdown 1l_5RK28JRL19wpT22B-DY9We3TVXnnQQ 51 | 52 | $ # a small file 53 | $ gdown https://drive.google.com/uc?id=0B9P1L--7Wd2vU3VUVlFnbTgtS2c 54 | $ cat spam.txt 55 | spam 56 | 57 | $ # download with fuzzy extraction of a file ID 58 | $ gdown --fuzzy 'https://drive.google.com/file/d/0B9P1L--7Wd2vU3VUVlFnbTgtS2c/view?usp=sharing&resourcekey=0-WWs_XOSctfaY_0-sJBKRSQ' 59 | $ cat spam.txt 60 | spam 61 | 62 | $ # --fuzzy option also works with Microsoft Powerpoint files 63 | $ gdown --fuzzy "https://docs.google.com/presentation/d/15umvZKlsJ3094HNg5S4vJsIhxcFlyTeK/edit?usp=sharing&ouid=117512221203072002113&rtpof=true&sd=true" 64 | 65 | $ # a folder 66 | $ gdown https://drive.google.com/drive/folders/15uNXeRBIhVvZJIhL4yTw4IsStMhUaaxl -O /tmp/folder --folder 67 | 68 | $ # as an alternative to curl/wget 69 | $ gdown https://httpbin.org/ip -O ip.json 70 | $ cat ip.json 71 | { 72 | "origin": "126.169.213.247" 73 | } 74 | 75 | $ # write stdout and pipe to extract 76 | $ gdown https://github.com/wkentaro/gdown/archive/refs/tags/v4.0.0.tar.gz -O - --quiet | tar zxvf - 77 | $ ls gdown-4.0.0/ 78 | gdown github2pypi LICENSE MANIFEST.in pyproject.toml README.md setup.cfg setup.py tests 79 | ``` 80 | 81 | ### via Python 82 | 83 | ```python 84 | import gdown 85 | 86 | # a file 87 | url = "https://drive.google.com/uc?id=1l_5RK28JRL19wpT22B-DY9We3TVXnnQQ" 88 | output = "fcn8s_from_caffe.npz" 89 | gdown.download(url, output) 90 | 91 | # same as the above, but with the file ID 92 | id = "0B9P1L--7Wd2vNm9zMTJWOGxobkU" 93 | gdown.download(id=id, output=output) 94 | 95 | # same as the above, and you can copy-and-paste a URL from Google Drive with fuzzy=True 96 | url = "https://drive.google.com/file/d/0B9P1L--7Wd2vNm9zMTJWOGxobkU/view?usp=sharing" 97 | gdown.download(url=url, output=output, fuzzy=True) 98 | 99 | # Cached download with identity check via MD5 (or SHA1, SHA256, etc). 100 | # Pass postprocess function e.g., extracting compressed file. 101 | md5 = "md5:fa837a88f0c40c513d975104edf3da17" 102 | gdown.cached_download(url, output, hash=hash, postprocess=gdown.extractall) 103 | 104 | # a folder 105 | url = "https://drive.google.com/drive/folders/15uNXeRBIhVvZJIhL4yTw4IsStMhUaaxl" 106 | gdown.download_folder(url) 107 | 108 | # same as the above, but with the folder ID 109 | id = "15uNXeRBIhVvZJIhL4yTw4IsStMhUaaxl" 110 | gdown.download_folder(id=id) 111 | ``` 112 | 113 | 114 | ## FAQ 115 | 116 | ### I get a 'Permission Denied' error. 117 | 118 | Have you made sure you set the file permission to 'Anyone with Link'? 119 | 120 | ### I set the permission 'Anyone with Link', but I still can't download. 121 | 122 | Google restricts access to a file when the download is concentrated. 123 | If you can still access to the file from your browser, downloading cookies file might 124 | help. Follow this step: 1) download cookies.txt using browser extensions like 125 | ([Get cookies.txt LOCALLY](https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)); 126 | 2) mv the `cookies.txt` to `~/.cache/gdown/cookies.txt`; 3) run download again. 127 | If you're using `gdown>=5.0.0`, it should be able to use the cookies same as your browser. 128 | 129 | 130 | ## License 131 | 132 | MIT 133 | -------------------------------------------------------------------------------- /commits.txt: -------------------------------------------------------------------------------- 1 | Commit on 2022-01-03 #1 2 | Commit on 2022-01-03 #2 3 | Commit on 2022-01-03 #3 4 | Commit on 2022-01-03 #4 5 | Commit on 2022-01-03 #5 6 | Commit on 2022-01-03 #6 7 | Commit on 2022-01-03 #7 8 | Commit on 2022-01-03 #8 9 | Commit on 2022-01-03 #9 10 | Commit on 2022-01-03 #10 11 | Commit on 2022-01-03 #11 12 | Commit on 2022-01-03 #12 13 | Commit on 2022-01-04 #1 14 | Commit on 2022-01-04 #2 15 | Commit on 2022-01-04 #3 16 | Commit on 2022-01-04 #4 17 | Commit on 2022-01-05 #1 18 | Commit on 2022-01-05 #2 19 | Commit on 2022-01-05 #3 20 | Commit on 2022-01-05 #4 21 | Commit on 2022-01-05 #5 22 | Commit on 2022-01-05 #6 23 | Commit on 2022-01-05 #7 24 | Commit on 2022-01-05 #8 25 | Commit on 2022-01-05 #9 26 | Commit on 2022-01-05 #10 27 | Commit on 2022-01-06 #1 28 | Commit on 2022-01-06 #2 29 | Commit on 2022-01-06 #3 30 | Commit on 2022-01-06 #4 31 | Commit on 2022-01-06 #5 32 | Commit on 2022-01-06 #6 33 | Commit on 2022-01-06 #7 34 | Commit on 2022-01-06 #8 35 | Commit on 2022-01-07 #1 36 | Commit on 2022-01-07 #2 37 | Commit on 2022-01-07 #3 38 | Commit on 2022-01-07 #4 39 | Commit on 2022-01-07 #5 40 | Commit on 2022-01-07 #6 41 | Commit on 2022-01-07 #7 42 | Commit on 2022-01-07 #8 43 | Commit on 2022-01-08 #1 44 | Commit on 2022-01-08 #2 45 | Commit on 2022-01-08 #3 46 | Commit on 2022-01-08 #4 47 | Commit on 2022-01-08 #5 48 | Commit on 2022-01-08 #6 49 | Commit on 2022-01-08 #7 50 | Commit on 2022-01-08 #8 51 | Commit on 2022-01-08 #9 52 | Commit on 2022-01-08 #10 53 | Commit on 2022-01-08 #11 54 | Commit on 2022-01-08 #12 55 | Commit on 2022-01-10 #1 56 | Commit on 2022-01-10 #2 57 | Commit on 2022-01-10 #3 58 | Commit on 2022-01-10 #4 59 | Commit on 2022-01-10 #5 60 | Commit on 2022-01-10 #6 61 | Commit on 2022-01-10 #7 62 | Commit on 2022-01-10 #8 63 | Commit on 2022-01-10 #9 64 | Commit on 2022-01-11 #1 65 | Commit on 2022-01-11 #2 66 | Commit on 2022-01-11 #3 67 | Commit on 2022-01-11 #4 68 | Commit on 2022-01-11 #5 69 | Commit on 2022-01-12 #1 70 | Commit on 2022-01-12 #2 71 | Commit on 2022-01-12 #3 72 | Commit on 2022-01-12 #4 73 | Commit on 2022-01-12 #5 74 | Commit on 2022-01-12 #6 75 | Commit on 2022-01-12 #7 76 | Commit on 2022-01-13 #1 77 | Commit on 2022-01-13 #2 78 | Commit on 2022-01-13 #3 79 | Commit on 2022-01-13 #4 80 | Commit on 2022-01-13 #5 81 | Commit on 2022-01-13 #6 82 | Commit on 2022-01-13 #7 83 | Commit on 2022-01-13 #8 84 | Commit on 2022-01-14 #1 85 | Commit on 2022-01-14 #2 86 | Commit on 2022-01-14 #3 87 | Commit on 2022-01-14 #4 88 | Commit on 2022-01-14 #5 89 | Commit on 2022-01-14 #6 90 | Commit on 2022-01-15 #1 91 | Commit on 2022-01-15 #2 92 | Commit on 2022-01-15 #3 93 | Commit on 2022-01-15 #4 94 | Commit on 2022-01-15 #5 95 | Commit on 2022-01-17 #1 96 | Commit on 2022-01-17 #2 97 | Commit on 2022-01-17 #3 98 | Commit on 2022-01-17 #4 99 | Commit on 2022-01-17 #5 100 | Commit on 2022-01-17 #6 101 | Commit on 2022-01-17 #7 102 | Commit on 2022-01-17 #8 103 | Commit on 2022-01-17 #9 104 | Commit on 2022-01-18 #1 105 | Commit on 2022-01-18 #2 106 | Commit on 2022-01-18 #3 107 | Commit on 2022-01-18 #4 108 | Commit on 2022-01-18 #5 109 | Commit on 2022-01-18 #6 110 | Commit on 2022-01-18 #7 111 | Commit on 2022-01-18 #8 112 | Commit on 2022-01-18 #9 113 | Commit on 2022-01-18 #10 114 | Commit on 2022-01-18 #11 115 | Commit on 2022-01-19 #1 116 | Commit on 2022-01-19 #2 117 | Commit on 2022-01-19 #3 118 | Commit on 2022-01-19 #4 119 | Commit on 2022-01-20 #1 120 | Commit on 2022-01-20 #2 121 | Commit on 2022-01-20 #3 122 | Commit on 2022-01-20 #4 123 | Commit on 2022-01-20 #5 124 | Commit on 2022-01-20 #6 125 | Commit on 2022-01-20 #7 126 | Commit on 2022-01-20 #8 127 | Commit on 2022-01-20 #9 128 | Commit on 2022-01-20 #10 129 | Commit on 2022-01-21 #1 130 | Commit on 2022-01-21 #2 131 | Commit on 2022-01-21 #3 132 | Commit on 2022-01-21 #4 133 | Commit on 2022-01-21 #5 134 | Commit on 2022-01-21 #6 135 | Commit on 2022-01-24 #1 136 | Commit on 2022-01-24 #2 137 | Commit on 2022-01-24 #3 138 | Commit on 2022-01-24 #4 139 | Commit on 2022-01-24 #5 140 | Commit on 2022-01-24 #6 141 | Commit on 2022-01-24 #7 142 | Commit on 2022-01-25 #1 143 | Commit on 2022-01-25 #2 144 | Commit on 2022-01-25 #3 145 | Commit on 2022-01-25 #4 146 | Commit on 2022-01-26 #1 147 | Commit on 2022-01-26 #2 148 | Commit on 2022-01-26 #3 149 | Commit on 2022-01-26 #4 150 | Commit on 2022-01-26 #5 151 | Commit on 2022-01-26 #6 152 | Commit on 2022-01-27 #1 153 | Commit on 2022-01-27 #2 154 | Commit on 2022-01-27 #3 155 | Commit on 2022-01-27 #4 156 | Commit on 2022-01-27 #5 157 | Commit on 2022-01-27 #6 158 | Commit on 2022-01-27 #7 159 | Commit on 2022-01-27 #8 160 | Commit on 2022-01-27 #9 161 | Commit on 2022-01-27 #10 162 | Commit on 2022-01-28 #1 163 | Commit on 2022-01-28 #2 164 | Commit on 2022-01-28 #3 165 | Commit on 2022-01-28 #4 166 | Commit on 2022-01-28 #5 167 | Commit on 2022-01-28 #6 168 | Commit on 2022-01-28 #7 169 | Commit on 2022-01-28 #8 170 | Commit on 2022-01-31 #1 171 | Commit on 2022-01-31 #2 172 | Commit on 2022-01-31 #3 173 | Commit on 2022-01-31 #4 174 | Commit on 2022-01-31 #5 175 | Commit on 2022-01-31 #6 176 | Commit on 2022-01-31 #7 177 | Commit on 2022-01-31 #8 178 | Commit on 2022-01-31 #9 179 | Commit on 2022-02-01 #1 180 | Commit on 2022-02-01 #2 181 | Commit on 2022-02-01 #3 182 | Commit on 2022-02-01 #4 183 | Commit on 2022-02-01 #5 184 | Commit on 2022-02-01 #6 185 | Commit on 2022-02-01 #7 186 | Commit on 2022-02-01 #8 187 | Commit on 2022-02-01 #9 188 | Commit on 2022-02-01 #10 189 | Commit on 2022-02-01 #11 190 | Commit on 2022-02-02 #1 191 | Commit on 2022-02-02 #2 192 | Commit on 2022-02-02 #3 193 | Commit on 2022-02-02 #4 194 | Commit on 2022-02-02 #5 195 | Commit on 2022-02-03 #1 196 | Commit on 2022-02-03 #2 197 | Commit on 2022-02-03 #3 198 | Commit on 2022-02-03 #4 199 | Commit on 2022-02-03 #5 200 | Commit on 2022-02-03 #6 201 | Commit on 2022-02-03 #7 202 | Commit on 2022-02-03 #8 203 | Commit on 2022-02-04 #1 204 | Commit on 2022-02-04 #2 205 | Commit on 2022-02-04 #3 206 | Commit on 2022-02-04 #4 207 | Commit on 2022-02-04 #5 208 | Commit on 2022-02-04 #6 209 | Commit on 2022-02-04 #7 210 | Commit on 2022-02-04 #8 211 | Commit on 2022-02-07 #1 212 | Commit on 2022-02-07 #2 213 | Commit on 2022-02-07 #3 214 | Commit on 2022-02-07 #4 215 | Commit on 2022-02-07 #5 216 | Commit on 2022-02-07 #6 217 | Commit on 2022-02-07 #7 218 | Commit on 2022-02-07 #8 219 | Commit on 2022-02-07 #9 220 | Commit on 2022-02-07 #10 221 | Commit on 2022-02-08 #1 222 | Commit on 2022-02-08 #2 223 | Commit on 2022-02-08 #3 224 | Commit on 2022-02-08 #4 225 | Commit on 2022-02-09 #1 226 | Commit on 2022-02-09 #2 227 | Commit on 2022-02-09 #3 228 | Commit on 2022-02-09 #4 229 | Commit on 2022-02-09 #5 230 | Commit on 2022-02-09 #6 231 | Commit on 2022-02-09 #7 232 | Commit on 2022-02-09 #8 233 | Commit on 2022-02-09 #9 234 | Commit on 2022-02-09 #10 235 | Commit on 2022-02-09 #11 236 | Commit on 2022-02-09 #12 237 | Commit on 2022-02-10 #1 238 | Commit on 2022-02-10 #2 239 | Commit on 2022-02-10 #3 240 | Commit on 2022-02-10 #4 241 | Commit on 2022-02-11 #1 242 | Commit on 2022-02-11 #2 243 | Commit on 2022-02-11 #3 244 | Commit on 2022-02-11 #4 245 | Commit on 2022-02-11 #5 246 | Commit on 2022-02-11 #6 247 | Commit on 2022-02-11 #7 248 | Commit on 2022-02-11 #8 249 | Commit on 2022-02-11 #9 250 | Commit on 2022-02-14 #1 251 | Commit on 2022-02-14 #2 252 | Commit on 2022-02-14 #3 253 | Commit on 2022-02-14 #4 254 | Commit on 2022-02-14 #5 255 | Commit on 2022-02-14 #6 256 | Commit on 2022-02-14 #7 257 | Commit on 2022-02-14 #8 258 | Commit on 2022-02-14 #9 259 | Commit on 2022-02-14 #10 260 | Commit on 2022-02-14 #11 261 | Commit on 2022-02-14 #12 262 | Commit on 2022-02-15 #1 263 | Commit on 2022-02-15 #2 264 | Commit on 2022-02-15 #3 265 | Commit on 2022-02-15 #4 266 | Commit on 2022-02-15 #5 267 | Commit on 2022-02-15 #6 268 | Commit on 2022-02-15 #7 269 | Commit on 2022-02-15 #8 270 | Commit on 2022-02-15 #9 271 | Commit on 2022-02-15 #10 272 | Commit on 2022-02-16 #1 273 | Commit on 2022-02-16 #2 274 | Commit on 2022-02-16 #3 275 | Commit on 2022-02-16 #4 276 | Commit on 2022-02-16 #5 277 | Commit on 2022-02-17 #1 278 | Commit on 2022-02-17 #2 279 | Commit on 2022-02-17 #3 280 | Commit on 2022-02-17 #4 281 | Commit on 2022-02-17 #5 282 | Commit on 2022-02-17 #6 283 | Commit on 2022-02-17 #7 284 | Commit on 2022-02-17 #8 285 | Commit on 2022-02-17 #9 286 | Commit on 2022-02-17 #10 287 | Commit on 2022-02-17 #11 288 | Commit on 2022-02-18 #1 289 | Commit on 2022-02-18 #2 290 | Commit on 2022-02-18 #3 291 | Commit on 2022-02-18 #4 292 | Commit on 2022-02-18 #5 293 | Commit on 2022-02-18 #6 294 | Commit on 2022-02-18 #7 295 | Commit on 2022-02-18 #8 296 | Commit on 2022-02-18 #9 297 | Commit on 2022-02-18 #10 298 | Commit on 2022-02-18 #11 299 | Commit on 2022-02-19 #1 300 | Commit on 2022-02-19 #2 301 | Commit on 2022-02-19 #3 302 | Commit on 2022-02-19 #4 303 | Commit on 2022-02-19 #5 304 | Commit on 2022-02-19 #6 305 | Commit on 2022-02-19 #7 306 | Commit on 2022-02-19 #8 307 | Commit on 2022-02-19 #9 308 | Commit on 2022-02-19 #10 309 | Commit on 2022-02-21 #1 310 | Commit on 2022-02-21 #2 311 | Commit on 2022-02-21 #3 312 | Commit on 2022-02-21 #4 313 | Commit on 2022-02-21 #5 314 | Commit on 2022-02-21 #6 315 | Commit on 2022-02-22 #1 316 | Commit on 2022-02-22 #2 317 | Commit on 2022-02-22 #3 318 | Commit on 2022-02-22 #4 319 | Commit on 2022-02-23 #1 320 | Commit on 2022-02-23 #2 321 | Commit on 2022-02-23 #3 322 | Commit on 2022-02-23 #4 323 | Commit on 2022-02-23 #5 324 | Commit on 2022-02-23 #6 325 | Commit on 2022-02-24 #1 326 | Commit on 2022-02-24 #2 327 | Commit on 2022-02-24 #3 328 | Commit on 2022-02-24 #4 329 | Commit on 2022-02-25 #1 330 | Commit on 2022-02-25 #2 331 | Commit on 2022-02-25 #3 332 | Commit on 2022-02-25 #4 333 | Commit on 2022-02-25 #5 334 | Commit on 2022-02-25 #6 335 | Commit on 2022-02-25 #7 336 | Commit on 2022-02-25 #8 337 | Commit on 2022-02-25 #9 338 | Commit on 2022-02-25 #10 339 | Commit on 2022-02-25 #11 340 | Commit on 2022-02-25 #12 341 | Commit on 2022-02-26 #1 342 | Commit on 2022-02-26 #2 343 | Commit on 2022-02-26 #3 344 | Commit on 2022-02-26 #4 345 | Commit on 2022-02-26 #5 346 | Commit on 2022-02-26 #6 347 | Commit on 2022-02-26 #7 348 | Commit on 2022-02-26 #8 349 | Commit on 2022-02-26 #9 350 | Commit on 2022-02-28 #1 351 | Commit on 2022-02-28 #2 352 | Commit on 2022-02-28 #3 353 | Commit on 2022-02-28 #4 354 | Commit on 2022-02-28 #5 355 | Commit on 2022-02-28 #6 356 | Commit on 2022-03-01 #1 357 | Commit on 2022-03-01 #2 358 | Commit on 2022-03-01 #3 359 | Commit on 2022-03-01 #4 360 | Commit on 2022-03-01 #5 361 | Commit on 2022-03-01 #6 362 | Commit on 2022-03-01 #7 363 | Commit on 2022-03-02 #1 364 | Commit on 2022-03-02 #2 365 | Commit on 2022-03-02 #3 366 | Commit on 2022-03-02 #4 367 | Commit on 2022-03-02 #5 368 | Commit on 2022-03-02 #6 369 | Commit on 2022-03-02 #7 370 | Commit on 2022-03-02 #8 371 | Commit on 2022-03-02 #9 372 | Commit on 2022-03-02 #10 373 | Commit on 2022-03-02 #11 374 | Commit on 2022-03-03 #1 375 | Commit on 2022-03-03 #2 376 | Commit on 2022-03-03 #3 377 | Commit on 2022-03-03 #4 378 | Commit on 2022-03-03 #5 379 | Commit on 2022-03-03 #6 380 | Commit on 2022-03-03 #7 381 | Commit on 2022-03-03 #8 382 | Commit on 2022-03-03 #9 383 | Commit on 2022-03-03 #10 384 | Commit on 2022-03-04 #1 385 | Commit on 2022-03-04 #2 386 | Commit on 2022-03-04 #3 387 | Commit on 2022-03-04 #4 388 | Commit on 2022-03-04 #5 389 | Commit on 2022-03-04 #6 390 | Commit on 2022-03-04 #7 391 | Commit on 2022-03-04 #8 392 | Commit on 2022-03-07 #1 393 | Commit on 2022-03-07 #2 394 | Commit on 2022-03-07 #3 395 | Commit on 2022-03-07 #4 396 | Commit on 2022-03-07 #5 397 | Commit on 2022-03-07 #6 398 | Commit on 2022-03-07 #7 399 | Commit on 2022-03-07 #8 400 | Commit on 2022-03-07 #9 401 | Commit on 2022-03-08 #1 402 | Commit on 2022-03-08 #2 403 | Commit on 2022-03-08 #3 404 | Commit on 2022-03-08 #4 405 | Commit on 2022-03-08 #5 406 | Commit on 2022-03-08 #6 407 | Commit on 2022-03-08 #7 408 | Commit on 2022-03-08 #8 409 | Commit on 2022-03-08 #9 410 | Commit on 2022-03-09 #1 411 | Commit on 2022-03-09 #2 412 | Commit on 2022-03-09 #3 413 | Commit on 2022-03-09 #4 414 | Commit on 2022-03-09 #5 415 | Commit on 2022-03-09 #6 416 | Commit on 2022-03-09 #7 417 | Commit on 2022-03-09 #8 418 | Commit on 2022-03-09 #9 419 | Commit on 2022-03-09 #10 420 | Commit on 2022-03-09 #11 421 | Commit on 2022-03-09 #12 422 | Commit on 2022-03-10 #1 423 | Commit on 2022-03-10 #2 424 | Commit on 2022-03-10 #3 425 | Commit on 2022-03-10 #4 426 | Commit on 2022-03-10 #5 427 | Commit on 2022-03-10 #6 428 | Commit on 2022-03-10 #7 429 | Commit on 2022-03-10 #8 430 | Commit on 2022-03-10 #9 431 | Commit on 2022-03-10 #10 432 | Commit on 2022-03-10 #11 433 | Commit on 2022-03-10 #12 434 | Commit on 2022-03-11 #1 435 | Commit on 2022-03-11 #2 436 | Commit on 2022-03-11 #3 437 | Commit on 2022-03-11 #4 438 | Commit on 2022-03-11 #5 439 | Commit on 2022-03-11 #6 440 | Commit on 2022-03-11 #7 441 | Commit on 2022-03-11 #8 442 | Commit on 2022-03-12 #1 443 | Commit on 2022-03-12 #2 444 | Commit on 2022-03-12 #3 445 | Commit on 2022-03-12 #4 446 | Commit on 2022-03-12 #5 447 | Commit on 2022-03-12 #6 448 | Commit on 2022-03-12 #7 449 | Commit on 2022-03-12 #8 450 | Commit on 2022-03-14 #1 451 | Commit on 2022-03-14 #2 452 | Commit on 2022-03-14 #3 453 | Commit on 2022-03-14 #4 454 | Commit on 2022-03-14 #5 455 | Commit on 2022-03-15 #1 456 | Commit on 2022-03-15 #2 457 | Commit on 2022-03-15 #3 458 | Commit on 2022-03-15 #4 459 | Commit on 2022-03-15 #5 460 | Commit on 2022-03-15 #6 461 | Commit on 2022-03-15 #7 462 | Commit on 2022-03-15 #8 463 | Commit on 2022-03-15 #9 464 | Commit on 2022-03-15 #10 465 | Commit on 2022-03-15 #11 466 | Commit on 2022-03-15 #12 467 | Commit on 2022-03-16 #1 468 | Commit on 2022-03-16 #2 469 | Commit on 2022-03-16 #3 470 | Commit on 2022-03-16 #4 471 | Commit on 2022-03-17 #1 472 | Commit on 2022-03-17 #2 473 | Commit on 2022-03-17 #3 474 | Commit on 2022-03-17 #4 475 | Commit on 2022-03-17 #5 476 | Commit on 2022-03-17 #6 477 | Commit on 2022-03-17 #7 478 | Commit on 2022-03-17 #8 479 | Commit on 2022-03-17 #9 480 | Commit on 2022-03-18 #1 481 | Commit on 2022-03-18 #2 482 | Commit on 2022-03-18 #3 483 | Commit on 2022-03-18 #4 484 | Commit on 2022-03-18 #5 485 | Commit on 2022-03-18 #6 486 | Commit on 2022-03-18 #7 487 | Commit on 2022-03-19 #1 488 | Commit on 2022-03-19 #2 489 | Commit on 2022-03-19 #3 490 | Commit on 2022-03-19 #4 491 | Commit on 2022-03-19 #5 492 | Commit on 2022-03-19 #6 493 | Commit on 2022-03-19 #7 494 | Commit on 2022-03-21 #1 495 | Commit on 2022-03-21 #2 496 | Commit on 2022-03-21 #3 497 | Commit on 2022-03-21 #4 498 | Commit on 2022-03-21 #5 499 | Commit on 2022-03-21 #6 500 | Commit on 2022-03-22 #1 501 | Commit on 2022-03-22 #2 502 | Commit on 2022-03-22 #3 503 | Commit on 2022-03-22 #4 504 | Commit on 2022-03-22 #5 505 | Commit on 2022-03-22 #6 506 | Commit on 2022-03-22 #7 507 | Commit on 2022-03-22 #8 508 | Commit on 2022-03-22 #9 509 | Commit on 2022-03-22 #10 510 | Commit on 2022-03-22 #11 511 | Commit on 2022-03-22 #12 512 | Commit on 2022-03-23 #1 513 | Commit on 2022-03-23 #2 514 | Commit on 2022-03-23 #3 515 | Commit on 2022-03-23 #4 516 | Commit on 2022-03-23 #5 517 | Commit on 2022-03-23 #6 518 | Commit on 2022-03-23 #7 519 | Commit on 2022-03-23 #8 520 | Commit on 2022-03-23 #9 521 | Commit on 2022-03-23 #10 522 | Commit on 2022-03-23 #11 523 | Commit on 2022-03-24 #1 524 | Commit on 2022-03-24 #2 525 | Commit on 2022-03-24 #3 526 | Commit on 2022-03-24 #4 527 | Commit on 2022-03-24 #5 528 | Commit on 2022-03-24 #6 529 | Commit on 2022-03-24 #7 530 | Commit on 2022-03-25 #1 531 | Commit on 2022-03-25 #2 532 | Commit on 2022-03-25 #3 533 | Commit on 2022-03-25 #4 534 | Commit on 2022-03-25 #5 535 | Commit on 2022-03-26 #1 536 | Commit on 2022-03-26 #2 537 | Commit on 2022-03-26 #3 538 | Commit on 2022-03-26 #4 539 | Commit on 2022-03-26 #5 540 | Commit on 2022-03-26 #6 541 | Commit on 2022-03-26 #7 542 | Commit on 2022-03-26 #8 543 | Commit on 2022-03-26 #9 544 | Commit on 2022-03-26 #10 545 | Commit on 2022-03-26 #11 546 | Commit on 2022-03-26 #12 547 | Commit on 2022-03-28 #1 548 | Commit on 2022-03-28 #2 549 | Commit on 2022-03-28 #3 550 | Commit on 2022-03-28 #4 551 | Commit on 2022-03-28 #5 552 | Commit on 2022-03-29 #1 553 | Commit on 2022-03-29 #2 554 | Commit on 2022-03-29 #3 555 | Commit on 2022-03-29 #4 556 | Commit on 2022-03-29 #5 557 | Commit on 2022-03-29 #6 558 | Commit on 2022-03-29 #7 559 | Commit on 2022-03-30 #1 560 | Commit on 2022-03-30 #2 561 | Commit on 2022-03-30 #3 562 | Commit on 2022-03-30 #4 563 | Commit on 2022-03-30 #5 564 | Commit on 2022-03-30 #6 565 | Commit on 2022-03-31 #1 566 | Commit on 2022-03-31 #2 567 | Commit on 2022-03-31 #3 568 | Commit on 2022-03-31 #4 569 | Commit on 2022-03-31 #5 570 | Commit on 2022-04-01 #1 571 | Commit on 2022-04-01 #2 572 | Commit on 2022-04-01 #3 573 | Commit on 2022-04-01 #4 574 | Commit on 2022-04-01 #5 575 | Commit on 2022-04-01 #6 576 | Commit on 2022-04-01 #7 577 | Commit on 2022-04-01 #8 578 | Commit on 2022-04-01 #9 579 | Commit on 2022-04-01 #10 580 | Commit on 2022-04-02 #1 581 | Commit on 2022-04-02 #2 582 | Commit on 2022-04-02 #3 583 | Commit on 2022-04-02 #4 584 | Commit on 2022-04-02 #5 585 | Commit on 2022-04-02 #6 586 | Commit on 2022-04-02 #7 587 | Commit on 2022-04-02 #8 588 | Commit on 2022-04-02 #9 589 | Commit on 2022-04-04 #1 590 | Commit on 2022-04-04 #2 591 | Commit on 2022-04-04 #3 592 | Commit on 2022-04-04 #4 593 | Commit on 2022-04-04 #5 594 | Commit on 2022-04-04 #6 595 | Commit on 2022-04-04 #7 596 | Commit on 2022-04-04 #8 597 | Commit on 2022-04-04 #9 598 | Commit on 2022-04-04 #10 599 | Commit on 2022-04-05 #1 600 | Commit on 2022-04-05 #2 601 | Commit on 2022-04-05 #3 602 | Commit on 2022-04-05 #4 603 | Commit on 2022-04-05 #5 604 | Commit on 2022-04-05 #6 605 | Commit on 2022-04-05 #7 606 | Commit on 2022-04-05 #8 607 | Commit on 2022-04-05 #9 608 | Commit on 2022-04-05 #10 609 | Commit on 2022-04-05 #11 610 | Commit on 2022-04-06 #1 611 | Commit on 2022-04-06 #2 612 | Commit on 2022-04-06 #3 613 | Commit on 2022-04-06 #4 614 | Commit on 2022-04-06 #5 615 | Commit on 2022-04-06 #6 616 | Commit on 2022-04-06 #7 617 | Commit on 2022-04-06 #8 618 | Commit on 2022-04-06 #9 619 | Commit on 2022-04-06 #10 620 | Commit on 2022-04-07 #1 621 | Commit on 2022-04-07 #2 622 | Commit on 2022-04-07 #3 623 | Commit on 2022-04-07 #4 624 | Commit on 2022-04-07 #5 625 | Commit on 2022-04-07 #6 626 | Commit on 2022-04-07 #7 627 | Commit on 2022-04-08 #1 628 | Commit on 2022-04-08 #2 629 | Commit on 2022-04-08 #3 630 | Commit on 2022-04-08 #4 631 | Commit on 2022-04-08 #5 632 | Commit on 2022-04-08 #6 633 | Commit on 2022-04-08 #7 634 | Commit on 2022-04-08 #8 635 | Commit on 2022-04-08 #9 636 | Commit on 2022-04-09 #1 637 | Commit on 2022-04-09 #2 638 | Commit on 2022-04-09 #3 639 | Commit on 2022-04-09 #4 640 | Commit on 2022-04-11 #1 641 | Commit on 2022-04-11 #2 642 | Commit on 2022-04-11 #3 643 | Commit on 2022-04-11 #4 644 | Commit on 2022-04-11 #5 645 | Commit on 2022-04-12 #1 646 | Commit on 2022-04-12 #2 647 | Commit on 2022-04-12 #3 648 | Commit on 2022-04-12 #4 649 | Commit on 2022-04-12 #5 650 | Commit on 2022-04-12 #6 651 | Commit on 2022-04-12 #7 652 | Commit on 2022-04-13 #1 653 | Commit on 2022-04-13 #2 654 | Commit on 2022-04-13 #3 655 | Commit on 2022-04-13 #4 656 | Commit on 2022-04-13 #5 657 | Commit on 2022-04-14 #1 658 | Commit on 2022-04-14 #2 659 | Commit on 2022-04-14 #3 660 | Commit on 2022-04-14 #4 661 | Commit on 2022-04-14 #5 662 | Commit on 2022-04-14 #6 663 | Commit on 2022-04-15 #1 664 | Commit on 2022-04-15 #2 665 | Commit on 2022-04-15 #3 666 | Commit on 2022-04-15 #4 667 | Commit on 2022-04-15 #5 668 | Commit on 2022-04-15 #6 669 | Commit on 2022-04-15 #7 670 | Commit on 2022-04-15 #8 671 | Commit on 2022-04-16 #1 672 | Commit on 2022-04-16 #2 673 | Commit on 2022-04-16 #3 674 | Commit on 2022-04-16 #4 675 | Commit on 2022-04-16 #5 676 | Commit on 2022-04-16 #6 677 | Commit on 2022-04-18 #1 678 | Commit on 2022-04-18 #2 679 | Commit on 2022-04-18 #3 680 | Commit on 2022-04-18 #4 681 | Commit on 2022-04-18 #5 682 | Commit on 2022-04-18 #6 683 | Commit on 2022-04-18 #7 684 | Commit on 2022-04-18 #8 685 | Commit on 2022-04-19 #1 686 | Commit on 2022-04-19 #2 687 | Commit on 2022-04-19 #3 688 | Commit on 2022-04-19 #4 689 | Commit on 2022-04-19 #5 690 | Commit on 2022-04-20 #1 691 | Commit on 2022-04-20 #2 692 | Commit on 2022-04-20 #3 693 | Commit on 2022-04-20 #4 694 | Commit on 2022-04-20 #5 695 | Commit on 2022-04-20 #6 696 | Commit on 2022-04-20 #7 697 | Commit on 2022-04-20 #8 698 | Commit on 2022-04-20 #9 699 | Commit on 2022-04-20 #10 700 | Commit on 2022-04-20 #11 701 | Commit on 2022-04-21 #1 702 | Commit on 2022-04-21 #2 703 | Commit on 2022-04-21 #3 704 | Commit on 2022-04-21 #4 705 | Commit on 2022-04-21 #5 706 | Commit on 2022-04-21 #6 707 | Commit on 2022-04-21 #7 708 | Commit on 2022-04-21 #8 709 | Commit on 2022-04-22 #1 710 | Commit on 2022-04-22 #2 711 | Commit on 2022-04-22 #3 712 | Commit on 2022-04-22 #4 713 | Commit on 2022-04-22 #5 714 | Commit on 2022-04-22 #6 715 | Commit on 2022-04-22 #7 716 | Commit on 2022-04-22 #8 717 | Commit on 2022-04-22 #9 718 | Commit on 2022-04-22 #10 719 | Commit on 2022-04-22 #11 720 | Commit on 2022-04-25 #1 721 | Commit on 2022-04-25 #2 722 | Commit on 2022-04-25 #3 723 | Commit on 2022-04-25 #4 724 | Commit on 2022-04-25 #5 725 | Commit on 2022-04-25 #6 726 | Commit on 2022-04-25 #7 727 | Commit on 2022-04-25 #8 728 | Commit on 2022-04-25 #9 729 | Commit on 2022-04-26 #1 730 | Commit on 2022-04-26 #2 731 | Commit on 2022-04-26 #3 732 | Commit on 2022-04-26 #4 733 | Commit on 2022-04-26 #5 734 | Commit on 2022-04-26 #6 735 | Commit on 2022-04-26 #7 736 | Commit on 2022-04-26 #8 737 | Commit on 2022-04-26 #9 738 | Commit on 2022-04-26 #10 739 | Commit on 2022-04-27 #1 740 | Commit on 2022-04-27 #2 741 | Commit on 2022-04-27 #3 742 | Commit on 2022-04-27 #4 743 | Commit on 2022-04-28 #1 744 | Commit on 2022-04-28 #2 745 | Commit on 2022-04-28 #3 746 | Commit on 2022-04-28 #4 747 | Commit on 2022-04-28 #5 748 | Commit on 2022-04-28 #6 749 | Commit on 2022-04-28 #7 750 | Commit on 2022-04-28 #8 751 | Commit on 2022-04-28 #9 752 | Commit on 2022-04-28 #10 753 | Commit on 2022-04-28 #11 754 | Commit on 2022-04-29 #1 755 | Commit on 2022-04-29 #2 756 | Commit on 2022-04-29 #3 757 | Commit on 2022-04-29 #4 758 | Commit on 2022-04-29 #5 759 | Commit on 2022-04-29 #6 760 | Commit on 2022-04-29 #7 761 | Commit on 2022-04-29 #8 762 | Commit on 2022-04-29 #9 763 | Commit on 2022-04-30 #1 764 | Commit on 2022-04-30 #2 765 | Commit on 2022-04-30 #3 766 | Commit on 2022-04-30 #4 767 | Commit on 2022-04-30 #5 768 | Commit on 2022-04-30 #6 769 | Commit on 2022-04-30 #7 770 | Commit on 2022-05-02 #1 771 | Commit on 2022-05-02 #2 772 | Commit on 2022-05-02 #3 773 | Commit on 2022-05-02 #4 774 | Commit on 2022-05-02 #5 775 | Commit on 2022-05-02 #6 776 | Commit on 2022-05-03 #1 777 | Commit on 2022-05-03 #2 778 | Commit on 2022-05-03 #3 779 | Commit on 2022-05-03 #4 780 | Commit on 2022-05-03 #5 781 | Commit on 2022-05-03 #6 782 | Commit on 2022-05-03 #7 783 | Commit on 2022-05-03 #8 784 | Commit on 2022-05-03 #9 785 | Commit on 2022-05-03 #10 786 | Commit on 2022-05-04 #1 787 | Commit on 2022-05-04 #2 788 | Commit on 2022-05-04 #3 789 | Commit on 2022-05-04 #4 790 | Commit on 2022-05-04 #5 791 | Commit on 2022-05-04 #6 792 | Commit on 2022-05-04 #7 793 | Commit on 2022-05-04 #8 794 | Commit on 2022-05-04 #9 795 | Commit on 2022-05-04 #10 796 | Commit on 2022-05-05 #1 797 | Commit on 2022-05-05 #2 798 | Commit on 2022-05-05 #3 799 | Commit on 2022-05-05 #4 800 | Commit on 2022-05-05 #5 801 | Commit on 2022-05-06 #1 802 | Commit on 2022-05-06 #2 803 | Commit on 2022-05-06 #3 804 | Commit on 2022-05-06 #4 805 | Commit on 2022-05-06 #5 806 | Commit on 2022-05-06 #6 807 | Commit on 2022-05-06 #7 808 | Commit on 2022-05-06 #8 809 | Commit on 2022-05-06 #9 810 | Commit on 2022-05-07 #1 811 | Commit on 2022-05-07 #2 812 | Commit on 2022-05-07 #3 813 | Commit on 2022-05-07 #4 814 | Commit on 2022-05-07 #5 815 | Commit on 2022-05-07 #6 816 | Commit on 2022-05-09 #1 817 | Commit on 2022-05-09 #2 818 | Commit on 2022-05-09 #3 819 | Commit on 2022-05-09 #4 820 | Commit on 2022-05-09 #5 821 | Commit on 2022-05-10 #1 822 | Commit on 2022-05-10 #2 823 | Commit on 2022-05-10 #3 824 | Commit on 2022-05-10 #4 825 | Commit on 2022-05-11 #1 826 | Commit on 2022-05-11 #2 827 | Commit on 2022-05-11 #3 828 | Commit on 2022-05-11 #4 829 | Commit on 2022-05-11 #5 830 | Commit on 2022-05-12 #1 831 | Commit on 2022-05-12 #2 832 | Commit on 2022-05-12 #3 833 | Commit on 2022-05-12 #4 834 | Commit on 2022-05-12 #5 835 | Commit on 2022-05-13 #1 836 | Commit on 2022-05-13 #2 837 | Commit on 2022-05-13 #3 838 | Commit on 2022-05-13 #4 839 | Commit on 2022-05-13 #5 840 | Commit on 2022-05-13 #6 841 | Commit on 2022-05-13 #7 842 | Commit on 2022-05-13 #8 843 | Commit on 2022-05-14 #1 844 | Commit on 2022-05-14 #2 845 | Commit on 2022-05-14 #3 846 | Commit on 2022-05-14 #4 847 | Commit on 2022-05-14 #5 848 | Commit on 2022-05-14 #6 849 | Commit on 2022-05-14 #7 850 | Commit on 2022-05-16 #1 851 | Commit on 2022-05-16 #2 852 | Commit on 2022-05-16 #3 853 | Commit on 2022-05-16 #4 854 | Commit on 2022-05-16 #5 855 | Commit on 2022-05-16 #6 856 | Commit on 2022-05-16 #7 857 | Commit on 2022-05-16 #8 858 | Commit on 2022-05-16 #9 859 | Commit on 2022-05-16 #10 860 | Commit on 2022-05-16 #11 861 | Commit on 2022-05-16 #12 862 | Commit on 2022-05-17 #1 863 | Commit on 2022-05-17 #2 864 | Commit on 2022-05-17 #3 865 | Commit on 2022-05-17 #4 866 | Commit on 2022-05-17 #5 867 | Commit on 2022-05-17 #6 868 | Commit on 2022-05-17 #7 869 | Commit on 2022-05-17 #8 870 | Commit on 2022-05-17 #9 871 | Commit on 2022-05-17 #10 872 | Commit on 2022-05-17 #11 873 | Commit on 2022-05-17 #12 874 | Commit on 2022-05-18 #1 875 | Commit on 2022-05-18 #2 876 | Commit on 2022-05-18 #3 877 | Commit on 2022-05-18 #4 878 | Commit on 2022-05-18 #5 879 | Commit on 2022-05-18 #6 880 | Commit on 2022-05-19 #1 881 | Commit on 2022-05-19 #2 882 | Commit on 2022-05-19 #3 883 | Commit on 2022-05-19 #4 884 | Commit on 2022-05-19 #5 885 | Commit on 2022-05-19 #6 886 | Commit on 2022-05-19 #7 887 | Commit on 2022-05-19 #8 888 | Commit on 2022-05-19 #9 889 | Commit on 2022-05-19 #10 890 | Commit on 2022-05-19 #11 891 | Commit on 2022-05-19 #12 892 | Commit on 2022-05-20 #1 893 | Commit on 2022-05-20 #2 894 | Commit on 2022-05-20 #3 895 | Commit on 2022-05-20 #4 896 | Commit on 2022-05-20 #5 897 | Commit on 2022-05-20 #6 898 | Commit on 2022-05-20 #7 899 | Commit on 2022-05-20 #8 900 | Commit on 2022-05-20 #9 901 | Commit on 2022-05-20 #10 902 | Commit on 2022-05-20 #11 903 | Commit on 2022-05-20 #12 904 | Commit on 2022-05-21 #1 905 | Commit on 2022-05-21 #2 906 | Commit on 2022-05-21 #3 907 | Commit on 2022-05-21 #4 908 | Commit on 2022-05-23 #1 909 | Commit on 2022-05-23 #2 910 | Commit on 2022-05-23 #3 911 | Commit on 2022-05-23 #4 912 | Commit on 2022-05-23 #5 913 | Commit on 2022-05-23 #6 914 | Commit on 2022-05-23 #7 915 | Commit on 2022-05-23 #8 916 | Commit on 2022-05-23 #9 917 | Commit on 2022-05-24 #1 918 | Commit on 2022-05-24 #2 919 | Commit on 2022-05-24 #3 920 | Commit on 2022-05-24 #4 921 | Commit on 2022-05-25 #1 922 | Commit on 2022-05-25 #2 923 | Commit on 2022-05-25 #3 924 | Commit on 2022-05-25 #4 925 | Commit on 2022-05-25 #5 926 | Commit on 2022-05-25 #6 927 | Commit on 2022-05-26 #1 928 | Commit on 2022-05-26 #2 929 | Commit on 2022-05-26 #3 930 | Commit on 2022-05-26 #4 931 | Commit on 2022-05-26 #5 932 | Commit on 2022-05-27 #1 933 | Commit on 2022-05-27 #2 934 | Commit on 2022-05-27 #3 935 | Commit on 2022-05-27 #4 936 | Commit on 2022-05-27 #5 937 | Commit on 2022-05-27 #6 938 | Commit on 2022-05-30 #1 939 | Commit on 2022-05-30 #2 940 | Commit on 2022-05-30 #3 941 | Commit on 2022-05-30 #4 942 | Commit on 2022-05-30 #5 943 | Commit on 2022-05-30 #6 944 | Commit on 2022-05-30 #7 945 | Commit on 2022-05-30 #8 946 | Commit on 2022-05-30 #9 947 | Commit on 2022-05-30 #10 948 | Commit on 2022-05-30 #11 949 | Commit on 2022-05-30 #12 950 | Commit on 2022-05-31 #1 951 | Commit on 2022-05-31 #2 952 | Commit on 2022-05-31 #3 953 | Commit on 2022-05-31 #4 954 | Commit on 2022-05-31 #5 955 | Commit on 2022-05-31 #6 956 | Commit on 2022-06-01 #1 957 | Commit on 2022-06-01 #2 958 | Commit on 2022-06-01 #3 959 | Commit on 2022-06-01 #4 960 | Commit on 2022-06-01 #5 961 | Commit on 2022-06-02 #1 962 | Commit on 2022-06-02 #2 963 | Commit on 2022-06-02 #3 964 | Commit on 2022-06-02 #4 965 | Commit on 2022-06-02 #5 966 | Commit on 2022-06-02 #6 967 | Commit on 2022-06-02 #7 968 | Commit on 2022-06-03 #1 969 | Commit on 2022-06-03 #2 970 | Commit on 2022-06-03 #3 971 | Commit on 2022-06-03 #4 972 | Commit on 2022-06-03 #5 973 | Commit on 2022-06-03 #6 974 | Commit on 2022-06-03 #7 975 | Commit on 2022-06-03 #8 976 | Commit on 2022-06-03 #9 977 | Commit on 2022-06-03 #10 978 | Commit on 2022-06-03 #11 979 | Commit on 2022-06-06 #1 980 | Commit on 2022-06-06 #2 981 | Commit on 2022-06-06 #3 982 | Commit on 2022-06-06 #4 983 | Commit on 2022-06-06 #5 984 | Commit on 2022-06-06 #6 985 | Commit on 2022-06-06 #7 986 | Commit on 2022-06-07 #1 987 | Commit on 2022-06-07 #2 988 | Commit on 2022-06-07 #3 989 | Commit on 2022-06-07 #4 990 | Commit on 2022-06-07 #5 991 | Commit on 2022-06-07 #6 992 | Commit on 2022-06-08 #1 993 | Commit on 2022-06-08 #2 994 | Commit on 2022-06-08 #3 995 | Commit on 2022-06-08 #4 996 | Commit on 2022-06-08 #5 997 | Commit on 2022-06-08 #6 998 | Commit on 2022-06-08 #7 999 | Commit on 2022-06-08 #8 1000 | Commit on 2022-06-09 #1 1001 | Commit on 2022-06-09 #2 1002 | Commit on 2022-06-09 #3 1003 | Commit on 2022-06-09 #4 1004 | Commit on 2022-06-09 #5 1005 | Commit on 2022-06-09 #6 1006 | Commit on 2022-06-09 #7 1007 | Commit on 2022-06-10 #1 1008 | Commit on 2022-06-10 #2 1009 | Commit on 2022-06-10 #3 1010 | Commit on 2022-06-10 #4 1011 | Commit on 2022-06-10 #5 1012 | Commit on 2022-06-10 #6 1013 | Commit on 2022-06-10 #7 1014 | Commit on 2022-06-10 #8 1015 | Commit on 2022-06-10 #9 1016 | Commit on 2022-06-10 #10 1017 | Commit on 2022-06-11 #1 1018 | Commit on 2022-06-11 #2 1019 | Commit on 2022-06-11 #3 1020 | Commit on 2022-06-11 #4 1021 | Commit on 2022-06-11 #5 1022 | Commit on 2022-06-11 #6 1023 | Commit on 2022-06-11 #7 1024 | Commit on 2022-06-11 #8 1025 | Commit on 2022-06-11 #9 1026 | Commit on 2022-06-11 #10 1027 | Commit on 2022-06-11 #11 1028 | Commit on 2022-06-11 #12 1029 | Commit on 2022-06-13 #1 1030 | Commit on 2022-06-13 #2 1031 | Commit on 2022-06-13 #3 1032 | Commit on 2022-06-13 #4 1033 | Commit on 2022-06-13 #5 1034 | Commit on 2022-06-13 #6 1035 | Commit on 2022-06-13 #7 1036 | Commit on 2022-06-13 #8 1037 | Commit on 2022-06-13 #9 1038 | Commit on 2022-06-13 #10 1039 | Commit on 2022-06-13 #11 1040 | Commit on 2022-06-13 #12 1041 | Commit on 2022-06-14 #1 1042 | Commit on 2022-06-14 #2 1043 | Commit on 2022-06-14 #3 1044 | Commit on 2022-06-14 #4 1045 | Commit on 2022-06-14 #5 1046 | Commit on 2022-06-14 #6 1047 | Commit on 2022-06-14 #7 1048 | Commit on 2022-06-14 #8 1049 | Commit on 2022-06-14 #9 1050 | Commit on 2022-06-14 #10 1051 | Commit on 2022-06-14 #11 1052 | Commit on 2022-06-14 #12 1053 | Commit on 2022-06-15 #1 1054 | Commit on 2022-06-15 #2 1055 | Commit on 2022-06-15 #3 1056 | Commit on 2022-06-15 #4 1057 | Commit on 2022-06-15 #5 1058 | Commit on 2022-06-15 #6 1059 | Commit on 2022-06-15 #7 1060 | Commit on 2022-06-15 #8 1061 | Commit on 2022-06-15 #9 1062 | Commit on 2022-06-15 #10 1063 | Commit on 2022-06-15 #11 1064 | Commit on 2022-06-15 #12 1065 | Commit on 2022-06-16 #1 1066 | Commit on 2022-06-16 #2 1067 | Commit on 2022-06-16 #3 1068 | Commit on 2022-06-16 #4 1069 | Commit on 2022-06-16 #5 1070 | Commit on 2022-06-16 #6 1071 | Commit on 2022-06-17 #1 1072 | Commit on 2022-06-17 #2 1073 | Commit on 2022-06-17 #3 1074 | Commit on 2022-06-17 #4 1075 | Commit on 2022-06-17 #5 1076 | Commit on 2022-06-17 #6 1077 | Commit on 2022-06-17 #7 1078 | Commit on 2022-06-17 #8 1079 | Commit on 2022-06-17 #9 1080 | Commit on 2022-06-17 #10 1081 | Commit on 2022-06-17 #11 1082 | Commit on 2022-06-18 #1 1083 | Commit on 2022-06-18 #2 1084 | Commit on 2022-06-18 #3 1085 | Commit on 2022-06-18 #4 1086 | Commit on 2022-06-20 #1 1087 | Commit on 2022-06-20 #2 1088 | Commit on 2022-06-20 #3 1089 | Commit on 2022-06-20 #4 1090 | Commit on 2022-06-20 #5 1091 | Commit on 2022-06-20 #6 1092 | Commit on 2022-06-20 #7 1093 | Commit on 2022-06-20 #8 1094 | Commit on 2022-06-20 #9 1095 | Commit on 2022-06-20 #10 1096 | Commit on 2022-06-21 #1 1097 | Commit on 2022-06-21 #2 1098 | Commit on 2022-06-21 #3 1099 | Commit on 2022-06-21 #4 1100 | Commit on 2022-06-21 #5 1101 | Commit on 2022-06-21 #6 1102 | Commit on 2022-06-21 #7 1103 | Commit on 2022-06-21 #8 1104 | Commit on 2022-06-21 #9 1105 | Commit on 2022-06-21 #10 1106 | Commit on 2022-06-21 #11 1107 | Commit on 2022-06-21 #12 1108 | Commit on 2022-06-22 #1 1109 | Commit on 2022-06-22 #2 1110 | Commit on 2022-06-22 #3 1111 | Commit on 2022-06-22 #4 1112 | Commit on 2022-06-23 #1 1113 | Commit on 2022-06-23 #2 1114 | Commit on 2022-06-23 #3 1115 | Commit on 2022-06-23 #4 1116 | Commit on 2022-06-23 #5 1117 | Commit on 2022-06-23 #6 1118 | Commit on 2022-06-23 #7 1119 | Commit on 2022-06-23 #8 1120 | Commit on 2022-06-24 #1 1121 | Commit on 2022-06-24 #2 1122 | Commit on 2022-06-24 #3 1123 | Commit on 2022-06-24 #4 1124 | Commit on 2022-06-24 #5 1125 | Commit on 2022-06-24 #6 1126 | Commit on 2022-06-27 #1 1127 | Commit on 2022-06-27 #2 1128 | Commit on 2022-06-27 #3 1129 | Commit on 2022-06-27 #4 1130 | Commit on 2022-06-28 #1 1131 | Commit on 2022-06-28 #2 1132 | Commit on 2022-06-28 #3 1133 | Commit on 2022-06-28 #4 1134 | Commit on 2022-06-28 #5 1135 | Commit on 2022-06-28 #6 1136 | Commit on 2022-06-29 #1 1137 | Commit on 2022-06-29 #2 1138 | Commit on 2022-06-29 #3 1139 | Commit on 2022-06-29 #4 1140 | Commit on 2022-06-29 #5 1141 | Commit on 2022-06-29 #6 1142 | Commit on 2022-06-29 #7 1143 | Commit on 2022-06-29 #8 1144 | Commit on 2022-06-29 #9 1145 | Commit on 2022-06-29 #10 1146 | Commit on 2022-06-29 #11 1147 | Commit on 2022-06-29 #12 1148 | Commit on 2022-06-30 #1 1149 | Commit on 2022-06-30 #2 1150 | Commit on 2022-06-30 #3 1151 | Commit on 2022-06-30 #4 1152 | Commit on 2022-06-30 #5 1153 | Commit on 2022-06-30 #6 1154 | Commit on 2022-07-01 #1 1155 | Commit on 2022-07-01 #2 1156 | Commit on 2022-07-01 #3 1157 | Commit on 2022-07-01 #4 1158 | Commit on 2022-07-01 #5 1159 | Commit on 2022-07-01 #6 1160 | Commit on 2022-07-04 #1 1161 | Commit on 2022-07-04 #2 1162 | Commit on 2022-07-04 #3 1163 | Commit on 2022-07-04 #4 1164 | Commit on 2022-07-04 #5 1165 | Commit on 2022-07-04 #6 1166 | Commit on 2022-07-04 #7 1167 | Commit on 2022-07-04 #8 1168 | Commit on 2022-07-04 #9 1169 | Commit on 2022-07-04 #10 1170 | Commit on 2022-07-05 #1 1171 | Commit on 2022-07-05 #2 1172 | Commit on 2022-07-05 #3 1173 | Commit on 2022-07-05 #4 1174 | Commit on 2022-07-05 #5 1175 | Commit on 2022-07-05 #6 1176 | Commit on 2022-07-05 #7 1177 | Commit on 2022-07-06 #1 1178 | Commit on 2022-07-06 #2 1179 | Commit on 2022-07-06 #3 1180 | Commit on 2022-07-06 #4 1181 | Commit on 2022-07-06 #5 1182 | Commit on 2022-07-06 #6 1183 | Commit on 2022-07-06 #7 1184 | Commit on 2022-07-06 #8 1185 | Commit on 2022-07-06 #9 1186 | Commit on 2022-07-06 #10 1187 | Commit on 2022-07-06 #11 1188 | Commit on 2022-07-07 #1 1189 | Commit on 2022-07-07 #2 1190 | Commit on 2022-07-07 #3 1191 | Commit on 2022-07-07 #4 1192 | Commit on 2022-07-07 #5 1193 | Commit on 2022-07-07 #6 1194 | Commit on 2022-07-07 #7 1195 | Commit on 2022-07-07 #8 1196 | Commit on 2022-07-07 #9 1197 | Commit on 2022-07-07 #10 1198 | Commit on 2022-07-08 #1 1199 | Commit on 2022-07-08 #2 1200 | Commit on 2022-07-08 #3 1201 | Commit on 2022-07-08 #4 1202 | Commit on 2022-07-08 #5 1203 | Commit on 2022-07-08 #6 1204 | Commit on 2022-07-08 #7 1205 | Commit on 2022-07-08 #8 1206 | Commit on 2022-07-08 #9 1207 | Commit on 2022-07-11 #1 1208 | Commit on 2022-07-11 #2 1209 | Commit on 2022-07-11 #3 1210 | Commit on 2022-07-11 #4 1211 | Commit on 2022-07-11 #5 1212 | Commit on 2022-07-11 #6 1213 | Commit on 2022-07-11 #7 1214 | Commit on 2022-07-11 #8 1215 | Commit on 2022-07-11 #9 1216 | Commit on 2022-07-11 #10 1217 | Commit on 2022-07-11 #11 1218 | Commit on 2022-07-12 #1 1219 | Commit on 2022-07-12 #2 1220 | Commit on 2022-07-12 #3 1221 | Commit on 2022-07-12 #4 1222 | Commit on 2022-07-12 #5 1223 | Commit on 2022-07-12 #6 1224 | Commit on 2022-07-12 #7 1225 | Commit on 2022-07-12 #8 1226 | Commit on 2022-07-12 #9 1227 | Commit on 2022-07-12 #10 1228 | Commit on 2022-07-12 #11 1229 | Commit on 2022-07-13 #1 1230 | Commit on 2022-07-13 #2 1231 | Commit on 2022-07-13 #3 1232 | Commit on 2022-07-13 #4 1233 | Commit on 2022-07-13 #5 1234 | Commit on 2022-07-13 #6 1235 | Commit on 2022-07-14 #1 1236 | Commit on 2022-07-14 #2 1237 | Commit on 2022-07-14 #3 1238 | Commit on 2022-07-14 #4 1239 | Commit on 2022-07-15 #1 1240 | Commit on 2022-07-15 #2 1241 | Commit on 2022-07-15 #3 1242 | Commit on 2022-07-15 #4 1243 | Commit on 2022-07-15 #5 1244 | Commit on 2022-07-15 #6 1245 | Commit on 2022-07-15 #7 1246 | Commit on 2022-07-15 #8 1247 | Commit on 2022-07-18 #1 1248 | Commit on 2022-07-18 #2 1249 | Commit on 2022-07-18 #3 1250 | Commit on 2022-07-18 #4 1251 | Commit on 2022-07-18 #5 1252 | Commit on 2022-07-19 #1 1253 | Commit on 2022-07-19 #2 1254 | Commit on 2022-07-19 #3 1255 | Commit on 2022-07-19 #4 1256 | Commit on 2022-07-19 #5 1257 | Commit on 2022-07-19 #6 1258 | Commit on 2022-07-19 #7 1259 | Commit on 2022-07-19 #8 1260 | Commit on 2022-07-20 #1 1261 | Commit on 2022-07-20 #2 1262 | Commit on 2022-07-20 #3 1263 | Commit on 2022-07-20 #4 1264 | Commit on 2022-07-20 #5 1265 | Commit on 2022-07-20 #6 1266 | Commit on 2022-07-20 #7 1267 | Commit on 2022-07-20 #8 1268 | Commit on 2022-07-20 #9 1269 | Commit on 2022-07-20 #10 1270 | Commit on 2022-07-21 #1 1271 | Commit on 2022-07-21 #2 1272 | Commit on 2022-07-21 #3 1273 | Commit on 2022-07-21 #4 1274 | Commit on 2022-07-21 #5 1275 | Commit on 2022-07-21 #6 1276 | Commit on 2022-07-21 #7 1277 | Commit on 2022-07-21 #8 1278 | Commit on 2022-07-21 #9 1279 | Commit on 2022-07-21 #10 1280 | Commit on 2022-07-21 #11 1281 | Commit on 2022-07-21 #12 1282 | Commit on 2022-07-22 #1 1283 | Commit on 2022-07-22 #2 1284 | Commit on 2022-07-22 #3 1285 | Commit on 2022-07-22 #4 1286 | Commit on 2022-07-22 #5 1287 | Commit on 2022-07-22 #6 1288 | Commit on 2022-07-22 #7 1289 | Commit on 2022-07-22 #8 1290 | Commit on 2022-07-22 #9 1291 | Commit on 2022-07-22 #10 1292 | Commit on 2022-07-22 #11 1293 | Commit on 2022-07-22 #12 1294 | Commit on 2022-07-23 #1 1295 | Commit on 2022-07-23 #2 1296 | Commit on 2022-07-23 #3 1297 | Commit on 2022-07-23 #4 1298 | Commit on 2022-07-23 #5 1299 | Commit on 2022-07-23 #6 1300 | Commit on 2022-07-25 #1 1301 | Commit on 2022-07-25 #2 1302 | Commit on 2022-07-25 #3 1303 | Commit on 2022-07-25 #4 1304 | Commit on 2022-07-25 #5 1305 | Commit on 2022-07-25 #6 1306 | Commit on 2022-07-25 #7 1307 | Commit on 2022-07-25 #8 1308 | Commit on 2022-07-25 #9 1309 | Commit on 2022-07-25 #10 1310 | Commit on 2022-07-26 #1 1311 | Commit on 2022-07-26 #2 1312 | Commit on 2022-07-26 #3 1313 | Commit on 2022-07-26 #4 1314 | Commit on 2022-07-26 #5 1315 | Commit on 2022-07-26 #6 1316 | Commit on 2022-07-26 #7 1317 | Commit on 2022-07-26 #8 1318 | Commit on 2022-07-26 #9 1319 | Commit on 2022-07-26 #10 1320 | Commit on 2022-07-26 #11 1321 | Commit on 2022-07-26 #12 1322 | Commit on 2022-07-27 #1 1323 | Commit on 2022-07-27 #2 1324 | Commit on 2022-07-27 #3 1325 | Commit on 2022-07-27 #4 1326 | Commit on 2022-07-27 #5 1327 | Commit on 2022-07-27 #6 1328 | Commit on 2022-07-27 #7 1329 | Commit on 2022-07-28 #1 1330 | Commit on 2022-07-28 #2 1331 | Commit on 2022-07-28 #3 1332 | Commit on 2022-07-28 #4 1333 | Commit on 2022-07-28 #5 1334 | Commit on 2022-07-28 #6 1335 | Commit on 2022-07-28 #7 1336 | Commit on 2022-07-28 #8 1337 | Commit on 2022-07-28 #9 1338 | Commit on 2022-07-28 #10 1339 | Commit on 2022-07-29 #1 1340 | Commit on 2022-07-29 #2 1341 | Commit on 2022-07-29 #3 1342 | Commit on 2022-07-29 #4 1343 | Commit on 2022-07-29 #5 1344 | Commit on 2022-07-29 #6 1345 | Commit on 2022-07-29 #7 1346 | Commit on 2022-08-01 #1 1347 | Commit on 2022-08-01 #2 1348 | Commit on 2022-08-01 #3 1349 | Commit on 2022-08-01 #4 1350 | Commit on 2022-08-01 #5 1351 | Commit on 2022-08-01 #6 1352 | Commit on 2022-08-01 #7 1353 | Commit on 2022-08-01 #8 1354 | Commit on 2022-08-01 #9 1355 | Commit on 2022-08-02 #1 1356 | Commit on 2022-08-02 #2 1357 | Commit on 2022-08-02 #3 1358 | Commit on 2022-08-02 #4 1359 | Commit on 2022-08-02 #5 1360 | Commit on 2022-08-02 #6 1361 | Commit on 2022-08-02 #7 1362 | Commit on 2022-08-02 #8 1363 | Commit on 2022-08-02 #9 1364 | Commit on 2022-08-03 #1 1365 | Commit on 2022-08-03 #2 1366 | Commit on 2022-08-03 #3 1367 | Commit on 2022-08-03 #4 1368 | Commit on 2022-08-03 #5 1369 | Commit on 2022-08-03 #6 1370 | Commit on 2022-08-03 #7 1371 | Commit on 2022-08-03 #8 1372 | Commit on 2022-08-03 #9 1373 | Commit on 2022-08-03 #10 1374 | Commit on 2022-08-03 #11 1375 | Commit on 2022-08-04 #1 1376 | Commit on 2022-08-04 #2 1377 | Commit on 2022-08-04 #3 1378 | Commit on 2022-08-04 #4 1379 | Commit on 2022-08-04 #5 1380 | Commit on 2022-08-04 #6 1381 | Commit on 2022-08-04 #7 1382 | Commit on 2022-08-05 #1 1383 | Commit on 2022-08-05 #2 1384 | Commit on 2022-08-05 #3 1385 | Commit on 2022-08-05 #4 1386 | Commit on 2022-08-05 #5 1387 | Commit on 2022-08-05 #6 1388 | Commit on 2022-08-05 #7 1389 | Commit on 2022-08-05 #8 1390 | Commit on 2022-08-05 #9 1391 | Commit on 2022-08-05 #10 1392 | Commit on 2022-08-06 #1 1393 | Commit on 2022-08-06 #2 1394 | Commit on 2022-08-06 #3 1395 | Commit on 2022-08-06 #4 1396 | Commit on 2022-08-06 #5 1397 | Commit on 2022-08-06 #6 1398 | Commit on 2022-08-06 #7 1399 | Commit on 2022-08-06 #8 1400 | Commit on 2022-08-08 #1 1401 | Commit on 2022-08-08 #2 1402 | Commit on 2022-08-08 #3 1403 | Commit on 2022-08-08 #4 1404 | Commit on 2022-08-08 #5 1405 | Commit on 2022-08-08 #6 1406 | Commit on 2022-08-08 #7 1407 | Commit on 2022-08-08 #8 1408 | Commit on 2022-08-08 #9 1409 | Commit on 2022-08-08 #10 1410 | Commit on 2022-08-09 #1 1411 | Commit on 2022-08-09 #2 1412 | Commit on 2022-08-09 #3 1413 | Commit on 2022-08-09 #4 1414 | Commit on 2022-08-09 #5 1415 | Commit on 2022-08-09 #6 1416 | Commit on 2022-08-09 #7 1417 | Commit on 2022-08-09 #8 1418 | Commit on 2022-08-09 #9 1419 | Commit on 2022-08-09 #10 1420 | Commit on 2022-08-10 #1 1421 | Commit on 2022-08-10 #2 1422 | Commit on 2022-08-10 #3 1423 | Commit on 2022-08-10 #4 1424 | Commit on 2022-08-10 #5 1425 | Commit on 2022-08-10 #6 1426 | Commit on 2022-08-10 #7 1427 | Commit on 2022-08-11 #1 1428 | Commit on 2022-08-11 #2 1429 | Commit on 2022-08-11 #3 1430 | Commit on 2022-08-11 #4 1431 | Commit on 2022-08-11 #5 1432 | Commit on 2022-08-11 #6 1433 | Commit on 2022-08-11 #7 1434 | Commit on 2022-08-11 #8 1435 | Commit on 2022-08-12 #1 1436 | Commit on 2022-08-12 #2 1437 | Commit on 2022-08-12 #3 1438 | Commit on 2022-08-12 #4 1439 | Commit on 2022-08-12 #5 1440 | Commit on 2022-08-12 #6 1441 | Commit on 2022-08-12 #7 1442 | Commit on 2022-08-12 #8 1443 | Commit on 2022-08-12 #9 1444 | Commit on 2022-08-12 #10 1445 | Commit on 2022-08-15 #1 1446 | Commit on 2022-08-15 #2 1447 | Commit on 2022-08-15 #3 1448 | Commit on 2022-08-15 #4 1449 | Commit on 2022-08-16 #1 1450 | Commit on 2022-08-16 #2 1451 | Commit on 2022-08-16 #3 1452 | Commit on 2022-08-16 #4 1453 | Commit on 2022-08-16 #5 1454 | Commit on 2022-08-17 #1 1455 | Commit on 2022-08-17 #2 1456 | Commit on 2022-08-17 #3 1457 | Commit on 2022-08-17 #4 1458 | Commit on 2022-08-17 #5 1459 | Commit on 2022-08-17 #6 1460 | Commit on 2022-08-17 #7 1461 | Commit on 2022-08-17 #8 1462 | Commit on 2022-08-17 #9 1463 | Commit on 2022-08-18 #1 1464 | Commit on 2022-08-18 #2 1465 | Commit on 2022-08-18 #3 1466 | Commit on 2022-08-18 #4 1467 | Commit on 2022-08-18 #5 1468 | Commit on 2022-08-18 #6 1469 | Commit on 2022-08-18 #7 1470 | Commit on 2022-08-18 #8 1471 | Commit on 2022-08-18 #9 1472 | Commit on 2022-08-18 #10 1473 | Commit on 2022-08-18 #11 1474 | Commit on 2022-08-18 #12 1475 | Commit on 2022-08-19 #1 1476 | Commit on 2022-08-19 #2 1477 | Commit on 2022-08-19 #3 1478 | Commit on 2022-08-19 #4 1479 | Commit on 2022-08-19 #5 1480 | Commit on 2022-08-20 #1 1481 | Commit on 2022-08-20 #2 1482 | Commit on 2022-08-20 #3 1483 | Commit on 2022-08-20 #4 1484 | Commit on 2022-08-20 #5 1485 | Commit on 2022-08-22 #1 1486 | Commit on 2022-08-22 #2 1487 | Commit on 2022-08-22 #3 1488 | Commit on 2022-08-22 #4 1489 | Commit on 2022-08-22 #5 1490 | Commit on 2022-08-22 #6 1491 | Commit on 2022-08-22 #7 1492 | Commit on 2022-08-22 #8 1493 | Commit on 2022-08-22 #9 1494 | Commit on 2022-08-22 #10 1495 | Commit on 2022-08-23 #1 1496 | Commit on 2022-08-23 #2 1497 | Commit on 2022-08-23 #3 1498 | Commit on 2022-08-23 #4 1499 | Commit on 2022-08-23 #5 1500 | Commit on 2022-08-23 #6 1501 | Commit on 2022-08-23 #7 1502 | Commit on 2022-08-23 #8 1503 | Commit on 2022-08-23 #9 1504 | Commit on 2022-08-24 #1 1505 | Commit on 2022-08-24 #2 1506 | Commit on 2022-08-24 #3 1507 | Commit on 2022-08-24 #4 1508 | Commit on 2022-08-24 #5 1509 | Commit on 2022-08-24 #6 1510 | Commit on 2022-08-24 #7 1511 | Commit on 2022-08-24 #8 1512 | Commit on 2022-08-24 #9 1513 | Commit on 2022-08-24 #10 1514 | Commit on 2022-08-24 #11 1515 | Commit on 2022-08-25 #1 1516 | Commit on 2022-08-25 #2 1517 | Commit on 2022-08-25 #3 1518 | Commit on 2022-08-25 #4 1519 | Commit on 2022-08-25 #5 1520 | Commit on 2022-08-26 #1 1521 | Commit on 2022-08-26 #2 1522 | Commit on 2022-08-26 #3 1523 | Commit on 2022-08-26 #4 1524 | Commit on 2022-08-26 #5 1525 | Commit on 2022-08-26 #6 1526 | Commit on 2022-08-26 #7 1527 | Commit on 2022-08-26 #8 1528 | Commit on 2022-08-26 #9 1529 | Commit on 2022-08-26 #10 1530 | Commit on 2022-08-29 #1 1531 | Commit on 2022-08-29 #2 1532 | Commit on 2022-08-29 #3 1533 | Commit on 2022-08-29 #4 1534 | Commit on 2022-08-29 #5 1535 | Commit on 2022-08-29 #6 1536 | Commit on 2022-08-30 #1 1537 | Commit on 2022-08-30 #2 1538 | Commit on 2022-08-30 #3 1539 | Commit on 2022-08-30 #4 1540 | Commit on 2022-08-30 #5 1541 | Commit on 2022-08-30 #6 1542 | Commit on 2022-08-30 #7 1543 | Commit on 2022-08-30 #8 1544 | Commit on 2022-08-30 #9 1545 | Commit on 2022-08-30 #10 1546 | Commit on 2022-08-30 #11 1547 | Commit on 2022-08-30 #12 1548 | Commit on 2022-08-31 #1 1549 | Commit on 2022-08-31 #2 1550 | Commit on 2022-08-31 #3 1551 | Commit on 2022-08-31 #4 1552 | Commit on 2022-08-31 #5 1553 | Commit on 2022-08-31 #6 1554 | Commit on 2022-09-01 #1 1555 | Commit on 2022-09-01 #2 1556 | Commit on 2022-09-01 #3 1557 | Commit on 2022-09-01 #4 1558 | Commit on 2022-09-01 #5 1559 | Commit on 2022-09-01 #6 1560 | Commit on 2022-09-01 #7 1561 | Commit on 2022-09-01 #8 1562 | Commit on 2022-09-02 #1 1563 | Commit on 2022-09-02 #2 1564 | Commit on 2022-09-02 #3 1565 | Commit on 2022-09-02 #4 1566 | Commit on 2022-09-02 #5 1567 | Commit on 2022-09-02 #6 1568 | Commit on 2022-09-02 #7 1569 | Commit on 2022-09-05 #1 1570 | Commit on 2022-09-05 #2 1571 | Commit on 2022-09-05 #3 1572 | Commit on 2022-09-05 #4 1573 | Commit on 2022-09-05 #5 1574 | Commit on 2022-09-05 #6 1575 | Commit on 2022-09-05 #7 1576 | Commit on 2022-09-05 #8 1577 | Commit on 2022-09-05 #9 1578 | Commit on 2022-09-05 #10 1579 | Commit on 2022-09-06 #1 1580 | Commit on 2022-09-06 #2 1581 | Commit on 2022-09-06 #3 1582 | Commit on 2022-09-06 #4 1583 | Commit on 2022-09-06 #5 1584 | Commit on 2022-09-06 #6 1585 | Commit on 2022-09-06 #7 1586 | Commit on 2022-09-06 #8 1587 | Commit on 2022-09-07 #1 1588 | Commit on 2022-09-07 #2 1589 | Commit on 2022-09-07 #3 1590 | Commit on 2022-09-07 #4 1591 | Commit on 2022-09-07 #5 1592 | Commit on 2022-09-08 #1 1593 | Commit on 2022-09-08 #2 1594 | Commit on 2022-09-08 #3 1595 | Commit on 2022-09-08 #4 1596 | Commit on 2022-09-08 #5 1597 | Commit on 2022-09-08 #6 1598 | Commit on 2022-09-08 #7 1599 | Commit on 2022-09-08 #8 1600 | Commit on 2022-09-08 #9 1601 | Commit on 2022-09-08 #10 1602 | Commit on 2022-09-08 #11 1603 | Commit on 2022-09-09 #1 1604 | Commit on 2022-09-09 #2 1605 | Commit on 2022-09-09 #3 1606 | Commit on 2022-09-09 #4 1607 | Commit on 2022-09-12 #1 1608 | Commit on 2022-09-12 #2 1609 | Commit on 2022-09-12 #3 1610 | Commit on 2022-09-12 #4 1611 | Commit on 2022-09-12 #5 1612 | Commit on 2022-09-12 #6 1613 | Commit on 2022-09-12 #7 1614 | Commit on 2022-09-13 #1 1615 | Commit on 2022-09-13 #2 1616 | Commit on 2022-09-13 #3 1617 | Commit on 2022-09-13 #4 1618 | Commit on 2022-09-13 #5 1619 | Commit on 2022-09-13 #6 1620 | Commit on 2022-09-13 #7 1621 | Commit on 2022-09-13 #8 1622 | Commit on 2022-09-13 #9 1623 | Commit on 2022-09-14 #1 1624 | Commit on 2022-09-14 #2 1625 | Commit on 2022-09-14 #3 1626 | Commit on 2022-09-14 #4 1627 | Commit on 2022-09-14 #5 1628 | Commit on 2022-09-15 #1 1629 | Commit on 2022-09-15 #2 1630 | Commit on 2022-09-15 #3 1631 | Commit on 2022-09-15 #4 1632 | Commit on 2022-09-16 #1 1633 | Commit on 2022-09-16 #2 1634 | Commit on 2022-09-16 #3 1635 | Commit on 2022-09-16 #4 1636 | Commit on 2022-09-16 #5 1637 | Commit on 2022-09-17 #1 1638 | Commit on 2022-09-17 #2 1639 | Commit on 2022-09-17 #3 1640 | Commit on 2022-09-17 #4 1641 | Commit on 2022-09-17 #5 1642 | Commit on 2022-09-17 #6 1643 | Commit on 2022-09-17 #7 1644 | Commit on 2022-09-17 #8 1645 | Commit on 2022-09-17 #9 1646 | Commit on 2022-09-17 #10 1647 | Commit on 2022-09-17 #11 1648 | Commit on 2022-09-19 #1 1649 | Commit on 2022-09-19 #2 1650 | Commit on 2022-09-19 #3 1651 | Commit on 2022-09-19 #4 1652 | Commit on 2022-09-19 #5 1653 | Commit on 2022-09-19 #6 1654 | Commit on 2022-09-20 #1 1655 | Commit on 2022-09-20 #2 1656 | Commit on 2022-09-20 #3 1657 | Commit on 2022-09-20 #4 1658 | Commit on 2022-09-20 #5 1659 | Commit on 2022-09-20 #6 1660 | Commit on 2022-09-20 #7 1661 | Commit on 2022-09-20 #8 1662 | Commit on 2022-09-20 #9 1663 | Commit on 2022-09-20 #10 1664 | Commit on 2022-09-20 #11 1665 | Commit on 2022-09-21 #1 1666 | Commit on 2022-09-21 #2 1667 | Commit on 2022-09-21 #3 1668 | Commit on 2022-09-21 #4 1669 | Commit on 2022-09-21 #5 1670 | Commit on 2022-09-22 #1 1671 | Commit on 2022-09-22 #2 1672 | Commit on 2022-09-22 #3 1673 | Commit on 2022-09-22 #4 1674 | Commit on 2022-09-22 #5 1675 | Commit on 2022-09-22 #6 1676 | Commit on 2022-09-22 #7 1677 | Commit on 2022-09-22 #8 1678 | Commit on 2022-09-22 #9 1679 | Commit on 2022-09-23 #1 1680 | Commit on 2022-09-23 #2 1681 | Commit on 2022-09-23 #3 1682 | Commit on 2022-09-23 #4 1683 | Commit on 2022-09-23 #5 1684 | Commit on 2022-09-24 #1 1685 | Commit on 2022-09-24 #2 1686 | Commit on 2022-09-24 #3 1687 | Commit on 2022-09-24 #4 1688 | Commit on 2022-09-24 #5 1689 | Commit on 2022-09-26 #1 1690 | Commit on 2022-09-26 #2 1691 | Commit on 2022-09-26 #3 1692 | Commit on 2022-09-26 #4 1693 | Commit on 2022-09-26 #5 1694 | Commit on 2022-09-26 #6 1695 | Commit on 2022-09-26 #7 1696 | Commit on 2022-09-27 #1 1697 | Commit on 2022-09-27 #2 1698 | Commit on 2022-09-27 #3 1699 | Commit on 2022-09-27 #4 1700 | Commit on 2022-09-27 #5 1701 | Commit on 2022-09-27 #6 1702 | Commit on 2022-09-27 #7 1703 | Commit on 2022-09-27 #8 1704 | Commit on 2022-09-27 #9 1705 | Commit on 2022-09-27 #10 1706 | Commit on 2022-09-28 #1 1707 | Commit on 2022-09-28 #2 1708 | Commit on 2022-09-28 #3 1709 | Commit on 2022-09-28 #4 1710 | Commit on 2022-09-28 #5 1711 | Commit on 2022-09-28 #6 1712 | Commit on 2022-09-28 #7 1713 | Commit on 2022-09-29 #1 1714 | Commit on 2022-09-29 #2 1715 | Commit on 2022-09-29 #3 1716 | Commit on 2022-09-29 #4 1717 | Commit on 2022-09-30 #1 1718 | Commit on 2022-09-30 #2 1719 | Commit on 2022-09-30 #3 1720 | Commit on 2022-09-30 #4 1721 | Commit on 2022-09-30 #5 1722 | Commit on 2022-09-30 #6 1723 | Commit on 2022-09-30 #7 1724 | Commit on 2022-09-30 #8 1725 | Commit on 2022-09-30 #9 1726 | Commit on 2022-09-30 #10 1727 | Commit on 2022-10-01 #1 1728 | Commit on 2022-10-01 #2 1729 | Commit on 2022-10-01 #3 1730 | Commit on 2022-10-01 #4 1731 | Commit on 2022-10-01 #5 1732 | Commit on 2022-10-01 #6 1733 | Commit on 2022-10-01 #7 1734 | Commit on 2022-10-01 #8 1735 | Commit on 2022-10-01 #9 1736 | Commit on 2022-10-01 #10 1737 | Commit on 2022-10-03 #1 1738 | Commit on 2022-10-03 #2 1739 | Commit on 2022-10-03 #3 1740 | Commit on 2022-10-03 #4 1741 | Commit on 2022-10-04 #1 1742 | Commit on 2022-10-04 #2 1743 | Commit on 2022-10-04 #3 1744 | Commit on 2022-10-04 #4 1745 | Commit on 2022-10-04 #5 1746 | Commit on 2022-10-04 #6 1747 | Commit on 2022-10-04 #7 1748 | Commit on 2022-10-05 #1 1749 | Commit on 2022-10-05 #2 1750 | Commit on 2022-10-05 #3 1751 | Commit on 2022-10-05 #4 1752 | Commit on 2022-10-05 #5 1753 | Commit on 2022-10-05 #6 1754 | Commit on 2022-10-05 #7 1755 | Commit on 2022-10-06 #1 1756 | Commit on 2022-10-06 #2 1757 | Commit on 2022-10-06 #3 1758 | Commit on 2022-10-06 #4 1759 | Commit on 2022-10-06 #5 1760 | Commit on 2022-10-06 #6 1761 | Commit on 2022-10-06 #7 1762 | Commit on 2022-10-06 #8 1763 | Commit on 2022-10-07 #1 1764 | Commit on 2022-10-07 #2 1765 | Commit on 2022-10-07 #3 1766 | Commit on 2022-10-07 #4 1767 | Commit on 2022-10-07 #5 1768 | Commit on 2022-10-07 #6 1769 | Commit on 2022-10-07 #7 1770 | Commit on 2022-10-07 #8 1771 | Commit on 2022-10-07 #9 1772 | Commit on 2022-10-07 #10 1773 | Commit on 2022-10-07 #11 1774 | Commit on 2022-10-07 #12 1775 | Commit on 2022-10-08 #1 1776 | Commit on 2022-10-08 #2 1777 | Commit on 2022-10-08 #3 1778 | Commit on 2022-10-08 #4 1779 | Commit on 2022-10-08 #5 1780 | Commit on 2022-10-08 #6 1781 | Commit on 2022-10-08 #7 1782 | Commit on 2022-10-08 #8 1783 | Commit on 2022-10-08 #9 1784 | Commit on 2022-10-08 #10 1785 | Commit on 2022-10-08 #11 1786 | Commit on 2022-10-08 #12 1787 | Commit on 2022-10-10 #1 1788 | Commit on 2022-10-10 #2 1789 | Commit on 2022-10-10 #3 1790 | Commit on 2022-10-10 #4 1791 | Commit on 2022-10-10 #5 1792 | Commit on 2022-10-11 #1 1793 | Commit on 2022-10-11 #2 1794 | Commit on 2022-10-11 #3 1795 | Commit on 2022-10-11 #4 1796 | Commit on 2022-10-12 #1 1797 | Commit on 2022-10-12 #2 1798 | Commit on 2022-10-12 #3 1799 | Commit on 2022-10-12 #4 1800 | Commit on 2022-10-12 #5 1801 | Commit on 2022-10-12 #6 1802 | Commit on 2022-10-12 #7 1803 | Commit on 2022-10-12 #8 1804 | Commit on 2022-10-12 #9 1805 | Commit on 2022-10-12 #10 1806 | Commit on 2022-10-12 #11 1807 | Commit on 2022-10-13 #1 1808 | Commit on 2022-10-13 #2 1809 | Commit on 2022-10-13 #3 1810 | Commit on 2022-10-13 #4 1811 | Commit on 2022-10-13 #5 1812 | Commit on 2022-10-13 #6 1813 | Commit on 2022-10-13 #7 1814 | Commit on 2022-10-14 #1 1815 | Commit on 2022-10-14 #2 1816 | Commit on 2022-10-14 #3 1817 | Commit on 2022-10-14 #4 1818 | Commit on 2022-10-17 #1 1819 | Commit on 2022-10-17 #2 1820 | Commit on 2022-10-17 #3 1821 | Commit on 2022-10-17 #4 1822 | Commit on 2022-10-17 #5 1823 | Commit on 2022-10-17 #6 1824 | Commit on 2022-10-17 #7 1825 | Commit on 2022-10-18 #1 1826 | Commit on 2022-10-18 #2 1827 | Commit on 2022-10-18 #3 1828 | Commit on 2022-10-18 #4 1829 | Commit on 2022-10-18 #5 1830 | Commit on 2022-10-18 #6 1831 | Commit on 2022-10-18 #7 1832 | Commit on 2022-10-18 #8 1833 | Commit on 2022-10-19 #1 1834 | Commit on 2022-10-19 #2 1835 | Commit on 2022-10-19 #3 1836 | Commit on 2022-10-19 #4 1837 | Commit on 2022-10-19 #5 1838 | Commit on 2022-10-20 #1 1839 | Commit on 2022-10-20 #2 1840 | Commit on 2022-10-20 #3 1841 | Commit on 2022-10-20 #4 1842 | Commit on 2022-10-20 #5 1843 | Commit on 2022-10-20 #6 1844 | Commit on 2022-10-20 #7 1845 | Commit on 2022-10-20 #8 1846 | Commit on 2022-10-20 #9 1847 | Commit on 2022-10-20 #10 1848 | Commit on 2022-10-20 #11 1849 | Commit on 2022-10-21 #1 1850 | Commit on 2022-10-21 #2 1851 | Commit on 2022-10-21 #3 1852 | Commit on 2022-10-21 #4 1853 | Commit on 2022-10-21 #5 1854 | Commit on 2022-10-21 #6 1855 | Commit on 2022-10-21 #7 1856 | Commit on 2022-10-21 #8 1857 | Commit on 2022-10-24 #1 1858 | Commit on 2022-10-24 #2 1859 | Commit on 2022-10-24 #3 1860 | Commit on 2022-10-24 #4 1861 | Commit on 2022-10-25 #1 1862 | Commit on 2022-10-25 #2 1863 | Commit on 2022-10-25 #3 1864 | Commit on 2022-10-25 #4 1865 | Commit on 2022-10-25 #5 1866 | Commit on 2022-10-25 #6 1867 | Commit on 2022-10-25 #7 1868 | Commit on 2022-10-26 #1 1869 | Commit on 2022-10-26 #2 1870 | Commit on 2022-10-26 #3 1871 | Commit on 2022-10-26 #4 1872 | Commit on 2022-10-26 #5 1873 | Commit on 2022-10-26 #6 1874 | Commit on 2022-10-26 #7 1875 | Commit on 2022-10-26 #8 1876 | Commit on 2022-10-26 #9 1877 | Commit on 2022-10-27 #1 1878 | Commit on 2022-10-27 #2 1879 | Commit on 2022-10-27 #3 1880 | Commit on 2022-10-27 #4 1881 | Commit on 2022-10-27 #5 1882 | Commit on 2022-10-27 #6 1883 | Commit on 2022-10-27 #7 1884 | Commit on 2022-10-27 #8 1885 | Commit on 2022-10-27 #9 1886 | Commit on 2022-10-27 #10 1887 | Commit on 2022-10-27 #11 1888 | Commit on 2022-10-27 #12 1889 | Commit on 2022-10-28 #1 1890 | Commit on 2022-10-28 #2 1891 | Commit on 2022-10-28 #3 1892 | Commit on 2022-10-28 #4 1893 | Commit on 2022-10-29 #1 1894 | Commit on 2022-10-29 #2 1895 | Commit on 2022-10-29 #3 1896 | Commit on 2022-10-29 #4 1897 | Commit on 2022-10-29 #5 1898 | Commit on 2022-10-29 #6 1899 | Commit on 2022-10-31 #1 1900 | Commit on 2022-10-31 #2 1901 | Commit on 2022-10-31 #3 1902 | Commit on 2022-10-31 #4 1903 | Commit on 2022-10-31 #5 1904 | Commit on 2022-10-31 #6 1905 | Commit on 2022-11-01 #1 1906 | Commit on 2022-11-01 #2 1907 | Commit on 2022-11-01 #3 1908 | Commit on 2022-11-01 #4 1909 | Commit on 2022-11-01 #5 1910 | Commit on 2022-11-01 #6 1911 | Commit on 2022-11-01 #7 1912 | Commit on 2022-11-01 #8 1913 | Commit on 2022-11-01 #9 1914 | Commit on 2022-11-01 #10 1915 | Commit on 2022-11-01 #11 1916 | Commit on 2022-11-01 #12 1917 | Commit on 2022-11-02 #1 1918 | Commit on 2022-11-02 #2 1919 | Commit on 2022-11-02 #3 1920 | Commit on 2022-11-02 #4 1921 | Commit on 2022-11-02 #5 1922 | Commit on 2022-11-02 #6 1923 | Commit on 2022-11-02 #7 1924 | Commit on 2022-11-02 #8 1925 | Commit on 2022-11-02 #9 1926 | Commit on 2022-11-02 #10 1927 | Commit on 2022-11-02 #11 1928 | Commit on 2022-11-02 #12 1929 | Commit on 2022-11-03 #1 1930 | Commit on 2022-11-03 #2 1931 | Commit on 2022-11-03 #3 1932 | Commit on 2022-11-03 #4 1933 | Commit on 2022-11-03 #5 1934 | Commit on 2022-11-03 #6 1935 | Commit on 2022-11-03 #7 1936 | Commit on 2022-11-03 #8 1937 | Commit on 2022-11-04 #1 1938 | Commit on 2022-11-04 #2 1939 | Commit on 2022-11-04 #3 1940 | Commit on 2022-11-04 #4 1941 | Commit on 2022-11-04 #5 1942 | Commit on 2022-11-04 #6 1943 | Commit on 2022-11-04 #7 1944 | Commit on 2022-11-05 #1 1945 | Commit on 2022-11-05 #2 1946 | Commit on 2022-11-05 #3 1947 | Commit on 2022-11-05 #4 1948 | Commit on 2022-11-05 #5 1949 | Commit on 2022-11-05 #6 1950 | Commit on 2022-11-05 #7 1951 | Commit on 2022-11-05 #8 1952 | Commit on 2022-11-05 #9 1953 | Commit on 2022-11-05 #10 1954 | Commit on 2022-11-05 #11 1955 | Commit on 2022-11-07 #1 1956 | Commit on 2022-11-07 #2 1957 | Commit on 2022-11-07 #3 1958 | Commit on 2022-11-07 #4 1959 | Commit on 2022-11-08 #1 1960 | Commit on 2022-11-08 #2 1961 | Commit on 2022-11-08 #3 1962 | Commit on 2022-11-08 #4 1963 | Commit on 2022-11-08 #5 1964 | Commit on 2022-11-08 #6 1965 | Commit on 2022-11-09 #1 1966 | Commit on 2022-11-09 #2 1967 | Commit on 2022-11-09 #3 1968 | Commit on 2022-11-09 #4 1969 | Commit on 2022-11-09 #5 1970 | Commit on 2022-11-09 #6 1971 | Commit on 2022-11-09 #7 1972 | Commit on 2022-11-09 #8 1973 | Commit on 2022-11-09 #9 1974 | Commit on 2022-11-09 #10 1975 | Commit on 2022-11-09 #11 1976 | Commit on 2022-11-10 #1 1977 | Commit on 2022-11-10 #2 1978 | Commit on 2022-11-10 #3 1979 | Commit on 2022-11-10 #4 1980 | Commit on 2022-11-10 #5 1981 | Commit on 2022-11-10 #6 1982 | Commit on 2022-11-11 #1 1983 | Commit on 2022-11-11 #2 1984 | Commit on 2022-11-11 #3 1985 | Commit on 2022-11-11 #4 1986 | Commit on 2022-11-11 #5 1987 | Commit on 2022-11-11 #6 1988 | Commit on 2022-11-11 #7 1989 | Commit on 2022-11-11 #8 1990 | Commit on 2022-11-11 #9 1991 | Commit on 2022-11-11 #10 1992 | Commit on 2022-11-11 #11 1993 | Commit on 2022-11-11 #12 1994 | Commit on 2022-11-12 #1 1995 | Commit on 2022-11-12 #2 1996 | Commit on 2022-11-12 #3 1997 | Commit on 2022-11-12 #4 1998 | Commit on 2022-11-12 #5 1999 | Commit on 2022-11-12 #6 2000 | Commit on 2022-11-12 #7 2001 | Commit on 2022-11-12 #8 2002 | Commit on 2022-11-12 #9 2003 | Commit on 2022-11-14 #1 2004 | Commit on 2022-11-14 #2 2005 | Commit on 2022-11-14 #3 2006 | Commit on 2022-11-14 #4 2007 | Commit on 2022-11-14 #5 2008 | Commit on 2022-11-14 #6 2009 | Commit on 2022-11-14 #7 2010 | Commit on 2022-11-14 #8 2011 | Commit on 2022-11-14 #9 2012 | Commit on 2022-11-14 #10 2013 | Commit on 2022-11-15 #1 2014 | Commit on 2022-11-15 #2 2015 | Commit on 2022-11-15 #3 2016 | Commit on 2022-11-15 #4 2017 | Commit on 2022-11-15 #5 2018 | Commit on 2022-11-15 #6 2019 | Commit on 2022-11-15 #7 2020 | Commit on 2022-11-15 #8 2021 | Commit on 2022-11-15 #9 2022 | Commit on 2022-11-16 #1 2023 | Commit on 2022-11-16 #2 2024 | Commit on 2022-11-16 #3 2025 | Commit on 2022-11-16 #4 2026 | Commit on 2022-11-17 #1 2027 | Commit on 2022-11-17 #2 2028 | Commit on 2022-11-17 #3 2029 | Commit on 2022-11-17 #4 2030 | Commit on 2022-11-17 #5 2031 | Commit on 2022-11-17 #6 2032 | Commit on 2022-11-17 #7 2033 | Commit on 2022-11-17 #8 2034 | Commit on 2022-11-17 #9 2035 | Commit on 2022-11-18 #1 2036 | Commit on 2022-11-18 #2 2037 | Commit on 2022-11-18 #3 2038 | Commit on 2022-11-18 #4 2039 | Commit on 2022-11-18 #5 2040 | Commit on 2022-11-18 #6 2041 | Commit on 2022-11-21 #1 2042 | Commit on 2022-11-21 #2 2043 | Commit on 2022-11-21 #3 2044 | Commit on 2022-11-21 #4 2045 | Commit on 2022-11-21 #5 2046 | Commit on 2022-11-21 #6 2047 | Commit on 2022-11-21 #7 2048 | Commit on 2022-11-21 #8 2049 | Commit on 2022-11-21 #9 2050 | Commit on 2022-11-21 #10 2051 | Commit on 2022-11-21 #11 2052 | Commit on 2022-11-22 #1 2053 | Commit on 2022-11-22 #2 2054 | Commit on 2022-11-22 #3 2055 | Commit on 2022-11-22 #4 2056 | Commit on 2022-11-22 #5 2057 | Commit on 2022-11-22 #6 2058 | Commit on 2022-11-22 #7 2059 | Commit on 2022-11-22 #8 2060 | Commit on 2022-11-23 #1 2061 | Commit on 2022-11-23 #2 2062 | Commit on 2022-11-23 #3 2063 | Commit on 2022-11-23 #4 2064 | Commit on 2022-11-23 #5 2065 | Commit on 2022-11-23 #6 2066 | Commit on 2022-11-23 #7 2067 | Commit on 2022-11-23 #8 2068 | Commit on 2022-11-23 #9 2069 | Commit on 2022-11-23 #10 2070 | Commit on 2022-11-24 #1 2071 | Commit on 2022-11-24 #2 2072 | Commit on 2022-11-24 #3 2073 | Commit on 2022-11-24 #4 2074 | Commit on 2022-11-24 #5 2075 | Commit on 2022-11-24 #6 2076 | Commit on 2022-11-24 #7 2077 | Commit on 2022-11-24 #8 2078 | Commit on 2022-11-24 #9 2079 | Commit on 2022-11-24 #10 2080 | Commit on 2022-11-24 #11 2081 | Commit on 2022-11-24 #12 2082 | Commit on 2022-11-25 #1 2083 | Commit on 2022-11-25 #2 2084 | Commit on 2022-11-25 #3 2085 | Commit on 2022-11-25 #4 2086 | Commit on 2022-11-25 #5 2087 | Commit on 2022-11-25 #6 2088 | Commit on 2022-11-25 #7 2089 | Commit on 2022-11-25 #8 2090 | Commit on 2022-11-26 #1 2091 | Commit on 2022-11-26 #2 2092 | Commit on 2022-11-26 #3 2093 | Commit on 2022-11-26 #4 2094 | Commit on 2022-11-26 #5 2095 | Commit on 2022-11-26 #6 2096 | Commit on 2022-11-26 #7 2097 | Commit on 2022-11-28 #1 2098 | Commit on 2022-11-28 #2 2099 | Commit on 2022-11-28 #3 2100 | Commit on 2022-11-28 #4 2101 | Commit on 2022-11-28 #5 2102 | Commit on 2022-11-29 #1 2103 | Commit on 2022-11-29 #2 2104 | Commit on 2022-11-29 #3 2105 | Commit on 2022-11-29 #4 2106 | Commit on 2022-11-29 #5 2107 | Commit on 2022-11-29 #6 2108 | Commit on 2022-11-29 #7 2109 | Commit on 2022-11-29 #8 2110 | Commit on 2022-11-29 #9 2111 | Commit on 2022-11-29 #10 2112 | Commit on 2022-11-29 #11 2113 | Commit on 2022-11-29 #12 2114 | Commit on 2022-11-30 #1 2115 | Commit on 2022-11-30 #2 2116 | Commit on 2022-11-30 #3 2117 | Commit on 2022-11-30 #4 2118 | Commit on 2022-11-30 #5 2119 | Commit on 2022-11-30 #6 2120 | Commit on 2022-12-01 #1 2121 | Commit on 2022-12-01 #2 2122 | Commit on 2022-12-01 #3 2123 | Commit on 2022-12-01 #4 2124 | Commit on 2022-12-01 #5 2125 | Commit on 2022-12-01 #6 2126 | Commit on 2022-12-01 #7 2127 | Commit on 2022-12-01 #8 2128 | Commit on 2022-12-01 #9 2129 | Commit on 2022-12-02 #1 2130 | Commit on 2022-12-02 #2 2131 | Commit on 2022-12-02 #3 2132 | Commit on 2022-12-02 #4 2133 | Commit on 2022-12-02 #5 2134 | Commit on 2022-12-02 #6 2135 | Commit on 2022-12-02 #7 2136 | Commit on 2022-12-02 #8 2137 | Commit on 2022-12-05 #1 2138 | Commit on 2022-12-05 #2 2139 | Commit on 2022-12-05 #3 2140 | Commit on 2022-12-05 #4 2141 | Commit on 2022-12-05 #5 2142 | Commit on 2022-12-05 #6 2143 | Commit on 2022-12-05 #7 2144 | Commit on 2022-12-05 #8 2145 | Commit on 2022-12-05 #9 2146 | Commit on 2022-12-05 #10 2147 | Commit on 2022-12-06 #1 2148 | Commit on 2022-12-06 #2 2149 | Commit on 2022-12-06 #3 2150 | Commit on 2022-12-06 #4 2151 | Commit on 2022-12-06 #5 2152 | Commit on 2022-12-06 #6 2153 | Commit on 2022-12-06 #7 2154 | Commit on 2022-12-07 #1 2155 | Commit on 2022-12-07 #2 2156 | Commit on 2022-12-07 #3 2157 | Commit on 2022-12-07 #4 2158 | Commit on 2022-12-07 #5 2159 | Commit on 2022-12-07 #6 2160 | Commit on 2022-12-07 #7 2161 | Commit on 2022-12-07 #8 2162 | Commit on 2022-12-07 #9 2163 | Commit on 2022-12-08 #1 2164 | Commit on 2022-12-08 #2 2165 | Commit on 2022-12-08 #3 2166 | Commit on 2022-12-08 #4 2167 | Commit on 2022-12-08 #5 2168 | Commit on 2022-12-08 #6 2169 | Commit on 2022-12-08 #7 2170 | Commit on 2022-12-08 #8 2171 | Commit on 2022-12-08 #9 2172 | Commit on 2022-12-08 #10 2173 | Commit on 2022-12-09 #1 2174 | Commit on 2022-12-09 #2 2175 | Commit on 2022-12-09 #3 2176 | Commit on 2022-12-09 #4 2177 | Commit on 2022-12-09 #5 2178 | Commit on 2022-12-09 #6 2179 | Commit on 2022-12-09 #7 2180 | Commit on 2022-12-09 #8 2181 | Commit on 2022-12-09 #9 2182 | Commit on 2022-12-09 #10 2183 | Commit on 2022-12-09 #11 2184 | Commit on 2022-12-12 #1 2185 | Commit on 2022-12-12 #2 2186 | Commit on 2022-12-12 #3 2187 | Commit on 2022-12-12 #4 2188 | Commit on 2022-12-13 #1 2189 | Commit on 2022-12-13 #2 2190 | Commit on 2022-12-13 #3 2191 | Commit on 2022-12-13 #4 2192 | Commit on 2022-12-13 #5 2193 | Commit on 2022-12-13 #6 2194 | Commit on 2022-12-14 #1 2195 | Commit on 2022-12-14 #2 2196 | Commit on 2022-12-14 #3 2197 | Commit on 2022-12-14 #4 2198 | Commit on 2022-12-14 #5 2199 | Commit on 2022-12-14 #6 2200 | Commit on 2022-12-14 #7 2201 | Commit on 2022-12-14 #8 2202 | Commit on 2022-12-14 #9 2203 | Commit on 2022-12-14 #10 2204 | Commit on 2022-12-15 #1 2205 | Commit on 2022-12-15 #2 2206 | Commit on 2022-12-15 #3 2207 | Commit on 2022-12-15 #4 2208 | Commit on 2022-12-16 #1 2209 | Commit on 2022-12-16 #2 2210 | Commit on 2022-12-16 #3 2211 | Commit on 2022-12-16 #4 2212 | Commit on 2022-12-17 #1 2213 | Commit on 2022-12-17 #2 2214 | Commit on 2022-12-17 #3 2215 | Commit on 2022-12-17 #4 2216 | Commit on 2022-12-17 #5 2217 | Commit on 2022-12-17 #6 2218 | Commit on 2022-12-17 #7 2219 | Commit on 2022-12-17 #8 2220 | Commit on 2022-12-17 #9 2221 | Commit on 2022-12-17 #10 2222 | Commit on 2022-12-17 #11 2223 | Commit on 2022-12-19 #1 2224 | Commit on 2022-12-19 #2 2225 | Commit on 2022-12-19 #3 2226 | Commit on 2022-12-19 #4 2227 | Commit on 2022-12-19 #5 2228 | Commit on 2022-12-19 #6 2229 | Commit on 2022-12-19 #7 2230 | Commit on 2022-12-19 #8 2231 | Commit on 2022-12-19 #9 2232 | Commit on 2022-12-19 #10 2233 | Commit on 2022-12-19 #11 2234 | Commit on 2022-12-20 #1 2235 | Commit on 2022-12-20 #2 2236 | Commit on 2022-12-20 #3 2237 | Commit on 2022-12-20 #4 2238 | Commit on 2022-12-20 #5 2239 | Commit on 2022-12-20 #6 2240 | Commit on 2022-12-20 #7 2241 | Commit on 2022-12-20 #8 2242 | Commit on 2022-12-21 #1 2243 | Commit on 2022-12-21 #2 2244 | Commit on 2022-12-21 #3 2245 | Commit on 2022-12-21 #4 2246 | Commit on 2022-12-22 #1 2247 | Commit on 2022-12-22 #2 2248 | Commit on 2022-12-22 #3 2249 | Commit on 2022-12-22 #4 2250 | Commit on 2022-12-22 #5 2251 | Commit on 2022-12-22 #6 2252 | Commit on 2022-12-22 #7 2253 | Commit on 2022-12-22 #8 2254 | Commit on 2022-12-22 #9 2255 | Commit on 2022-12-23 #1 2256 | Commit on 2022-12-23 #2 2257 | Commit on 2022-12-23 #3 2258 | Commit on 2022-12-23 #4 2259 | Commit on 2022-12-23 #5 2260 | Commit on 2022-12-23 #6 2261 | Commit on 2022-12-23 #7 2262 | Commit on 2022-12-26 #1 2263 | Commit on 2022-12-26 #2 2264 | Commit on 2022-12-26 #3 2265 | Commit on 2022-12-26 #4 2266 | Commit on 2022-12-26 #5 2267 | Commit on 2022-12-26 #6 2268 | Commit on 2022-12-26 #7 2269 | Commit on 2022-12-27 #1 2270 | Commit on 2022-12-27 #2 2271 | Commit on 2022-12-27 #3 2272 | Commit on 2022-12-27 #4 2273 | Commit on 2022-12-27 #5 2274 | Commit on 2022-12-27 #6 2275 | Commit on 2022-12-28 #1 2276 | Commit on 2022-12-28 #2 2277 | Commit on 2022-12-28 #3 2278 | Commit on 2022-12-28 #4 2279 | Commit on 2022-12-28 #5 2280 | Commit on 2022-12-28 #6 2281 | Commit on 2022-12-28 #7 2282 | Commit on 2022-12-28 #8 2283 | Commit on 2022-12-28 #9 2284 | Commit on 2022-12-28 #10 2285 | Commit on 2022-12-28 #11 2286 | Commit on 2022-12-28 #12 2287 | Commit on 2022-12-29 #1 2288 | Commit on 2022-12-29 #2 2289 | Commit on 2022-12-29 #3 2290 | Commit on 2022-12-29 #4 2291 | Commit on 2022-12-29 #5 2292 | Commit on 2022-12-29 #6 2293 | Commit on 2022-12-29 #7 2294 | Commit on 2022-12-29 #8 2295 | Commit on 2022-12-29 #9 2296 | Commit on 2022-12-29 #10 2297 | Commit on 2022-12-29 #11 2298 | Commit on 2022-12-30 #1 2299 | Commit on 2022-12-30 #2 2300 | Commit on 2022-12-30 #3 2301 | Commit on 2022-12-30 #4 2302 | Commit on 2022-12-30 #5 2303 | Commit on 2022-12-30 #6 2304 | Commit on 2022-12-30 #7 2305 | Commit on 2022-12-30 #8 2306 | Commit on 2022-12-31 #1 2307 | Commit on 2022-12-31 #2 2308 | Commit on 2022-12-31 #3 2309 | Commit on 2022-12-31 #4 2310 | Commit on 2022-12-31 #5 2311 | Commit on 2022-12-31 #6 2312 | Commit on 2022-12-31 #7 2313 | Commit on 2022-12-31 #8 2314 | Commit on 2022-12-31 #9 2315 | -------------------------------------------------------------------------------- /gdown/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | import importlib.metadata 4 | 5 | from . import exceptions 6 | from .cached_download import cached_download 7 | from .cached_download import md5sum 8 | from .download import download 9 | from .download_folder import download_folder 10 | from .extractall import extractall 11 | 12 | __version__ = importlib.metadata.version("gdown") 13 | -------------------------------------------------------------------------------- /gdown/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os.path 3 | import re 4 | import sys 5 | import textwrap 6 | import warnings 7 | 8 | import requests 9 | 10 | from . import __version__ 11 | from ._indent import indent 12 | from .download import download 13 | from .download_folder import MAX_NUMBER_FILES 14 | from .download_folder import download_folder 15 | from .exceptions import FileURLRetrievalError 16 | from .exceptions import FolderContentsMaximumLimitError 17 | 18 | 19 | class _ShowVersionAction(argparse.Action): 20 | def __call__(self, parser, namespace, values, option_string=None): 21 | print( 22 | "gdown {ver} at {pos}".format( 23 | ver=__version__, pos=os.path.dirname(os.path.dirname(__file__)) 24 | ) 25 | ) 26 | parser.exit() 27 | 28 | 29 | def file_size(argv): 30 | if argv is not None: 31 | m = re.match(r"([0-9]+)(GB|MB|KB|B)", argv) 32 | if not m: 33 | raise TypeError 34 | size, unit = m.groups() 35 | size = float(size) 36 | if unit == "KB": 37 | size *= 1024 38 | elif unit == "MB": 39 | size *= 1024**2 40 | elif unit == "GB": 41 | size *= 1024**3 42 | elif unit == "B": 43 | pass 44 | return size 45 | 46 | 47 | def main(): 48 | parser = argparse.ArgumentParser( 49 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 50 | ) 51 | parser.add_argument( 52 | "-V", 53 | "--version", 54 | action=_ShowVersionAction, 55 | help="display version", 56 | nargs=0, 57 | ) 58 | parser.add_argument( 59 | "url_or_id", help="url or file/folder id (with --id) to download from" 60 | ) 61 | parser.add_argument( 62 | "-O", 63 | "--output", 64 | help=( 65 | f'output file name/path; end with "{os.path.sep}"' 66 | "to create a new directory" 67 | ), 68 | ) 69 | parser.add_argument( 70 | "-q", 71 | "--quiet", 72 | action="store_true", 73 | help="suppress logging except errors", 74 | ) 75 | parser.add_argument( 76 | "--fuzzy", 77 | action="store_true", 78 | help="(file only) extract Google Drive's file ID", 79 | ) 80 | parser.add_argument( 81 | "--id", 82 | action="store_true", 83 | help="[DEPRECATED] flag to specify file/folder id instead of url", 84 | ) 85 | parser.add_argument( 86 | "--proxy", 87 | help=" download using the specified proxy", 88 | ) 89 | parser.add_argument( 90 | "--speed", 91 | type=file_size, 92 | help="download speed limit in second (e.g., '10MB' -> 10MB/s)", 93 | ) 94 | parser.add_argument( 95 | "--no-cookies", 96 | action="store_true", 97 | help="don't use cookies in ~/.cache/gdown/cookies.txt", 98 | ) 99 | parser.add_argument( 100 | "--no-check-certificate", 101 | action="store_true", 102 | help="don't check the server's TLS certificate", 103 | ) 104 | parser.add_argument( 105 | "--continue", 106 | "-c", 107 | dest="continue_", 108 | action="store_true", 109 | help="resume getting partially-downloaded files while " 110 | "skipping fully downloaded ones", 111 | ) 112 | parser.add_argument( 113 | "--folder", 114 | action="store_true", 115 | help="download entire folder instead of a single file " 116 | "(max {max} files per folder)".format(max=MAX_NUMBER_FILES), 117 | ) 118 | parser.add_argument( 119 | "--remaining-ok", 120 | action="store_true", 121 | help="(folder only) asserts that is ok to download max " 122 | "{max} files per folder.".format(max=MAX_NUMBER_FILES), 123 | ) 124 | parser.add_argument( 125 | "--format", 126 | help="Format of Google Docs, Spreadsheets and Slides. " 127 | "Default is Google Docs: 'docx', Spreadsheet: 'xlsx', Slides: 'pptx'.", 128 | ) 129 | parser.add_argument( 130 | "--user-agent", 131 | help="User-Agent to use for downloading file.", 132 | ) 133 | 134 | args = parser.parse_args() 135 | 136 | if args.output == "-": 137 | args.output = sys.stdout.buffer 138 | 139 | if args.id: 140 | warnings.warn( 141 | "Option `--id` was deprecated in version 4.3.1 " 142 | "and will be removed in 5.0. You don't need to " 143 | "pass it anymore to use a file ID.", 144 | category=FutureWarning, 145 | ) 146 | url = None 147 | id = args.url_or_id 148 | else: 149 | if re.match("^https?://.*", args.url_or_id): 150 | url = args.url_or_id 151 | id = None 152 | else: 153 | url = None 154 | id = args.url_or_id 155 | 156 | try: 157 | if args.folder: 158 | download_folder( 159 | url=url, 160 | id=id, 161 | output=args.output, 162 | quiet=args.quiet, 163 | proxy=args.proxy, 164 | speed=args.speed, 165 | use_cookies=not args.no_cookies, 166 | verify=not args.no_check_certificate, 167 | remaining_ok=args.remaining_ok, 168 | user_agent=args.user_agent, 169 | resume=args.continue_, 170 | ) 171 | else: 172 | download( 173 | url=url, 174 | output=args.output, 175 | quiet=args.quiet, 176 | proxy=args.proxy, 177 | speed=args.speed, 178 | use_cookies=not args.no_cookies, 179 | verify=not args.no_check_certificate, 180 | id=id, 181 | fuzzy=args.fuzzy, 182 | resume=args.continue_, 183 | format=args.format, 184 | user_agent=args.user_agent, 185 | ) 186 | except FileURLRetrievalError as e: 187 | print(e, file=sys.stderr) 188 | sys.exit(1) 189 | except FolderContentsMaximumLimitError as e: 190 | print( 191 | "Failed to retrieve folder contents:\n\n{}\n\n" 192 | "You can use `--remaining-ok` option to ignore this error.".format( 193 | indent("\n".join(textwrap.wrap(str(e))), prefix="\t") 194 | ), 195 | file=sys.stderr, 196 | ) 197 | sys.exit(1) 198 | except requests.exceptions.ProxyError as e: 199 | print( 200 | "Failed to use proxy:\n\n{}\n\nPlease check your proxy settings.".format( 201 | indent("\n".join(textwrap.wrap(str(e))), prefix="\t") 202 | ), 203 | file=sys.stderr, 204 | ) 205 | sys.exit(1) 206 | except Exception as e: 207 | print( 208 | "Error:\n\n{}\n\nTo report issues, please visit " 209 | "https://github.com/wkentaro/gdown/issues.".format( 210 | indent("\n".join(textwrap.wrap(str(e))), prefix="\t") 211 | ), 212 | file=sys.stderr, 213 | ) 214 | sys.exit(1) 215 | 216 | 217 | if __name__ == "__main__": 218 | main() 219 | -------------------------------------------------------------------------------- /gdown/_indent.py: -------------------------------------------------------------------------------- 1 | # textwrap.indent for Python2 2 | def indent(text, prefix): 3 | def prefixed_lines(): 4 | for line in text.splitlines(True): 5 | yield (prefix + line if line.strip() else line) 6 | 7 | return "".join(prefixed_lines()) 8 | -------------------------------------------------------------------------------- /gdown/cached_download.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os 3 | import os.path as osp 4 | import shutil 5 | import sys 6 | import tempfile 7 | import warnings 8 | from typing import Optional 9 | 10 | import filelock 11 | 12 | from .download import download 13 | 14 | cache_root = osp.join(osp.expanduser("~"), ".cache/gdown") 15 | if not osp.exists(cache_root): 16 | try: 17 | os.makedirs(cache_root) 18 | except OSError: 19 | pass 20 | 21 | 22 | def md5sum(filename, blocksize=None): 23 | warnings.warn( 24 | "md5sum is deprecated and will be removed in the future.", FutureWarning 25 | ) 26 | 27 | if blocksize is None: 28 | blocksize = 65536 29 | 30 | hash = hashlib.md5() 31 | with open(filename, "rb") as f: 32 | for block in iter(lambda: f.read(blocksize), b""): 33 | hash.update(block) 34 | return hash.hexdigest() 35 | 36 | 37 | def assert_md5sum(filename, md5, quiet=False, blocksize=None): 38 | warnings.warn( 39 | "assert_md5sum is deprecated and will be removed in the future.", FutureWarning 40 | ) 41 | 42 | if not (isinstance(md5, str) and len(md5) == 32): 43 | raise ValueError(f"MD5 must be 32 chars: {md5}") 44 | 45 | md5_actual = md5sum(filename) 46 | 47 | if md5_actual == md5: 48 | if not quiet: 49 | print(f"MD5 matches: {filename!r} == {md5!r}", file=sys.stderr) 50 | return True 51 | 52 | raise AssertionError(f"MD5 doesn't match:\nactual: {md5_actual}\nexpected: {md5}") 53 | 54 | 55 | def cached_download( 56 | url=None, 57 | path=None, 58 | md5=None, 59 | quiet=False, 60 | postprocess=None, 61 | hash: Optional[str] = None, 62 | **kwargs, 63 | ): 64 | """Cached download from URL. 65 | 66 | Parameters 67 | ---------- 68 | url: str 69 | URL. Google Drive URL is also supported. 70 | path: str, optional 71 | Output filename. Default is basename of URL. 72 | md5: str, optional 73 | Expected MD5 for specified file. Deprecated in favor of `hash`. 74 | quiet: bool 75 | Suppress terminal output. Default is False. 76 | postprocess: callable, optional 77 | Function called with filename as postprocess. 78 | hash: str, optional 79 | Hash value of file in the format of {algorithm}:{hash_value} 80 | such as sha256:abcdef.... Supported algorithms: md5, sha1, sha256, sha512. 81 | kwargs: dict 82 | Keyword arguments to be passed to `download`. 83 | 84 | Returns 85 | ------- 86 | path: str 87 | Output filename. 88 | """ 89 | if path is None: 90 | path = ( 91 | url.replace("/", "-SLASH-") 92 | .replace(":", "-COLON-") 93 | .replace("=", "-EQUAL-") 94 | .replace("?", "-QUESTION-") 95 | ) 96 | path = osp.join(cache_root, path) 97 | 98 | if md5 is not None and hash is not None: 99 | raise ValueError("md5 and hash cannot be specified at the same time.") 100 | 101 | if md5 is not None: 102 | warnings.warn( 103 | "md5 is deprecated in favor of hash. Please use hash='md5:xxx...' instead.", 104 | FutureWarning, 105 | ) 106 | hash = f"md5:{md5}" 107 | del md5 108 | 109 | # check existence 110 | if osp.exists(path) and not hash: 111 | if not quiet: 112 | print(f"File exists: {path}", file=sys.stderr) 113 | return path 114 | elif osp.exists(path) and hash: 115 | try: 116 | _assert_filehash(path=path, hash=hash, quiet=quiet) 117 | return path 118 | except AssertionError as e: 119 | # show warning and overwrite if md5 doesn't match 120 | print(e, file=sys.stderr) 121 | 122 | # download 123 | lock_path = osp.join(cache_root, "_dl_lock") 124 | try: 125 | os.makedirs(osp.dirname(path)) 126 | except OSError: 127 | pass 128 | temp_root = tempfile.mkdtemp(dir=cache_root) 129 | try: 130 | temp_path = osp.join(temp_root, "dl") 131 | 132 | log_message_hash = f"Hash: {hash}\n" if hash else "" 133 | download( 134 | url=url, 135 | output=temp_path, 136 | quiet=quiet, 137 | log_messages={ 138 | "start": f"Cached downloading...\n{log_message_hash}", 139 | "output": f"To: {path}\n", 140 | }, 141 | **kwargs, 142 | ) 143 | with filelock.FileLock(lock_path): 144 | shutil.move(temp_path, path) 145 | except Exception: 146 | shutil.rmtree(temp_root) 147 | raise 148 | 149 | if hash: 150 | _assert_filehash(path=path, hash=hash, quiet=quiet) 151 | 152 | # postprocess 153 | if postprocess is not None: 154 | postprocess(path) 155 | 156 | return path 157 | 158 | 159 | def _compute_filehash(path, algorithm): 160 | BLOCKSIZE = 65536 161 | 162 | if algorithm not in hashlib.algorithms_guaranteed: 163 | raise ValueError( 164 | f"Unsupported hash algorithm: {algorithm}. " 165 | f"Supported algorithms: {hashlib.algorithms_guaranteed}" 166 | ) 167 | 168 | algorithm_instance = getattr(hashlib, algorithm)() 169 | with open(path, "rb") as f: 170 | for block in iter(lambda: f.read(BLOCKSIZE), b""): 171 | algorithm_instance.update(block) 172 | return f"{algorithm}:{algorithm_instance.hexdigest()}" 173 | 174 | 175 | def _assert_filehash(path, hash, quiet=False, blocksize=None): 176 | if ":" not in hash: 177 | raise ValueError( 178 | f"Invalid hash: {hash}. " 179 | "Hash must be in the format of {algorithm}:{hash_value}." 180 | ) 181 | algorithm = hash.split(":")[0] 182 | 183 | hash_actual = _compute_filehash(path=path, algorithm=algorithm) 184 | 185 | if hash_actual == hash: 186 | return True 187 | 188 | raise AssertionError( 189 | f"File hash doesn't match:\nactual: {hash_actual}\nexpected: {hash}" 190 | ) 191 | -------------------------------------------------------------------------------- /gdown/download.py: -------------------------------------------------------------------------------- 1 | import email.utils 2 | import os 3 | import os.path as osp 4 | import re 5 | import shutil 6 | import sys 7 | import tempfile 8 | import textwrap 9 | import time 10 | import urllib.parse 11 | from http.cookiejar import MozillaCookieJar 12 | 13 | import bs4 14 | import requests 15 | import tqdm 16 | 17 | from ._indent import indent 18 | from .exceptions import FileURLRetrievalError 19 | from .parse_url import parse_url 20 | 21 | CHUNK_SIZE = 512 * 1024 # 512KB 22 | home = osp.expanduser("~") 23 | 24 | 25 | def get_url_from_gdrive_confirmation(contents): 26 | url = "" 27 | for line in contents.splitlines(): 28 | m = re.search(r'href="(\/uc\?export=download[^"]+)', line) 29 | if m: 30 | url = "https://docs.google.com" + m.groups()[0] 31 | url = url.replace("&", "&") 32 | break 33 | soup = bs4.BeautifulSoup(line, features="html.parser") 34 | form = soup.select_one("#download-form") 35 | if form is not None: 36 | url = form["action"].replace("&", "&") 37 | url_components = urllib.parse.urlsplit(url) 38 | query_params = urllib.parse.parse_qs(url_components.query) 39 | for param in form.findChildren("input", attrs={"type": "hidden"}): 40 | query_params[param["name"]] = param["value"] 41 | query = urllib.parse.urlencode(query_params, doseq=True) 42 | url = urllib.parse.urlunsplit(url_components._replace(query=query)) 43 | break 44 | m = re.search('"downloadUrl":"([^"]+)', line) 45 | if m: 46 | url = m.groups()[0] 47 | url = url.replace("\\u003d", "=") 48 | url = url.replace("\\u0026", "&") 49 | break 50 | m = re.search('

(.*)

', line) 51 | if m: 52 | error = m.groups()[0] 53 | raise FileURLRetrievalError(error) 54 | if not url: 55 | raise FileURLRetrievalError( 56 | "Cannot retrieve the public link of the file. " 57 | "You may need to change the permission to " 58 | "'Anyone with the link', or have had many accesses. " 59 | "Check FAQ in https://github.com/wkentaro/gdown?tab=readme-ov-file#faq.", 60 | ) 61 | return url 62 | 63 | 64 | def _get_filename_from_response(response): 65 | content_disposition = urllib.parse.unquote(response.headers["Content-Disposition"]) 66 | 67 | m = re.search(r"filename\*=UTF-8''(.*)", content_disposition) 68 | if m: 69 | filename = m.groups()[0] 70 | return filename.replace(osp.sep, "_") 71 | 72 | m = re.search('attachment; filename="(.*?)"', content_disposition) 73 | if m: 74 | filename = m.groups()[0] 75 | return filename 76 | 77 | return None 78 | 79 | 80 | def _get_modified_time_from_response(response): 81 | if "Last-Modified" not in response.headers: 82 | return None 83 | 84 | raw = response.headers["Last-Modified"] 85 | if raw is None: 86 | return None 87 | 88 | return email.utils.parsedate_to_datetime(raw) 89 | 90 | 91 | def _get_session(proxy, use_cookies, user_agent, return_cookies_file=False): 92 | sess = requests.session() 93 | 94 | sess.headers.update({"User-Agent": user_agent}) 95 | 96 | if proxy is not None: 97 | sess.proxies = {"http": proxy, "https": proxy} 98 | print("Using proxy:", proxy, file=sys.stderr) 99 | 100 | # Load cookies if exists 101 | cookies_file = osp.join(home, ".cache/gdown/cookies.txt") 102 | if use_cookies and osp.exists(cookies_file): 103 | cookie_jar = MozillaCookieJar(cookies_file) 104 | cookie_jar.load() 105 | sess.cookies.update(cookie_jar) 106 | 107 | if return_cookies_file: 108 | return sess, cookies_file 109 | else: 110 | return sess 111 | 112 | 113 | def download( 114 | url=None, 115 | output=None, 116 | quiet=False, 117 | proxy=None, 118 | speed=None, 119 | use_cookies=True, 120 | verify=True, 121 | id=None, 122 | fuzzy=False, 123 | resume=False, 124 | format=None, 125 | user_agent=None, 126 | log_messages=None, 127 | ): 128 | """Download file from URL. 129 | 130 | Parameters 131 | ---------- 132 | url: str 133 | URL. Google Drive URL is also supported. 134 | output: str 135 | Output filename/directory. Default is basename of URL. 136 | If output ends with separator '/' basename will be kept and the 137 | parameter will be treated as parenting directory. 138 | quiet: bool 139 | Suppress terminal output. Default is False. 140 | proxy: str 141 | Proxy. 142 | speed: float 143 | Download byte size per second (e.g., 256KB/s = 256 * 1024). 144 | use_cookies: bool 145 | Flag to use cookies. Default is True. 146 | verify: bool or string 147 | Either a bool, in which case it controls whether the server's TLS 148 | certificate is verified, or a string, in which case it must be a path 149 | to a CA bundle to use. Default is True. 150 | id: str 151 | Google Drive's file ID. 152 | fuzzy: bool 153 | Fuzzy extraction of Google Drive's file Id. Default is False. 154 | resume: bool 155 | Resume interrupted downloads while skipping completed ones. 156 | Default is False. 157 | format: str, optional 158 | Format of Google Docs, Spreadsheets and Slides. Default is: 159 | - Google Docs: 'docx' 160 | - Google Spreadsheet: 'xlsx' 161 | - Google Slides: 'pptx' 162 | user_agent: str, optional 163 | User-agent to use in the HTTP request. 164 | log_messages: dict, optional 165 | Log messages to customize. Currently it supports: 166 | - 'start': the message to show the start of the download 167 | - 'output': the message to show the output filename 168 | 169 | Returns 170 | ------- 171 | output: str 172 | Output filename. 173 | """ 174 | if not (id is None) ^ (url is None): 175 | raise ValueError("Either url or id has to be specified") 176 | if id is not None: 177 | url = "https://drive.google.com/uc?id={id}".format(id=id) 178 | if user_agent is None: 179 | # We need to use different user agent for file download c.f., folder 180 | user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36" # NOQA: E501 181 | if log_messages is None: 182 | log_messages = {} 183 | 184 | url_origin = url 185 | 186 | sess, cookies_file = _get_session( 187 | proxy=proxy, 188 | use_cookies=use_cookies, 189 | user_agent=user_agent, 190 | return_cookies_file=True, 191 | ) 192 | 193 | gdrive_file_id, is_gdrive_download_link = parse_url(url, warning=not fuzzy) 194 | 195 | if fuzzy and gdrive_file_id: 196 | # overwrite the url with fuzzy match of a file id 197 | url = "https://drive.google.com/uc?id={id}".format(id=gdrive_file_id) 198 | url_origin = url 199 | is_gdrive_download_link = True 200 | 201 | while True: 202 | res = sess.get(url, stream=True, verify=verify) 203 | 204 | if not (gdrive_file_id and is_gdrive_download_link): 205 | break 206 | 207 | if url == url_origin and res.status_code == 500: 208 | # The file could be Google Docs or Spreadsheets. 209 | url = "https://drive.google.com/open?id={id}".format(id=gdrive_file_id) 210 | continue 211 | 212 | if res.headers["Content-Type"].startswith("text/html"): 213 | m = re.search("(.+)", res.text) 214 | if m and m.groups()[0].endswith(" - Google Docs"): 215 | url = ( 216 | "https://docs.google.com/document/d/{id}/export" 217 | "?format={format}".format( 218 | id=gdrive_file_id, 219 | format="docx" if format is None else format, 220 | ) 221 | ) 222 | continue 223 | elif m and m.groups()[0].endswith(" - Google Sheets"): 224 | url = ( 225 | "https://docs.google.com/spreadsheets/d/{id}/export" 226 | "?format={format}".format( 227 | id=gdrive_file_id, 228 | format="xlsx" if format is None else format, 229 | ) 230 | ) 231 | continue 232 | elif m and m.groups()[0].endswith(" - Google Slides"): 233 | url = ( 234 | "https://docs.google.com/presentation/d/{id}/export" 235 | "?format={format}".format( 236 | id=gdrive_file_id, 237 | format="pptx" if format is None else format, 238 | ) 239 | ) 240 | continue 241 | elif ( 242 | "Content-Disposition" in res.headers 243 | and res.headers["Content-Disposition"].endswith("pptx") 244 | and format not in {None, "pptx"} 245 | ): 246 | url = ( 247 | "https://docs.google.com/presentation/d/{id}/export" 248 | "?format={format}".format( 249 | id=gdrive_file_id, 250 | format="pptx" if format is None else format, 251 | ) 252 | ) 253 | continue 254 | 255 | if use_cookies: 256 | cookie_jar = MozillaCookieJar(cookies_file) 257 | for cookie in sess.cookies: 258 | cookie_jar.set_cookie(cookie) 259 | cookie_jar.save() 260 | 261 | if "Content-Disposition" in res.headers: 262 | # This is the file 263 | break 264 | 265 | # Need to redirect with confirmation 266 | try: 267 | url = get_url_from_gdrive_confirmation(res.text) 268 | except FileURLRetrievalError as e: 269 | message = ( 270 | "Failed to retrieve file url:\n\n{}\n\n" 271 | "You may still be able to access the file from the browser:" 272 | "\n\n\t{}\n\n" 273 | "but Gdown can't. Please check connections and permissions." 274 | ).format( 275 | indent("\n".join(textwrap.wrap(str(e))), prefix="\t"), 276 | url_origin, 277 | ) 278 | raise FileURLRetrievalError(message) 279 | 280 | filename_from_url = None 281 | last_modified_time = None 282 | if gdrive_file_id and is_gdrive_download_link: 283 | filename_from_url = _get_filename_from_response(response=res) 284 | last_modified_time = _get_modified_time_from_response(response=res) 285 | if filename_from_url is None: 286 | filename_from_url = osp.basename(url) 287 | 288 | if output is None: 289 | output = filename_from_url 290 | 291 | output_is_path = isinstance(output, str) 292 | if output_is_path and output.endswith(osp.sep): 293 | if not osp.exists(output): 294 | os.makedirs(output) 295 | output = osp.join(output, filename_from_url) 296 | 297 | if output_is_path: 298 | if resume and os.path.isfile(output): 299 | if not quiet: 300 | print(f"Skipping already downloaded file {output}", file=sys.stderr) 301 | return output 302 | 303 | existing_tmp_files = [] 304 | for file in os.listdir(osp.dirname(output) or "."): 305 | if file.startswith(osp.basename(output)) and file.endswith(".part"): 306 | existing_tmp_files.append(osp.join(osp.dirname(output), file)) 307 | if resume and existing_tmp_files: 308 | if len(existing_tmp_files) != 1: 309 | print( 310 | "There are multiple temporary files to resume:", 311 | file=sys.stderr, 312 | ) 313 | print("\n") 314 | for file in existing_tmp_files: 315 | print("\t", file, file=sys.stderr) 316 | print("\n") 317 | print( 318 | "Please remove them except one to resume downloading.", 319 | file=sys.stderr, 320 | ) 321 | return 322 | tmp_file = existing_tmp_files[0] 323 | else: 324 | resume = False 325 | # mkstemp is preferred, but does not work on Windows 326 | # https://github.com/wkentaro/gdown/issues/153 327 | tmp_file = tempfile.mktemp( 328 | suffix=".part", 329 | prefix=osp.basename(output), 330 | dir=osp.dirname(output), 331 | ) 332 | f = open(tmp_file, "ab") 333 | else: 334 | tmp_file = None 335 | f = output 336 | 337 | if tmp_file is not None and f.tell() != 0: 338 | start_size = f.tell() 339 | headers = {"Range": "bytes={}-".format(start_size)} 340 | res = sess.get(url, headers=headers, stream=True, verify=verify) 341 | else: 342 | start_size = 0 343 | 344 | if not quiet: 345 | print(log_messages.get("start", "Downloading...\n"), file=sys.stderr, end="") 346 | if resume: 347 | print("Resume:", tmp_file, file=sys.stderr) 348 | if url_origin != url: 349 | print("From (original):", url_origin, file=sys.stderr) 350 | print("From (redirected):", url, file=sys.stderr) 351 | else: 352 | print("From:", url, file=sys.stderr) 353 | print( 354 | log_messages.get( 355 | "output", f"To: {osp.abspath(output) if output_is_path else output}\n" 356 | ), 357 | file=sys.stderr, 358 | end="", 359 | ) 360 | 361 | try: 362 | total = res.headers.get("Content-Length") 363 | if total is not None: 364 | total = int(total) + start_size 365 | if not quiet: 366 | pbar = tqdm.tqdm(total=total, unit="B", initial=start_size, unit_scale=True) 367 | t_start = time.time() 368 | for chunk in res.iter_content(chunk_size=CHUNK_SIZE): 369 | f.write(chunk) 370 | if not quiet: 371 | pbar.update(len(chunk)) 372 | if speed is not None: 373 | elapsed_time_expected = 1.0 * pbar.n / speed 374 | elapsed_time = time.time() - t_start 375 | if elapsed_time < elapsed_time_expected: 376 | time.sleep(elapsed_time_expected - elapsed_time) 377 | if not quiet: 378 | pbar.close() 379 | if tmp_file: 380 | f.close() 381 | shutil.move(tmp_file, output) 382 | if output_is_path and last_modified_time: 383 | mtime = last_modified_time.timestamp() 384 | os.utime(output, (mtime, mtime)) 385 | finally: 386 | sess.close() 387 | 388 | return output 389 | -------------------------------------------------------------------------------- /gdown/download_folder.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import itertools 3 | import json 4 | import os 5 | import os.path as osp 6 | import re 7 | import sys 8 | import warnings 9 | from typing import List 10 | from typing import Union 11 | 12 | import bs4 13 | 14 | from .download import _get_session 15 | from .download import download 16 | from .exceptions import FolderContentsMaximumLimitError 17 | from .parse_url import is_google_drive_url 18 | 19 | MAX_NUMBER_FILES = 50 20 | 21 | 22 | class _GoogleDriveFile(object): 23 | TYPE_FOLDER = "application/vnd.google-apps.folder" 24 | 25 | def __init__(self, id, name, type, children=None): 26 | self.id = id 27 | self.name = name 28 | self.type = type 29 | self.children = children if children is not None else [] 30 | 31 | def is_folder(self): 32 | return self.type == self.TYPE_FOLDER 33 | 34 | 35 | def _parse_google_drive_file(url, content): 36 | """Extracts information about the current page file and its children.""" 37 | 38 | folder_soup = bs4.BeautifulSoup(content, features="html.parser") 39 | 40 | # finds the script tag with window['_DRIVE_ivd'] 41 | encoded_data = None 42 | for script in folder_soup.select("script"): 43 | inner_html = script.decode_contents() 44 | 45 | if "_DRIVE_ivd" in inner_html: 46 | # first js string is _DRIVE_ivd, the second one is the encoded arr 47 | regex_iter = re.compile(r"'((?:[^'\\]|\\.)*)'").finditer(inner_html) 48 | # get the second elem in the iter 49 | try: 50 | encoded_data = next(itertools.islice(regex_iter, 1, None)).group(1) 51 | except StopIteration: 52 | raise RuntimeError("Couldn't find the folder encoded JS string") 53 | break 54 | 55 | if encoded_data is None: 56 | raise RuntimeError( 57 | "Cannot retrieve the folder information from the link. " 58 | "You may need to change the permission to " 59 | "'Anyone with the link', or have had many accesses. " 60 | "Check FAQ in https://github.com/wkentaro/gdown?tab=readme-ov-file#faq.", 61 | ) 62 | 63 | # decodes the array and evaluates it as a python array 64 | with warnings.catch_warnings(): 65 | warnings.filterwarnings("ignore", category=DeprecationWarning) 66 | decoded = encoded_data.encode("utf-8").decode("unicode_escape") 67 | folder_arr = json.loads(decoded) 68 | 69 | folder_contents = [] if folder_arr[0] is None else folder_arr[0] 70 | 71 | sep = " - " # unicode dash 72 | splitted = folder_soup.title.contents[0].split(sep) 73 | if len(splitted) >= 2: 74 | name = sep.join(splitted[:-1]) 75 | else: 76 | raise RuntimeError( 77 | "file/folder name cannot be extracted from: {}".format( 78 | folder_soup.title.contents[0] 79 | ) 80 | ) 81 | 82 | gdrive_file = _GoogleDriveFile( 83 | id=url.split("/")[-1], 84 | name=name, 85 | type=_GoogleDriveFile.TYPE_FOLDER, 86 | ) 87 | 88 | id_name_type_iter = [ 89 | (e[0], e[2].encode("raw_unicode_escape").decode("utf-8"), e[3]) 90 | for e in folder_contents 91 | ] 92 | 93 | return gdrive_file, id_name_type_iter 94 | 95 | 96 | def _download_and_parse_google_drive_link( 97 | sess, 98 | url, 99 | quiet=False, 100 | remaining_ok=False, 101 | verify=True, 102 | ): 103 | """Get folder structure of Google Drive folder URL.""" 104 | 105 | return_code = True 106 | 107 | for _ in range(2): 108 | if is_google_drive_url(url): 109 | # canonicalize the language into English 110 | if "?" in url: 111 | url += "&hl=en" 112 | else: 113 | url += "?hl=en" 114 | 115 | res = sess.get(url, verify=verify) 116 | if res.status_code != 200: 117 | return False, None 118 | 119 | if is_google_drive_url(url): 120 | break 121 | 122 | if not is_google_drive_url(res.url): 123 | break 124 | 125 | # need to try with canonicalized url if the original url redirects to gdrive 126 | url = res.url 127 | 128 | gdrive_file, id_name_type_iter = _parse_google_drive_file( 129 | url=url, 130 | content=res.text, 131 | ) 132 | 133 | for child_id, child_name, child_type in id_name_type_iter: 134 | if child_type != _GoogleDriveFile.TYPE_FOLDER: 135 | if not quiet: 136 | print( 137 | "Processing file", 138 | child_id, 139 | child_name, 140 | ) 141 | gdrive_file.children.append( 142 | _GoogleDriveFile( 143 | id=child_id, 144 | name=child_name, 145 | type=child_type, 146 | ) 147 | ) 148 | if not return_code: 149 | return return_code, None 150 | continue 151 | 152 | if not quiet: 153 | print( 154 | "Retrieving folder", 155 | child_id, 156 | child_name, 157 | ) 158 | return_code, child = _download_and_parse_google_drive_link( 159 | sess=sess, 160 | url="https://drive.google.com/drive/folders/" + child_id, 161 | quiet=quiet, 162 | remaining_ok=remaining_ok, 163 | ) 164 | if not return_code: 165 | return return_code, None 166 | gdrive_file.children.append(child) 167 | has_at_least_max_files = len(gdrive_file.children) == MAX_NUMBER_FILES 168 | if not remaining_ok and has_at_least_max_files: 169 | message = " ".join( 170 | [ 171 | "The gdrive folder with url: {url}".format(url=url), 172 | "has more than {max} files,".format(max=MAX_NUMBER_FILES), 173 | "gdrive can't download more than this limit.", 174 | ] 175 | ) 176 | raise FolderContentsMaximumLimitError(message) 177 | return return_code, gdrive_file 178 | 179 | 180 | def _get_directory_structure(gdrive_file, previous_path): 181 | """Converts a Google Drive folder structure into a local directory list.""" 182 | 183 | directory_structure = [] 184 | for file in gdrive_file.children: 185 | file.name = file.name.replace(osp.sep, "_") 186 | if file.is_folder(): 187 | directory_structure.append((None, osp.join(previous_path, file.name))) 188 | for i in _get_directory_structure(file, osp.join(previous_path, file.name)): 189 | directory_structure.append(i) 190 | elif not file.children: 191 | directory_structure.append((file.id, osp.join(previous_path, file.name))) 192 | return directory_structure 193 | 194 | 195 | GoogleDriveFileToDownload = collections.namedtuple( 196 | "GoogleDriveFileToDownload", ("id", "path", "local_path") 197 | ) 198 | 199 | 200 | def download_folder( 201 | url=None, 202 | id=None, 203 | output=None, 204 | quiet=False, 205 | proxy=None, 206 | speed=None, 207 | use_cookies=True, 208 | remaining_ok=False, 209 | verify=True, 210 | user_agent=None, 211 | skip_download: bool = False, 212 | resume=False, 213 | ) -> Union[List[str], List[GoogleDriveFileToDownload], None]: 214 | """Downloads entire folder from URL. 215 | 216 | Parameters 217 | ---------- 218 | url: str 219 | URL of the Google Drive folder. 220 | Must be of the format 'https://drive.google.com/drive/folders/{url}'. 221 | id: str 222 | Google Drive's folder ID. 223 | output: str, optional 224 | String containing the path of the output folder. 225 | Defaults to current working directory. 226 | quiet: bool, optional 227 | Suppress terminal output. 228 | proxy: str, optional 229 | Proxy. 230 | speed: float, optional 231 | Download byte size per second (e.g., 256KB/s = 256 * 1024). 232 | use_cookies: bool, optional 233 | Flag to use cookies. Default is True. 234 | verify: bool or string 235 | Either a bool, in which case it controls whether the server's TLS 236 | certificate is verified, or a string, in which case it must be a path 237 | to a CA bundle to use. Default is True. 238 | user_agent: str, optional 239 | User-agent to use in the HTTP request. 240 | skip_download: bool, optional 241 | If True, return the list of files to download without downloading them. 242 | Defaults to False. 243 | resume: bool 244 | Resume interrupted transfers. 245 | Completed output files will be skipped. 246 | Partial tempfiles will be reused, if the transfer is incomplete. 247 | Default is False. 248 | 249 | Returns 250 | ------- 251 | files: List[str] or List[GoogleDriveFileToDownload] or None 252 | If dry_run is False, list of local file paths downloaded or None if failed. 253 | If dry_run is True, list of GoogleDriveFileToDownload that contains 254 | id, path, and local_path. 255 | 256 | Example 257 | ------- 258 | gdown.download_folder( 259 | "https://drive.google.com/drive/folders/" + 260 | "1ZXEhzbLRLU1giKKRJkjm8N04cO_JoYE2", 261 | ) 262 | """ 263 | if not (id is None) ^ (url is None): 264 | raise ValueError("Either url or id has to be specified") 265 | if id is not None: 266 | url = "https://drive.google.com/drive/folders/{id}".format(id=id) 267 | if user_agent is None: 268 | # We need to use different user agent for folder download c.f., file 269 | user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36" # NOQA: E501 270 | 271 | sess = _get_session(proxy=proxy, use_cookies=use_cookies, user_agent=user_agent) 272 | 273 | if not quiet: 274 | print("Retrieving folder contents", file=sys.stderr) 275 | is_success, gdrive_file = _download_and_parse_google_drive_link( 276 | sess, 277 | url, 278 | quiet=quiet, 279 | remaining_ok=remaining_ok, 280 | verify=verify, 281 | ) 282 | if not is_success: 283 | print("Failed to retrieve folder contents", file=sys.stderr) 284 | return None 285 | 286 | if not quiet: 287 | print("Retrieving folder contents completed", file=sys.stderr) 288 | print("Building directory structure", file=sys.stderr) 289 | directory_structure = _get_directory_structure(gdrive_file, previous_path="") 290 | if not quiet: 291 | print("Building directory structure completed", file=sys.stderr) 292 | 293 | if output is None: 294 | output = os.getcwd() + osp.sep 295 | if output.endswith(osp.sep): 296 | root_dir = osp.join(output, gdrive_file.name) 297 | else: 298 | root_dir = output 299 | if not skip_download and not osp.exists(root_dir): 300 | os.makedirs(root_dir) 301 | 302 | files = [] 303 | for id, path in directory_structure: 304 | local_path = osp.join(root_dir, path) 305 | 306 | if id is None: # folder 307 | if not skip_download and not osp.exists(local_path): 308 | os.makedirs(local_path) 309 | continue 310 | 311 | if skip_download: 312 | files.append( 313 | GoogleDriveFileToDownload(id=id, path=path, local_path=local_path) 314 | ) 315 | else: 316 | if resume and os.path.isfile(local_path): 317 | if not quiet: 318 | print( 319 | f"Skipping already downloaded file {local_path}", 320 | file=sys.stderr, 321 | ) 322 | files.append(local_path) 323 | continue 324 | 325 | local_path = download( 326 | url="https://drive.google.com/uc?id=" + id, 327 | output=local_path, 328 | quiet=quiet, 329 | proxy=proxy, 330 | speed=speed, 331 | use_cookies=use_cookies, 332 | verify=verify, 333 | resume=resume, 334 | ) 335 | if local_path is None: 336 | if not quiet: 337 | print("Download ended unsuccessfully", file=sys.stderr) 338 | return None 339 | files.append(local_path) 340 | if not quiet: 341 | print("Download completed", file=sys.stderr) 342 | return files 343 | -------------------------------------------------------------------------------- /gdown/exceptions.py: -------------------------------------------------------------------------------- 1 | class FileURLRetrievalError(Exception): 2 | pass 3 | 4 | 5 | class FolderContentsMaximumLimitError(Exception): 6 | pass 7 | -------------------------------------------------------------------------------- /gdown/extractall.py: -------------------------------------------------------------------------------- 1 | import os.path as osp 2 | import tarfile 3 | import zipfile 4 | 5 | 6 | def extractall(path, to=None): 7 | """Extract archive file. 8 | 9 | Parameters 10 | ---------- 11 | path: str 12 | Path of archive file to be extracted. 13 | to: str, optional 14 | Directory to which the archive file will be extracted. 15 | If None, it will be set to the parent directory of the archive file. 16 | """ 17 | if to is None: 18 | to = osp.dirname(path) 19 | 20 | if path.endswith(".zip"): 21 | opener, mode = zipfile.ZipFile, "r" 22 | elif path.endswith(".tar"): 23 | opener, mode = tarfile.open, "r" 24 | elif path.endswith(".tar.gz") or path.endswith(".tgz"): 25 | opener, mode = tarfile.open, "r:gz" 26 | elif path.endswith(".tar.bz2") or path.endswith(".tbz"): 27 | opener, mode = tarfile.open, "r:bz2" 28 | else: 29 | raise ValueError( 30 | "Could not extract '%s' as no appropriate " "extractor is found" % path 31 | ) 32 | 33 | def namelist(f): 34 | if isinstance(f, zipfile.ZipFile): 35 | return f.namelist() 36 | return [m.path for m in f.members] 37 | 38 | def filelist(f): 39 | files = [] 40 | for fname in namelist(f): 41 | fname = osp.join(to, fname) 42 | files.append(fname) 43 | return files 44 | 45 | with opener(path, mode) as f: 46 | f.extractall(path=to) 47 | 48 | return filelist(f) 49 | -------------------------------------------------------------------------------- /gdown/parse_url.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.parse 3 | import warnings 4 | 5 | 6 | def is_google_drive_url(url): 7 | parsed = urllib.parse.urlparse(url) 8 | return parsed.hostname in ["drive.google.com", "docs.google.com"] 9 | 10 | 11 | def parse_url(url, warning=True): 12 | """Parse URLs especially for Google Drive links. 13 | 14 | file_id: ID of file on Google Drive. 15 | is_download_link: Flag if it is download link of Google Drive. 16 | """ 17 | parsed = urllib.parse.urlparse(url) 18 | query = urllib.parse.parse_qs(parsed.query) 19 | is_gdrive = is_google_drive_url(url=url) 20 | is_download_link = parsed.path.endswith("/uc") 21 | 22 | if not is_gdrive: 23 | return is_gdrive, is_download_link 24 | 25 | file_id = None 26 | if "id" in query: 27 | file_ids = query["id"] 28 | if len(file_ids) == 1: 29 | file_id = file_ids[0] 30 | else: 31 | patterns = [ 32 | r"^/file/d/(.*?)/(edit|view)$", 33 | r"^/file/u/[0-9]+/d/(.*?)/(edit|view)$", 34 | r"^/document/d/(.*?)/(edit|htmlview|view)$", 35 | r"^/document/u/[0-9]+/d/(.*?)/(edit|htmlview|view)$", 36 | r"^/presentation/d/(.*?)/(edit|htmlview|view)$", 37 | r"^/presentation/u/[0-9]+/d/(.*?)/(edit|htmlview|view)$", 38 | r"^/spreadsheets/d/(.*?)/(edit|htmlview|view)$", 39 | r"^/spreadsheets/u/[0-9]+/d/(.*?)/(edit|htmlview|view)$", 40 | ] 41 | for pattern in patterns: 42 | match = re.match(pattern, parsed.path) 43 | if match: 44 | file_id = match.groups()[0] 45 | break 46 | 47 | if warning and not is_download_link: 48 | warnings.warn( 49 | "You specified a Google Drive link that is not the correct link " 50 | "to download a file. You might want to try `--fuzzy` option " 51 | "or the following url: {url}".format( 52 | url="https://drive.google.com/uc?id={}".format(file_id) 53 | ) 54 | ) 55 | 56 | return file_id, is_download_link 57 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling>=1.20.0", "hatch-vcs", "hatch-fancy-pypi-readme"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "gdown" 7 | description = "Google Drive Public File/Folder Downloader" 8 | license = { text = "MIT" } 9 | requires-python = ">=3.8" 10 | authors = [ 11 | { name = "Kentaro Wada", email = "www.kentaro.wada@gmail.com" }, 12 | ] 13 | keywords = [ 14 | "google-drive", 15 | "download", 16 | "wget", 17 | "curl", 18 | ] 19 | classifiers = [ 20 | "Development Status :: 5 - Production/Stable", 21 | "Environment :: Console", 22 | "Intended Audience :: Developers", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: OS Independent", 25 | "Programming Language :: Python", 26 | "Programming Language :: Python :: 3 :: Only", 27 | "Programming Language :: Python :: 3.8", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: 3.11", 31 | "Programming Language :: Python :: 3.12", 32 | "Topic :: Software Development :: Libraries :: Python Modules", 33 | ] 34 | dependencies = [ 35 | "filelock", 36 | "requests[socks]", 37 | "tqdm", 38 | "beautifulsoup4", 39 | ] 40 | dynamic = ["readme", "version"] 41 | 42 | [project.optional-dependencies] 43 | test = [ 44 | "build", 45 | "mypy", 46 | "pytest", 47 | "pytest-xdist", 48 | "ruff", 49 | "twine", 50 | "types-requests", 51 | "types-setuptools", 52 | ] 53 | 54 | [project.urls] 55 | Homepage = "https://github.com/wkentaro/gdown" 56 | 57 | [tool.hatch.metadata.hooks.fancy-pypi-readme] 58 | content-type = "text/markdown" 59 | fragments = [ 60 | { path = "README.md" }, 61 | ] 62 | 63 | [tool.hatch.version] 64 | source = "vcs" 65 | 66 | [project.scripts] 67 | gdown = "gdown.__main__:main" 68 | 69 | [tool.mypy] 70 | ignore_missing_imports = true 71 | 72 | [tool.ruff] 73 | exclude = [ 74 | ".conda", 75 | ".git", 76 | "src", 77 | ] 78 | 79 | line-length = 88 80 | indent-width = 4 81 | 82 | [tool.ruff.lint] 83 | # Enable Pyflakes (`F`), pycodestyle (`E`), isort (`I`). 84 | select = ["E", "F", "I"] 85 | ignore = [] 86 | 87 | # Allow fix for all enabled rules (when `--fix`) is provided. 88 | fixable = ["ALL"] 89 | unfixable = [] 90 | 91 | [tool.ruff.format] 92 | # Like Black, use double quotes for strings. 93 | quote-style = "double" 94 | 95 | # Like Black, indent with spaces, rather than tabs. 96 | indent-style = "space" 97 | 98 | # Like Black, respect magic trailing commas. 99 | skip-magic-trailing-comma = false 100 | 101 | # Like Black, automatically detect the appropriate line ending. 102 | line-ending = "auto" 103 | 104 | [tool.ruff.lint.isort] 105 | force-single-line = true 106 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 1. Define your commit messages 4 | COMMIT_MESSAGES=( 5 | "Refactor code for clarity" 6 | "Fix minor bug" 7 | "Add new feature" 8 | "Update documentation" 9 | "Improve performance" 10 | "Cleanup deprecated code" 11 | "Add test coverage" 12 | "Code style improvements" 13 | "Fix merge conflicts" 14 | "Version bump" 15 | ) 16 | 17 | # 2. Define author details 18 | AUTHOR_NAME="Hardcoding-1992" 19 | AUTHOR_EMAIL="pro100nick@mail.ru" 20 | 21 | # 3. Define your date range 22 | START_DATE="2022-01-03" 23 | END_DATE="2022-6-30" 24 | 25 | # 4. Generate all dates from START_DATE to END_DATE using Python 26 | date_list=$(python3 -c " 27 | import datetime 28 | start = datetime.datetime.strptime('$START_DATE', '%Y-%m-%d') 29 | end = datetime.datetime.strptime('$END_DATE', '%Y-%m-%d') 30 | current = start 31 | while current <= end: 32 | print(current.strftime('%Y-%m-%d')) 33 | current += datetime.timedelta(days=1) 34 | ") 35 | 36 | # 5. Iterate over the generated dates 37 | for current_date in $date_list; do 38 | 39 | # --- Determine day of week (Monday=0, Sunday=6) --- 40 | day_of_week=$(python3 -c " 41 | import datetime 42 | dt = datetime.datetime.strptime('$current_date', '%Y-%m-%d') 43 | print(dt.weekday()) 44 | ") 45 | 46 | # Skip Sundays entirely (weekday=6) 47 | if [ "$day_of_week" -eq 6 ]; then 48 | continue # no commits on Sunday 49 | fi 50 | 51 | # Randomly skip some Saturdays (weekday=5) 52 | if [ "$day_of_week" -eq 5 ]; then 53 | # 50% chance to skip 54 | if [ $((RANDOM % 2)) -eq 0 ]; then 55 | continue # skip this Saturday 56 | fi 57 | fi 58 | 59 | # Generate a random number between 4 and 12 for daily commits 60 | # RANDOM % 9 gives 0..8; adding 4 yields 4..12 61 | num_commits=$((4 + RANDOM % 9)) 62 | 63 | for i in $(seq 1 "$num_commits"); do 64 | # Pick a random message from the array 65 | random_index=$((RANDOM % ${#COMMIT_MESSAGES[@]})) 66 | commit_message="${COMMIT_MESSAGES[$random_index]}" 67 | 68 | # Make a file change so there's something to commit 69 | echo "Commit on $current_date #$i" >> commits.txt 70 | 71 | # Stage changes 72 | git add commits.txt 73 | 74 | # Commit with artificial author/committer details and date 75 | GIT_AUTHOR_NAME="$AUTHOR_NAME" \ 76 | GIT_AUTHOR_EMAIL="$AUTHOR_EMAIL" \ 77 | GIT_COMMITTER_NAME="$AUTHOR_NAME" \ 78 | GIT_COMMITTER_EMAIL="$AUTHOR_EMAIL" \ 79 | GIT_AUTHOR_DATE="$current_date 12:00:00" \ 80 | GIT_COMMITTER_DATE="$current_date 12:00:00" \ 81 | git commit -m "$commit_message" 82 | done 83 | 84 | done 85 | -------------------------------------------------------------------------------- /tests/data/file_ids.csv: -------------------------------------------------------------------------------- 1 | 1cKSdgtWrPgvEsBGmjWALOH33taGyVXKb 2 | 1_t2gD7qvdcqwCLspSG0vHQRNdD_EXYz7 3 | 1LedkA5VOKZvG9eVzy9JV1tTQIF9ZQJ6J 4 | 1Tcyw0gPGJEKZsQxnNzfSe5ZuTcBcM8NY 5 | -------------------------------------------------------------------------------- /tests/data/file_ids_large.csv: -------------------------------------------------------------------------------- 1 | 1s52ek_4YTDRt_EOkx1FS53u-vJa0c4nu,d221e4cefe458b4820002d782ae1f9a4 2 | 1babjBFdPgfMOt18f0CzeKlmV_8hcWjbH,757ca625d1cc9bb3260e73365d5517bf 3 | 19dfbVDndFkboGLHESi8DGtuxF1B21Nm8,744fc6215f3e0fd67977ba75b814a3d4 4 | 1htCJ4b2rFiifPvEbo0GJpP1x8ZwCOS1v,4e46d3debea895f149c94aadae70cb74 5 | 1P2pV7CTRZbBoqv3i3np2WwU9wCVyHIxG,275b739e803084c0894641e49e696cb8 6 | -------------------------------------------------------------------------------- /tests/data/folder_ids.csv: -------------------------------------------------------------------------------- 1 | 1KpLl_1tcK0eeehzN980zbG-3M2nhbVks,9d9f7949b5d3320f0bf34abb11f169a9 2 | 1LVNwy88DovNenEQUtCNmiuB6zYnqJXiZ,9bc44284893bbac303956c69d86f51df 3 | 1LufWJZBSP-qij-4rC_F8BPpf_JFxW2ku,0352392ed807721032db55b627fbebb5 4 | 1OZ9Z71lHZiCLB3cYU9kXktyYJ8VjSFwX,4ec68be97a942c3d67bfdc0f5553ad56 5 | -------------------------------------------------------------------------------- /tests/test___main__.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os 3 | import subprocess 4 | import sys 5 | import tempfile 6 | 7 | from gdown.cached_download import _assert_filehash 8 | from gdown.cached_download import _compute_filehash 9 | 10 | here = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | 13 | def _test_cli_with_md5(url_or_id, md5, options=None): 14 | # We can't use NamedTemporaryFile because Windows doesn't allow the subprocess 15 | # to write the file created by the parent process. 16 | with tempfile.TemporaryDirectory() as d: 17 | file_path = os.path.join(d, "file") 18 | cmd = ["gdown", "--no-cookies", url_or_id, "-O", file_path] 19 | if options is not None: 20 | cmd.extend(options) 21 | subprocess.call(cmd) 22 | _assert_filehash(path=file_path, hash=f"md5:{md5}") 23 | 24 | 25 | def _test_cli_with_content(url_or_id, content): 26 | # We can't use NamedTemporaryFile because Windows doesn't allow the subprocess 27 | # to write the file created by the parent process. 28 | with tempfile.TemporaryDirectory() as d: 29 | file_path = os.path.join(d, "file") 30 | cmd = ["gdown", "--no-cookies", url_or_id, "-O", file_path] 31 | subprocess.call(cmd) 32 | with open(file_path) as f: 33 | assert f.read() == content 34 | 35 | 36 | def test_download_from_url_other_than_gdrive(): 37 | url = "https://raw.githubusercontent.com/wkentaro/gdown/3.1.0/gdown/__init__.py" 38 | md5 = "2a51927dde6b146ce56b4d89ebbb5268" 39 | _test_cli_with_md5(url_or_id=url, md5=md5) 40 | 41 | 42 | def test_download_small_file_from_gdrive(): 43 | with open(os.path.join(here, "data/file_ids.csv")) as f: 44 | file_ids = [file_id.strip() for file_id in f] 45 | 46 | for file_id in file_ids: 47 | try: 48 | _test_cli_with_content(url_or_id=file_id, content="spam\n") 49 | break 50 | except AssertionError as e: 51 | print(e, file=sys.stderr) 52 | continue 53 | else: 54 | raise AssertionError(f"Failed to download any of the files: {file_ids}") 55 | 56 | 57 | def test_download_large_file_from_gdrive(): 58 | with open(os.path.join(here, "data/file_ids_large.csv")) as f: 59 | file_id_and_md5s = [[x.strip() for x in file_id.split(",")] for file_id in f] 60 | 61 | for file_id, md5 in file_id_and_md5s: 62 | try: 63 | _test_cli_with_md5(url_or_id=file_id, md5=md5) 64 | break 65 | except AssertionError as e: 66 | print(e, file=sys.stderr) 67 | continue 68 | else: 69 | file_ids, _ = zip(*file_id_and_md5s) 70 | raise AssertionError(f"Failed to download any of the files: {file_ids}") 71 | 72 | 73 | def test_download_and_extract(): 74 | cmd = "gdown --no-cookies https://github.com/wkentaro/gdown/archive/refs/tags/v4.0.0.tar.gz -O - | tar zxvf -" # noqa: E501 75 | with tempfile.TemporaryDirectory() as d: 76 | subprocess.call(cmd, shell=True, cwd=d) 77 | assert os.path.exists(os.path.join(d, "gdown-4.0.0/gdown/__init__.py")) 78 | 79 | 80 | def test_download_folder_from_gdrive(): 81 | with open(os.path.join(here, "data/folder_ids.csv")) as f: 82 | folder_id_and_md5s = [ 83 | [x.strip() for x in folder_id.split(",")] for folder_id in f 84 | ] 85 | 86 | for folder_id, md5 in folder_id_and_md5s: 87 | with tempfile.TemporaryDirectory() as d: 88 | cmd = ["gdown", "--no-cookies", folder_id, "-O", d, "--folder"] 89 | subprocess.call(cmd) 90 | 91 | md5s_actual = [] 92 | for dirpath, dirnames, filenames in os.walk(d): 93 | for filename in filenames: 94 | md5_actual = _compute_filehash( 95 | path=os.path.join(dirpath, filename), algorithm="md5" 96 | )[len("md5:") :] 97 | md5s_actual.append(md5_actual) 98 | 99 | md5_actual = hashlib.md5( 100 | ("".join(x + "\n" for x in sorted(md5s_actual))).encode() 101 | ).hexdigest() 102 | try: 103 | assert md5_actual == md5 104 | break 105 | except AssertionError as e: 106 | print(e, file=sys.stderr) 107 | else: 108 | file_ids, md5s = zip(*folder_id_and_md5s) 109 | raise AssertionError(f"Failed to download any of the folders: {file_ids}") 110 | 111 | 112 | def test_download_a_folder_with_remining_ok_false(): 113 | with tempfile.TemporaryDirectory() as d: 114 | cmd = [ 115 | "gdown", 116 | "--no-cookies", 117 | "https://drive.google.com/drive/folders/1gd3xLkmjT8IckN6WtMbyFZvLR4exRIkn", 118 | "-O", 119 | d, 120 | "--folder", 121 | ] 122 | assert subprocess.call(cmd) == 1 123 | 124 | 125 | # def test_download_docs_from_gdrive(): 126 | # file_id = "1TFYNzuZJTgNGzGmjraZ58ZVOh9_YoKeBnU-opWgXQL4" 127 | # md5 = "6c17d87d3d01405ac5c9bb65ee2d2fc2" 128 | # _test_cli_with_md5(url_or_id=file_id, md5=md5, options="--format txt") 129 | # 130 | # 131 | # def test_download_spreadsheets_from_gdrive(): 132 | # file_id = "1h6wQX7ATSJDOSWFEjHPmv_nukJzZD_zZ30Jvy6XNiTE" 133 | # md5 = "5be20dd8a23afa06365714edc24856f3" 134 | # _test_cli_with_md5(url_or_id=file_id, md5=md5, options="--format pdf") 135 | 136 | 137 | def test_download_slides_from_gdrive(): 138 | file_id = "13AhW1Z1GYGaiTpJ0Pr2TTXoQivb6jx-a" 139 | md5 = "96704c6c40e308a68d3842e83a0136b9" 140 | _test_cli_with_md5(url_or_id=file_id, md5=md5, options=["--format", "pdf"]) 141 | 142 | 143 | def test_download_a_folder_with_file_content_more_than_the_limit(): 144 | url = "https://drive.google.com/drive/folders/1gd3xLkmjT8IckN6WtMbyFZvLR4exRIkn" 145 | 146 | with tempfile.TemporaryDirectory() as d: 147 | cmd = ["gdown", "--no-cookies", url, "-O", d, "--folder", "--remaining-ok"] 148 | subprocess.check_call(cmd) 149 | 150 | filenames = sorted(os.listdir(d)) 151 | for i in range(50): 152 | assert filenames[i] == f"file_{i:02d}.txt" 153 | -------------------------------------------------------------------------------- /tests/test_cached_download.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | 4 | import gdown 5 | 6 | 7 | def _cached_download(**kwargs): 8 | url = "https://drive.google.com/uc?id=0B9P1L--7Wd2vU3VUVlFnbTgtS2c" 9 | path = tempfile.mktemp() 10 | for _ in range(2): 11 | gdown.cached_download(url=url, path=path, **kwargs) 12 | os.remove(path) 13 | 14 | 15 | def test_cached_download_md5(): 16 | _cached_download(hash="md5:cb31a703b96c1ab2f80d164e9676fe7d") 17 | 18 | 19 | def test_cached_download_sha1(): 20 | _cached_download(hash="sha1:69a5a1000f98237efea9231c8a39d05edf013494") 21 | 22 | 23 | def test_cached_download_sha256(): 24 | _cached_download( 25 | hash="sha256:284e3029cce3ae5ee0b05866100e300046359f53ae4c77fe6b34c05aa7a72cee" 26 | ) 27 | -------------------------------------------------------------------------------- /tests/test_download.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | 4 | from gdown.download import download 5 | 6 | 7 | def test_download(): 8 | with tempfile.TemporaryDirectory() as d: 9 | file_path = os.path.join(d, "file") 10 | url = "https://raw.githubusercontent.com/wkentaro/gdown/3.1.0/gdown/__init__.py" # NOQA 11 | # Usage before https://github.com/wkentaro/gdown/pull/32 12 | assert download(url=url, output=file_path, quiet=False) == file_path 13 | -------------------------------------------------------------------------------- /tests/test_download_folder.py: -------------------------------------------------------------------------------- 1 | import os.path as osp 2 | import tempfile 3 | 4 | from gdown.download_folder import _parse_google_drive_file 5 | from gdown.download_folder import download_folder 6 | 7 | here = osp.dirname(osp.abspath(__file__)) 8 | 9 | 10 | def test_valid_page(): 11 | html_file = osp.join(here, "data/folder-page-sample.html") 12 | with open(html_file) as f: 13 | content = f.read() 14 | folder = "".join( 15 | [ 16 | "https://drive.google.com", 17 | "/drive/folders/1KpLl_1tcK0eeehzN980zbG-3M2nhbVks", 18 | ] 19 | ) 20 | gdrive_file, id_name_type_iter = _parse_google_drive_file( 21 | folder, 22 | content, 23 | ) 24 | assert gdrive_file.id == "1KpLl_1tcK0eeehzN980zbG-3M2nhbVks" 25 | 26 | assert gdrive_file.name == "gdown_folder_test" 27 | assert gdrive_file.type == "application/vnd.google-apps.folder" 28 | assert gdrive_file.children == [] 29 | assert gdrive_file.is_folder() 30 | 31 | expected_children_ids = [ 32 | "1aMZqPaU03E7XOQNXtjSCdguRHBaIQ82m", 33 | "1hVAxfM7_doToqQ24eVd65cgiaoLi0TtO", 34 | "1Z2VYnXb01h-3uvEptoQ48Fo__eAn0wc1", 35 | "14xzOzvKjP0at07jfonV7qVrTKoctFijz", 36 | "1wlapSEt6N9Ayf7fzCTOkra_4GIg-cqeD", 37 | ] 38 | 39 | expected_children_names = [ 40 | "directory-0", 41 | "directory-1", 42 | "fractal.jpg", 43 | "this is a file.txt", 44 | "tux.jpg", 45 | ] 46 | 47 | expected_children_types = [ 48 | "application/vnd.google-apps.folder", 49 | "application/vnd.google-apps.folder", 50 | "image/jpeg", 51 | "text/plain", 52 | "image/jpeg", 53 | ] 54 | 55 | children_info = list(id_name_type_iter) 56 | actual_children_ids = [t[0] for t in children_info] 57 | actual_children_names = [t[1] for t in children_info] 58 | actual_children_types = [t[2] for t in children_info] 59 | 60 | assert actual_children_ids == expected_children_ids 61 | assert actual_children_names == expected_children_names 62 | assert actual_children_types == expected_children_types 63 | 64 | 65 | def test_download_folder_dry_run(): 66 | url = "https://drive.google.com/drive/folders/1KpLl_1tcK0eeehzN980zbG-3M2nhbVks" 67 | tmp_dir = tempfile.mkdtemp() 68 | files = download_folder(url=url, output=tmp_dir, skip_download=True) 69 | assert len(files) == 6 70 | for file in files: 71 | assert hasattr(file, "id") 72 | assert hasattr(file, "path") 73 | assert hasattr(file, "local_path") 74 | -------------------------------------------------------------------------------- /tests/test_parse_url.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from gdown.parse_url import parse_url 4 | 5 | 6 | def test_parse_url(): 7 | file_id = "0B_NiLAzvehC9R2stRmQyM3ZiVjQ" 8 | 9 | # list of (url, expected, check_warn) 10 | urls = [ 11 | ( 12 | "https://drive.google.com/open?id={}".format(file_id), 13 | (file_id, False), 14 | True, 15 | ), 16 | ( 17 | "https://drive.google.com/uc?id={}".format(file_id), 18 | (file_id, True), 19 | False, 20 | ), 21 | ( 22 | "https://drive.google.com/file/d/{}/view?usp=sharing".format(file_id), 23 | (file_id, False), 24 | True, 25 | ), 26 | ( 27 | "https://drive.google.com/a/jsk.imi.i.u-tokyo.ac.jp/uc?id={}&export=download".format( # NOQA 28 | file_id 29 | ), 30 | (file_id, True), 31 | False, 32 | ), 33 | ] 34 | 35 | for url, expected, check_warn in urls: 36 | if check_warn: 37 | with pytest.warns(UserWarning): 38 | assert parse_url(url) == expected 39 | else: 40 | assert parse_url(url) == expected 41 | --------------------------------------------------------------------------------