igdl 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- igdl-0.1.0/PKG-INFO +252 -0
- igdl-0.1.0/README.md +241 -0
- igdl-0.1.0/igdl/__init__.py +0 -0
- igdl-0.1.0/igdl/cli.py +55 -0
- igdl-0.1.0/igdl/config.py +30 -0
- igdl-0.1.0/igdl/downloader.py +86 -0
- igdl-0.1.0/igdl.egg-info/PKG-INFO +252 -0
- igdl-0.1.0/igdl.egg-info/SOURCES.txt +12 -0
- igdl-0.1.0/igdl.egg-info/dependency_links.txt +1 -0
- igdl-0.1.0/igdl.egg-info/entry_points.txt +2 -0
- igdl-0.1.0/igdl.egg-info/requires.txt +1 -0
- igdl-0.1.0/igdl.egg-info/top_level.txt +1 -0
- igdl-0.1.0/pyproject.toml +19 -0
- igdl-0.1.0/setup.cfg +4 -0
igdl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: igdl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool to download images and videos from Instagram posts
|
|
5
|
+
Author: viviciu
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/viviciu/igdl
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: instaloader
|
|
11
|
+
|
|
12
|
+
# igdl
|
|
13
|
+
|
|
14
|
+
A minimal CLI tool that downloads all images and videos from an Instagram post to a local folder. Paste a link, get the files.
|
|
15
|
+
|
|
16
|
+
- Supports posts, reels, carousels, and IGTV
|
|
17
|
+
- Downloads all slides in a carousel in one command
|
|
18
|
+
- Optional login to bypass Instagram rate limits
|
|
19
|
+
- Configurable download directory and file naming
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Python 3.8+
|
|
26
|
+
- macOS or Linux (Windows untested)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
The easiest way is with [pipx](https://pipx.pypa.io), which handles all the Python environment setup automatically.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Install pipx if you don't have it
|
|
36
|
+
brew install pipx
|
|
37
|
+
|
|
38
|
+
# Install igdl
|
|
39
|
+
pipx install git+https://github.com/viviciu/igdl
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**zsh users:** add this to `~/.zshrc` to prevent zsh from misreading `?` in URLs:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
alias igdl='noglob igdl'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then reload: `source ~/.zshrc`
|
|
49
|
+
|
|
50
|
+
### Manual install (without pipx)
|
|
51
|
+
|
|
52
|
+
<details>
|
|
53
|
+
<summary>Expand</summary>
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/viviciu/igdl ~/igdl
|
|
57
|
+
cd ~/igdl
|
|
58
|
+
python3 -m venv .venv
|
|
59
|
+
.venv/bin/pip install -e .
|
|
60
|
+
mkdir -p ~/.local/bin
|
|
61
|
+
ln -sf ~/igdl/.venv/bin/igdl ~/.local/bin/igdl
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Make sure `~/.local/bin` is in your PATH (`export PATH="$PATH:$HOME/.local/bin"` in `~/.zshrc`), then add the `noglob` alias above.
|
|
65
|
+
|
|
66
|
+
</details>
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Setup
|
|
71
|
+
|
|
72
|
+
Set your download directory once:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
igdl config --dir ~/Downloads/Instagram
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This saves to `~/.igdl/config.json`. Run `igdl config` with no flags to see the current setting.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Download a post (images, videos, or carousels)
|
|
86
|
+
igdl https://www.instagram.com/p/SHORTCODE/
|
|
87
|
+
|
|
88
|
+
# Reels and IGTV work too
|
|
89
|
+
igdl https://www.instagram.com/reel/SHORTCODE/
|
|
90
|
+
igdl https://www.instagram.com/tv/SHORTCODE/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Instagram rate-limits anonymous requests. Log in once to avoid this:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
igdl login your_username
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Your session is saved to `~/.igdl/session_<username>` and reused automatically on future downloads.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Customizing file naming
|
|
104
|
+
|
|
105
|
+
Files are named using [instaloader's filename pattern](https://instaloader.github.io/module/instaloader.html#instaloader.Instaloader). The default is `{date_utc}_UTC` (e.g. `2026-04-08_11-09-53_UTC_1.jpg`).
|
|
106
|
+
|
|
107
|
+
To change it, edit `igdl/downloader.py` in the `_loader()` function and add a `filename_pattern` argument to the `Instaloader()` constructor:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
L = instaloader.Instaloader(
|
|
111
|
+
filename_pattern="{owner_username}_{shortcode}", # ← change this
|
|
112
|
+
...
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Available variables:
|
|
117
|
+
|
|
118
|
+
| Variable | Example |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `{date_utc}` | `2026-04-08_11-09-53` |
|
|
121
|
+
| `{shortcode}` | `DW3lV16CO5K` |
|
|
122
|
+
| `{owner_username}` | `folchstudio` |
|
|
123
|
+
| `{mediaid}` | `3612345678901234567` |
|
|
124
|
+
| `{typename}` | `GraphImage`, `GraphVideo` |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Project structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
igdl/
|
|
132
|
+
pyproject.toml # package metadata and dependencies
|
|
133
|
+
igdl/
|
|
134
|
+
cli.py # argument parsing and command routing
|
|
135
|
+
config.py # read/write ~/.igdl/config.json
|
|
136
|
+
downloader.py # instaloader wrapper — core download logic
|
|
137
|
+
__init__.py
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Runtime data (created automatically, not in the repo):
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
~/.igdl/
|
|
144
|
+
config.json # stores your configured download directory
|
|
145
|
+
session_<username> # saved Instagram session from igdl login
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Dependencies
|
|
151
|
+
|
|
152
|
+
- [instaloader](https://instaloader.github.io) — handles Instagram auth, carousels, and all media types
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
# Why do we install this way? How does install work?
|
|
157
|
+
|
|
158
|
+
This section answers various questions I had along the way, to inform me about why certain dev decisions were made and in which scenarios I'd want to execute them again.
|
|
159
|
+
|
|
160
|
+
## Is a venv created every time you use the tool?
|
|
161
|
+
|
|
162
|
+
No — it's created once when you install, and just sits at ~/igdl/.venv/
|
|
163
|
+
permanently. Every time you run igdl, it uses that same existing environment.
|
|
164
|
+
Nothing is recreated.
|
|
165
|
+
|
|
166
|
+
## Do most distributed CLI tools use a venv?
|
|
167
|
+
|
|
168
|
+
No. The more common approaches for tools meant to be shared are:
|
|
169
|
+
|
|
170
|
+
- pipx — the actual standard for Python CLI tools. It automatically creates an
|
|
171
|
+
isolated environment per tool behind the scenes, so the user never thinks
|
|
172
|
+
about it. pipx install igdl and you're done.
|
|
173
|
+
- brew — macOS users expect brew install sometool. Homebrew handles isolation
|
|
174
|
+
itself.
|
|
175
|
+
- A single script — if the tool has no dependencies or uses only stdlib, just
|
|
176
|
+
ship one .py file.
|
|
177
|
+
|
|
178
|
+
Venvs are really a development tool, not a distribution mechanism. The way we
|
|
179
|
+
set it up is fine for personal use, but if you were seriously publishing this
|
|
180
|
+
for others, you'd set it up for pipx instead.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
## We can't use a single script to run our CLI because we are using external dependencies.
|
|
184
|
+
Instaloader is an external dependency so a
|
|
185
|
+
single script won't work on its own. You'd need the user to have it
|
|
186
|
+
pre-installed, which isn't a great experience.
|
|
187
|
+
|
|
188
|
+
Where dependencies are declared: ~/igdl/pyproject.toml — the dependencies =
|
|
189
|
+
["instaloader"] line. That's the one source of truth. When pip installs the
|
|
190
|
+
package, it reads that file and pulls instaloader in automatically.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
Every file/folder in ~/igdl/:
|
|
194
|
+
|
|
195
|
+
~/igdl/
|
|
196
|
+
├── pyproject.toml # Package metadata: name, version, dependencies,
|
|
197
|
+
│ # and which function to run when you type `igdl`
|
|
198
|
+
├── README.md # Documentation (what you're editing)
|
|
199
|
+
├── .gitignore # Tells git which files/folders to never commit
|
|
200
|
+
│
|
|
201
|
+
├── igdl/ # The actual Python source code
|
|
202
|
+
│ ├── __init__.py # Makes this folder a Python package (can be empty)
|
|
203
|
+
│ ├── cli.py # Parses what you type and routes to the right action
|
|
204
|
+
│ ├── config.py # Reads/writes ~/.igdl/config.json
|
|
205
|
+
│ └── downloader.py # All the Instagram download logic
|
|
206
|
+
│
|
|
207
|
+
├── igdl.egg-info/ # ← Auto-generated by pip when you ran `pip install -e .`
|
|
208
|
+
│ # Bookkeeping metadata pip uses to track the install.
|
|
209
|
+
│ # You never touch it, git ignores it, safe to delete
|
|
210
|
+
│ # (it regenerates itself next time you install)
|
|
211
|
+
│
|
|
212
|
+
└── .venv/ # Your isolated Python environment — Python itself,
|
|
213
|
+
pip, and instaloader all live here. Never committed.
|
|
214
|
+
|
|
215
|
+
## What is `egg-info`? pip's 'notes to self'
|
|
216
|
+
|
|
217
|
+
egg-info is just pip's scratch notes about your local install. The name comes
|
|
218
|
+
from an old Python packaging format called "eggs" that predates the current
|
|
219
|
+
standard — the name stuck even though eggs themselves are gone.
|
|
220
|
+
|
|
221
|
+
## Whats `__pycache__`? Optimization.
|
|
222
|
+
|
|
223
|
+
__init__.py doesn't create __pycache__ — Python itself does. Any time Python
|
|
224
|
+
runs a .py file, it compiles it to bytecode (a faster, pre-parsed version) and
|
|
225
|
+
caches it in __pycache__/. This happens automatically for every .py file that
|
|
226
|
+
gets imported. It's just a performance optimization — Python skips re-parsing
|
|
227
|
+
files it's already seen.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
## Do the Python files communicate with each other?
|
|
231
|
+
|
|
232
|
+
Yes, through import. Look at the top of `cli.py`:
|
|
233
|
+
|
|
234
|
+
`from .config import get_download_dir, set_download_dir`
|
|
235
|
+
`from .downloader import download, login`
|
|
236
|
+
|
|
237
|
+
The . means "from this same package." So when you run igdl, Python loads
|
|
238
|
+
`cli.py`, which pulls in specific functions from config.py and downloader.py.
|
|
239
|
+
They don't run in parallel or send messages — it's more like **cli.py is in
|
|
240
|
+
charge and borrows tools from the other files when it needs them.**
|
|
241
|
+
|
|
242
|
+
The flow when you run igdl <url>:
|
|
243
|
+
|
|
244
|
+
cli.py ← entry point, reads your command
|
|
245
|
+
→ config.py ← "what's the download directory?"
|
|
246
|
+
→ downloader.py ← "go download this URL"
|
|
247
|
+
→ instaloader ← (external library, lives in .venv)
|
|
248
|
+
|
|
249
|
+
## What's `__init__.py`?
|
|
250
|
+
__init__.py just tells Python "this folder is a package, treat the files
|
|
251
|
+
inside as importable modules." **Without it, the from .config import ... lines
|
|
252
|
+
in cli.py wouldn't work.** It's essentially a flag file.
|
igdl-0.1.0/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# igdl
|
|
2
|
+
|
|
3
|
+
A minimal CLI tool that downloads all images and videos from an Instagram post to a local folder. Paste a link, get the files.
|
|
4
|
+
|
|
5
|
+
- Supports posts, reels, carousels, and IGTV
|
|
6
|
+
- Downloads all slides in a carousel in one command
|
|
7
|
+
- Optional login to bypass Instagram rate limits
|
|
8
|
+
- Configurable download directory and file naming
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Python 3.8+
|
|
15
|
+
- macOS or Linux (Windows untested)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
The easiest way is with [pipx](https://pipx.pypa.io), which handles all the Python environment setup automatically.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Install pipx if you don't have it
|
|
25
|
+
brew install pipx
|
|
26
|
+
|
|
27
|
+
# Install igdl
|
|
28
|
+
pipx install git+https://github.com/viviciu/igdl
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**zsh users:** add this to `~/.zshrc` to prevent zsh from misreading `?` in URLs:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
alias igdl='noglob igdl'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then reload: `source ~/.zshrc`
|
|
38
|
+
|
|
39
|
+
### Manual install (without pipx)
|
|
40
|
+
|
|
41
|
+
<details>
|
|
42
|
+
<summary>Expand</summary>
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/viviciu/igdl ~/igdl
|
|
46
|
+
cd ~/igdl
|
|
47
|
+
python3 -m venv .venv
|
|
48
|
+
.venv/bin/pip install -e .
|
|
49
|
+
mkdir -p ~/.local/bin
|
|
50
|
+
ln -sf ~/igdl/.venv/bin/igdl ~/.local/bin/igdl
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Make sure `~/.local/bin` is in your PATH (`export PATH="$PATH:$HOME/.local/bin"` in `~/.zshrc`), then add the `noglob` alias above.
|
|
54
|
+
|
|
55
|
+
</details>
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Setup
|
|
60
|
+
|
|
61
|
+
Set your download directory once:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
igdl config --dir ~/Downloads/Instagram
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This saves to `~/.igdl/config.json`. Run `igdl config` with no flags to see the current setting.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Download a post (images, videos, or carousels)
|
|
75
|
+
igdl https://www.instagram.com/p/SHORTCODE/
|
|
76
|
+
|
|
77
|
+
# Reels and IGTV work too
|
|
78
|
+
igdl https://www.instagram.com/reel/SHORTCODE/
|
|
79
|
+
igdl https://www.instagram.com/tv/SHORTCODE/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Instagram rate-limits anonymous requests. Log in once to avoid this:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
igdl login your_username
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Your session is saved to `~/.igdl/session_<username>` and reused automatically on future downloads.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Customizing file naming
|
|
93
|
+
|
|
94
|
+
Files are named using [instaloader's filename pattern](https://instaloader.github.io/module/instaloader.html#instaloader.Instaloader). The default is `{date_utc}_UTC` (e.g. `2026-04-08_11-09-53_UTC_1.jpg`).
|
|
95
|
+
|
|
96
|
+
To change it, edit `igdl/downloader.py` in the `_loader()` function and add a `filename_pattern` argument to the `Instaloader()` constructor:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
L = instaloader.Instaloader(
|
|
100
|
+
filename_pattern="{owner_username}_{shortcode}", # ← change this
|
|
101
|
+
...
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Available variables:
|
|
106
|
+
|
|
107
|
+
| Variable | Example |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `{date_utc}` | `2026-04-08_11-09-53` |
|
|
110
|
+
| `{shortcode}` | `DW3lV16CO5K` |
|
|
111
|
+
| `{owner_username}` | `folchstudio` |
|
|
112
|
+
| `{mediaid}` | `3612345678901234567` |
|
|
113
|
+
| `{typename}` | `GraphImage`, `GraphVideo` |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Project structure
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
igdl/
|
|
121
|
+
pyproject.toml # package metadata and dependencies
|
|
122
|
+
igdl/
|
|
123
|
+
cli.py # argument parsing and command routing
|
|
124
|
+
config.py # read/write ~/.igdl/config.json
|
|
125
|
+
downloader.py # instaloader wrapper — core download logic
|
|
126
|
+
__init__.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Runtime data (created automatically, not in the repo):
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
~/.igdl/
|
|
133
|
+
config.json # stores your configured download directory
|
|
134
|
+
session_<username> # saved Instagram session from igdl login
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Dependencies
|
|
140
|
+
|
|
141
|
+
- [instaloader](https://instaloader.github.io) — handles Instagram auth, carousels, and all media types
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
# Why do we install this way? How does install work?
|
|
146
|
+
|
|
147
|
+
This section answers various questions I had along the way, to inform me about why certain dev decisions were made and in which scenarios I'd want to execute them again.
|
|
148
|
+
|
|
149
|
+
## Is a venv created every time you use the tool?
|
|
150
|
+
|
|
151
|
+
No — it's created once when you install, and just sits at ~/igdl/.venv/
|
|
152
|
+
permanently. Every time you run igdl, it uses that same existing environment.
|
|
153
|
+
Nothing is recreated.
|
|
154
|
+
|
|
155
|
+
## Do most distributed CLI tools use a venv?
|
|
156
|
+
|
|
157
|
+
No. The more common approaches for tools meant to be shared are:
|
|
158
|
+
|
|
159
|
+
- pipx — the actual standard for Python CLI tools. It automatically creates an
|
|
160
|
+
isolated environment per tool behind the scenes, so the user never thinks
|
|
161
|
+
about it. pipx install igdl and you're done.
|
|
162
|
+
- brew — macOS users expect brew install sometool. Homebrew handles isolation
|
|
163
|
+
itself.
|
|
164
|
+
- A single script — if the tool has no dependencies or uses only stdlib, just
|
|
165
|
+
ship one .py file.
|
|
166
|
+
|
|
167
|
+
Venvs are really a development tool, not a distribution mechanism. The way we
|
|
168
|
+
set it up is fine for personal use, but if you were seriously publishing this
|
|
169
|
+
for others, you'd set it up for pipx instead.
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
## We can't use a single script to run our CLI because we are using external dependencies.
|
|
173
|
+
Instaloader is an external dependency so a
|
|
174
|
+
single script won't work on its own. You'd need the user to have it
|
|
175
|
+
pre-installed, which isn't a great experience.
|
|
176
|
+
|
|
177
|
+
Where dependencies are declared: ~/igdl/pyproject.toml — the dependencies =
|
|
178
|
+
["instaloader"] line. That's the one source of truth. When pip installs the
|
|
179
|
+
package, it reads that file and pulls instaloader in automatically.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
Every file/folder in ~/igdl/:
|
|
183
|
+
|
|
184
|
+
~/igdl/
|
|
185
|
+
├── pyproject.toml # Package metadata: name, version, dependencies,
|
|
186
|
+
│ # and which function to run when you type `igdl`
|
|
187
|
+
├── README.md # Documentation (what you're editing)
|
|
188
|
+
├── .gitignore # Tells git which files/folders to never commit
|
|
189
|
+
│
|
|
190
|
+
├── igdl/ # The actual Python source code
|
|
191
|
+
│ ├── __init__.py # Makes this folder a Python package (can be empty)
|
|
192
|
+
│ ├── cli.py # Parses what you type and routes to the right action
|
|
193
|
+
│ ├── config.py # Reads/writes ~/.igdl/config.json
|
|
194
|
+
│ └── downloader.py # All the Instagram download logic
|
|
195
|
+
│
|
|
196
|
+
├── igdl.egg-info/ # ← Auto-generated by pip when you ran `pip install -e .`
|
|
197
|
+
│ # Bookkeeping metadata pip uses to track the install.
|
|
198
|
+
│ # You never touch it, git ignores it, safe to delete
|
|
199
|
+
│ # (it regenerates itself next time you install)
|
|
200
|
+
│
|
|
201
|
+
└── .venv/ # Your isolated Python environment — Python itself,
|
|
202
|
+
pip, and instaloader all live here. Never committed.
|
|
203
|
+
|
|
204
|
+
## What is `egg-info`? pip's 'notes to self'
|
|
205
|
+
|
|
206
|
+
egg-info is just pip's scratch notes about your local install. The name comes
|
|
207
|
+
from an old Python packaging format called "eggs" that predates the current
|
|
208
|
+
standard — the name stuck even though eggs themselves are gone.
|
|
209
|
+
|
|
210
|
+
## Whats `__pycache__`? Optimization.
|
|
211
|
+
|
|
212
|
+
__init__.py doesn't create __pycache__ — Python itself does. Any time Python
|
|
213
|
+
runs a .py file, it compiles it to bytecode (a faster, pre-parsed version) and
|
|
214
|
+
caches it in __pycache__/. This happens automatically for every .py file that
|
|
215
|
+
gets imported. It's just a performance optimization — Python skips re-parsing
|
|
216
|
+
files it's already seen.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
## Do the Python files communicate with each other?
|
|
220
|
+
|
|
221
|
+
Yes, through import. Look at the top of `cli.py`:
|
|
222
|
+
|
|
223
|
+
`from .config import get_download_dir, set_download_dir`
|
|
224
|
+
`from .downloader import download, login`
|
|
225
|
+
|
|
226
|
+
The . means "from this same package." So when you run igdl, Python loads
|
|
227
|
+
`cli.py`, which pulls in specific functions from config.py and downloader.py.
|
|
228
|
+
They don't run in parallel or send messages — it's more like **cli.py is in
|
|
229
|
+
charge and borrows tools from the other files when it needs them.**
|
|
230
|
+
|
|
231
|
+
The flow when you run igdl <url>:
|
|
232
|
+
|
|
233
|
+
cli.py ← entry point, reads your command
|
|
234
|
+
→ config.py ← "what's the download directory?"
|
|
235
|
+
→ downloader.py ← "go download this URL"
|
|
236
|
+
→ instaloader ← (external library, lives in .venv)
|
|
237
|
+
|
|
238
|
+
## What's `__init__.py`?
|
|
239
|
+
__init__.py just tells Python "this folder is a package, treat the files
|
|
240
|
+
inside as importable modules." **Without it, the from .config import ... lines
|
|
241
|
+
in cli.py wouldn't work.** It's essentially a flag file.
|
|
File without changes
|
igdl-0.1.0/igdl/cli.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from .config import get_download_dir, set_download_dir
|
|
5
|
+
from .downloader import download, login
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
# Short-circuit URL detection before argparse sees it,
|
|
10
|
+
# so subparsers don't conflict with bare URL arguments.
|
|
11
|
+
if len(sys.argv) > 1 and sys.argv[1].startswith(("http://", "https://")):
|
|
12
|
+
url = sys.argv[1]
|
|
13
|
+
download_dir = get_download_dir()
|
|
14
|
+
if not download_dir:
|
|
15
|
+
print(
|
|
16
|
+
"No download directory configured.\n"
|
|
17
|
+
"Run: igdl config --dir ~/path/to/folder",
|
|
18
|
+
file=sys.stderr,
|
|
19
|
+
)
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
download(url, download_dir)
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="igdl",
|
|
26
|
+
description="Download images/videos from an Instagram post.",
|
|
27
|
+
epilog="Usage: igdl <url>",
|
|
28
|
+
)
|
|
29
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
30
|
+
|
|
31
|
+
config_parser = subparsers.add_parser("config", help="Set configuration options")
|
|
32
|
+
config_parser.add_argument("--dir", metavar="PATH", help="Set the download directory")
|
|
33
|
+
|
|
34
|
+
login_parser = subparsers.add_parser("login", help="Save an Instagram session to bypass rate limits")
|
|
35
|
+
login_parser.add_argument("username", help="Your Instagram username")
|
|
36
|
+
|
|
37
|
+
args = parser.parse_args()
|
|
38
|
+
|
|
39
|
+
if args.command == "config":
|
|
40
|
+
if args.dir:
|
|
41
|
+
resolved = set_download_dir(args.dir)
|
|
42
|
+
print(f"Download directory set to: {resolved}")
|
|
43
|
+
else:
|
|
44
|
+
d = get_download_dir()
|
|
45
|
+
if d:
|
|
46
|
+
print(f"Download directory: {d}")
|
|
47
|
+
else:
|
|
48
|
+
print("No download directory set. Use: igdl config --dir <path>")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if args.command == "login":
|
|
52
|
+
login(args.username)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
parser.print_help()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
CONFIG_DIR = Path.home() / ".igdl"
|
|
6
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_config() -> dict:
|
|
10
|
+
if not CONFIG_FILE.exists():
|
|
11
|
+
return {}
|
|
12
|
+
with open(CONFIG_FILE) as f:
|
|
13
|
+
return json.load(f)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_download_dir() -> Path | None:
|
|
17
|
+
cfg = get_config()
|
|
18
|
+
d = cfg.get("download_dir")
|
|
19
|
+
return Path(d) if d else None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def set_download_dir(path: str) -> Path:
|
|
23
|
+
resolved = Path(path).expanduser().resolve()
|
|
24
|
+
resolved.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
cfg = get_config()
|
|
27
|
+
cfg["download_dir"] = str(resolved)
|
|
28
|
+
with open(CONFIG_FILE, "w") as f:
|
|
29
|
+
json.dump(cfg, f, indent=2)
|
|
30
|
+
return resolved
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import instaloader
|
|
6
|
+
from instaloader import LoginRequiredException, Post
|
|
7
|
+
|
|
8
|
+
SESSION_DIR = Path.home() / ".igdl"
|
|
9
|
+
SHORTCODE_RE = re.compile(r"instagram\.com/(?:p|reel|tv)/([A-Za-z0-9_-]+)")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def extract_shortcode(url: str) -> str:
|
|
13
|
+
m = SHORTCODE_RE.search(url)
|
|
14
|
+
if not m:
|
|
15
|
+
print("Error: could not parse Instagram URL. Expected a /p/, /reel/, or /tv/ link.", file=sys.stderr)
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
return m.group(1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _loader(username: str | None = None, download_dir: Path | None = None) -> instaloader.Instaloader:
|
|
21
|
+
L = instaloader.Instaloader(
|
|
22
|
+
download_video_thumbnails=False,
|
|
23
|
+
save_metadata=False,
|
|
24
|
+
download_geotags=False,
|
|
25
|
+
download_comments=False,
|
|
26
|
+
post_metadata_txt_pattern="",
|
|
27
|
+
dirname_pattern=str(download_dir) if download_dir else "{target}",
|
|
28
|
+
)
|
|
29
|
+
if username:
|
|
30
|
+
session_file = SESSION_DIR / f"session_{username}"
|
|
31
|
+
if session_file.exists():
|
|
32
|
+
L.load_session_from_file(username, str(session_file))
|
|
33
|
+
return L
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _saved_usernames() -> list[str]:
|
|
37
|
+
return [
|
|
38
|
+
f.name[len("session_"):]
|
|
39
|
+
for f in SESSION_DIR.iterdir()
|
|
40
|
+
if f.name.startswith("session_")
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def download(url: str, download_dir: Path) -> None:
|
|
45
|
+
shortcode = extract_shortcode(url)
|
|
46
|
+
|
|
47
|
+
# Try with a saved session first, fall back to anonymous
|
|
48
|
+
usernames = _saved_usernames()
|
|
49
|
+
L = _loader(usernames[0] if usernames else None, download_dir=download_dir)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
post = Post.from_shortcode(L.context, shortcode)
|
|
53
|
+
print(f"Downloading post by @{post.owner_username} to {download_dir}/")
|
|
54
|
+
L.download_post(post, target=shortcode)
|
|
55
|
+
print("Done.")
|
|
56
|
+
except LoginRequiredException:
|
|
57
|
+
print(
|
|
58
|
+
"Error: Instagram requires a login to download this post.\n"
|
|
59
|
+
"Run `igdl login` to save your session, then try again.",
|
|
60
|
+
file=sys.stderr,
|
|
61
|
+
)
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
except instaloader.exceptions.InstaloaderException as e:
|
|
64
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def login(username: str) -> None:
|
|
69
|
+
L = instaloader.Instaloader()
|
|
70
|
+
import getpass
|
|
71
|
+
password = getpass.getpass(f"Instagram password for {username}: ")
|
|
72
|
+
try:
|
|
73
|
+
L.login(username, password)
|
|
74
|
+
SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
session_file = str(SESSION_DIR / f"session_{username}")
|
|
76
|
+
L.save_session_to_file(session_file)
|
|
77
|
+
print(f"Session saved. You can now download private/rate-limited posts.")
|
|
78
|
+
except instaloader.exceptions.BadCredentialsException:
|
|
79
|
+
print("Error: incorrect username or password.", file=sys.stderr)
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
except instaloader.exceptions.TwoFactorAuthRequiredException:
|
|
82
|
+
code = input("Two-factor auth code: ")
|
|
83
|
+
L.two_factor_login(code)
|
|
84
|
+
session_file = str(SESSION_DIR / f"session_{username}")
|
|
85
|
+
L.save_session_to_file(session_file)
|
|
86
|
+
print("Session saved.")
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: igdl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool to download images and videos from Instagram posts
|
|
5
|
+
Author: viviciu
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/viviciu/igdl
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: instaloader
|
|
11
|
+
|
|
12
|
+
# igdl
|
|
13
|
+
|
|
14
|
+
A minimal CLI tool that downloads all images and videos from an Instagram post to a local folder. Paste a link, get the files.
|
|
15
|
+
|
|
16
|
+
- Supports posts, reels, carousels, and IGTV
|
|
17
|
+
- Downloads all slides in a carousel in one command
|
|
18
|
+
- Optional login to bypass Instagram rate limits
|
|
19
|
+
- Configurable download directory and file naming
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Python 3.8+
|
|
26
|
+
- macOS or Linux (Windows untested)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
The easiest way is with [pipx](https://pipx.pypa.io), which handles all the Python environment setup automatically.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Install pipx if you don't have it
|
|
36
|
+
brew install pipx
|
|
37
|
+
|
|
38
|
+
# Install igdl
|
|
39
|
+
pipx install git+https://github.com/viviciu/igdl
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**zsh users:** add this to `~/.zshrc` to prevent zsh from misreading `?` in URLs:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
alias igdl='noglob igdl'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then reload: `source ~/.zshrc`
|
|
49
|
+
|
|
50
|
+
### Manual install (without pipx)
|
|
51
|
+
|
|
52
|
+
<details>
|
|
53
|
+
<summary>Expand</summary>
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/viviciu/igdl ~/igdl
|
|
57
|
+
cd ~/igdl
|
|
58
|
+
python3 -m venv .venv
|
|
59
|
+
.venv/bin/pip install -e .
|
|
60
|
+
mkdir -p ~/.local/bin
|
|
61
|
+
ln -sf ~/igdl/.venv/bin/igdl ~/.local/bin/igdl
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Make sure `~/.local/bin` is in your PATH (`export PATH="$PATH:$HOME/.local/bin"` in `~/.zshrc`), then add the `noglob` alias above.
|
|
65
|
+
|
|
66
|
+
</details>
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Setup
|
|
71
|
+
|
|
72
|
+
Set your download directory once:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
igdl config --dir ~/Downloads/Instagram
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This saves to `~/.igdl/config.json`. Run `igdl config` with no flags to see the current setting.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Download a post (images, videos, or carousels)
|
|
86
|
+
igdl https://www.instagram.com/p/SHORTCODE/
|
|
87
|
+
|
|
88
|
+
# Reels and IGTV work too
|
|
89
|
+
igdl https://www.instagram.com/reel/SHORTCODE/
|
|
90
|
+
igdl https://www.instagram.com/tv/SHORTCODE/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Instagram rate-limits anonymous requests. Log in once to avoid this:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
igdl login your_username
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Your session is saved to `~/.igdl/session_<username>` and reused automatically on future downloads.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Customizing file naming
|
|
104
|
+
|
|
105
|
+
Files are named using [instaloader's filename pattern](https://instaloader.github.io/module/instaloader.html#instaloader.Instaloader). The default is `{date_utc}_UTC` (e.g. `2026-04-08_11-09-53_UTC_1.jpg`).
|
|
106
|
+
|
|
107
|
+
To change it, edit `igdl/downloader.py` in the `_loader()` function and add a `filename_pattern` argument to the `Instaloader()` constructor:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
L = instaloader.Instaloader(
|
|
111
|
+
filename_pattern="{owner_username}_{shortcode}", # ← change this
|
|
112
|
+
...
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Available variables:
|
|
117
|
+
|
|
118
|
+
| Variable | Example |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `{date_utc}` | `2026-04-08_11-09-53` |
|
|
121
|
+
| `{shortcode}` | `DW3lV16CO5K` |
|
|
122
|
+
| `{owner_username}` | `folchstudio` |
|
|
123
|
+
| `{mediaid}` | `3612345678901234567` |
|
|
124
|
+
| `{typename}` | `GraphImage`, `GraphVideo` |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Project structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
igdl/
|
|
132
|
+
pyproject.toml # package metadata and dependencies
|
|
133
|
+
igdl/
|
|
134
|
+
cli.py # argument parsing and command routing
|
|
135
|
+
config.py # read/write ~/.igdl/config.json
|
|
136
|
+
downloader.py # instaloader wrapper — core download logic
|
|
137
|
+
__init__.py
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Runtime data (created automatically, not in the repo):
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
~/.igdl/
|
|
144
|
+
config.json # stores your configured download directory
|
|
145
|
+
session_<username> # saved Instagram session from igdl login
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Dependencies
|
|
151
|
+
|
|
152
|
+
- [instaloader](https://instaloader.github.io) — handles Instagram auth, carousels, and all media types
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
# Why do we install this way? How does install work?
|
|
157
|
+
|
|
158
|
+
This section answers various questions I had along the way, to inform me about why certain dev decisions were made and in which scenarios I'd want to execute them again.
|
|
159
|
+
|
|
160
|
+
## Is a venv created every time you use the tool?
|
|
161
|
+
|
|
162
|
+
No — it's created once when you install, and just sits at ~/igdl/.venv/
|
|
163
|
+
permanently. Every time you run igdl, it uses that same existing environment.
|
|
164
|
+
Nothing is recreated.
|
|
165
|
+
|
|
166
|
+
## Do most distributed CLI tools use a venv?
|
|
167
|
+
|
|
168
|
+
No. The more common approaches for tools meant to be shared are:
|
|
169
|
+
|
|
170
|
+
- pipx — the actual standard for Python CLI tools. It automatically creates an
|
|
171
|
+
isolated environment per tool behind the scenes, so the user never thinks
|
|
172
|
+
about it. pipx install igdl and you're done.
|
|
173
|
+
- brew — macOS users expect brew install sometool. Homebrew handles isolation
|
|
174
|
+
itself.
|
|
175
|
+
- A single script — if the tool has no dependencies or uses only stdlib, just
|
|
176
|
+
ship one .py file.
|
|
177
|
+
|
|
178
|
+
Venvs are really a development tool, not a distribution mechanism. The way we
|
|
179
|
+
set it up is fine for personal use, but if you were seriously publishing this
|
|
180
|
+
for others, you'd set it up for pipx instead.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
## We can't use a single script to run our CLI because we are using external dependencies.
|
|
184
|
+
Instaloader is an external dependency so a
|
|
185
|
+
single script won't work on its own. You'd need the user to have it
|
|
186
|
+
pre-installed, which isn't a great experience.
|
|
187
|
+
|
|
188
|
+
Where dependencies are declared: ~/igdl/pyproject.toml — the dependencies =
|
|
189
|
+
["instaloader"] line. That's the one source of truth. When pip installs the
|
|
190
|
+
package, it reads that file and pulls instaloader in automatically.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
Every file/folder in ~/igdl/:
|
|
194
|
+
|
|
195
|
+
~/igdl/
|
|
196
|
+
├── pyproject.toml # Package metadata: name, version, dependencies,
|
|
197
|
+
│ # and which function to run when you type `igdl`
|
|
198
|
+
├── README.md # Documentation (what you're editing)
|
|
199
|
+
├── .gitignore # Tells git which files/folders to never commit
|
|
200
|
+
│
|
|
201
|
+
├── igdl/ # The actual Python source code
|
|
202
|
+
│ ├── __init__.py # Makes this folder a Python package (can be empty)
|
|
203
|
+
│ ├── cli.py # Parses what you type and routes to the right action
|
|
204
|
+
│ ├── config.py # Reads/writes ~/.igdl/config.json
|
|
205
|
+
│ └── downloader.py # All the Instagram download logic
|
|
206
|
+
│
|
|
207
|
+
├── igdl.egg-info/ # ← Auto-generated by pip when you ran `pip install -e .`
|
|
208
|
+
│ # Bookkeeping metadata pip uses to track the install.
|
|
209
|
+
│ # You never touch it, git ignores it, safe to delete
|
|
210
|
+
│ # (it regenerates itself next time you install)
|
|
211
|
+
│
|
|
212
|
+
└── .venv/ # Your isolated Python environment — Python itself,
|
|
213
|
+
pip, and instaloader all live here. Never committed.
|
|
214
|
+
|
|
215
|
+
## What is `egg-info`? pip's 'notes to self'
|
|
216
|
+
|
|
217
|
+
egg-info is just pip's scratch notes about your local install. The name comes
|
|
218
|
+
from an old Python packaging format called "eggs" that predates the current
|
|
219
|
+
standard — the name stuck even though eggs themselves are gone.
|
|
220
|
+
|
|
221
|
+
## Whats `__pycache__`? Optimization.
|
|
222
|
+
|
|
223
|
+
__init__.py doesn't create __pycache__ — Python itself does. Any time Python
|
|
224
|
+
runs a .py file, it compiles it to bytecode (a faster, pre-parsed version) and
|
|
225
|
+
caches it in __pycache__/. This happens automatically for every .py file that
|
|
226
|
+
gets imported. It's just a performance optimization — Python skips re-parsing
|
|
227
|
+
files it's already seen.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
## Do the Python files communicate with each other?
|
|
231
|
+
|
|
232
|
+
Yes, through import. Look at the top of `cli.py`:
|
|
233
|
+
|
|
234
|
+
`from .config import get_download_dir, set_download_dir`
|
|
235
|
+
`from .downloader import download, login`
|
|
236
|
+
|
|
237
|
+
The . means "from this same package." So when you run igdl, Python loads
|
|
238
|
+
`cli.py`, which pulls in specific functions from config.py and downloader.py.
|
|
239
|
+
They don't run in parallel or send messages — it's more like **cli.py is in
|
|
240
|
+
charge and borrows tools from the other files when it needs them.**
|
|
241
|
+
|
|
242
|
+
The flow when you run igdl <url>:
|
|
243
|
+
|
|
244
|
+
cli.py ← entry point, reads your command
|
|
245
|
+
→ config.py ← "what's the download directory?"
|
|
246
|
+
→ downloader.py ← "go download this URL"
|
|
247
|
+
→ instaloader ← (external library, lives in .venv)
|
|
248
|
+
|
|
249
|
+
## What's `__init__.py`?
|
|
250
|
+
__init__.py just tells Python "this folder is a package, treat the files
|
|
251
|
+
inside as importable modules." **Without it, the from .config import ... lines
|
|
252
|
+
in cli.py wouldn't work.** It's essentially a flag file.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
igdl/__init__.py
|
|
4
|
+
igdl/cli.py
|
|
5
|
+
igdl/config.py
|
|
6
|
+
igdl/downloader.py
|
|
7
|
+
igdl.egg-info/PKG-INFO
|
|
8
|
+
igdl.egg-info/SOURCES.txt
|
|
9
|
+
igdl.egg-info/dependency_links.txt
|
|
10
|
+
igdl.egg-info/entry_points.txt
|
|
11
|
+
igdl.egg-info/requires.txt
|
|
12
|
+
igdl.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
instaloader
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
igdl
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "igdl"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI tool to download images and videos from Instagram posts"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "viviciu" }]
|
|
13
|
+
dependencies = ["instaloader"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://github.com/viviciu/igdl"
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
igdl = "igdl.cli:main"
|
igdl-0.1.0/setup.cfg
ADDED