├── .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 |
--------------------------------------------------------------------------------