igtools-pip 1.0.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.
- igtools_pip-1.0.0/LICENSE +21 -0
- igtools_pip-1.0.0/PKG-INFO +96 -0
- igtools_pip-1.0.0/README.md +76 -0
- igtools_pip-1.0.0/igtools/__init__.py +19 -0
- igtools_pip-1.0.0/igtools/cli.py +608 -0
- igtools_pip-1.0.0/igtools_pip.egg-info/PKG-INFO +96 -0
- igtools_pip-1.0.0/igtools_pip.egg-info/SOURCES.txt +13 -0
- igtools_pip-1.0.0/igtools_pip.egg-info/dependency_links.txt +1 -0
- igtools_pip-1.0.0/igtools_pip.egg-info/entry_points.txt +3 -0
- igtools_pip-1.0.0/igtools_pip.egg-info/requires.txt +5 -0
- igtools_pip-1.0.0/igtools_pip.egg-info/top_level.txt +1 -0
- igtools_pip-1.0.0/pyproject.toml +33 -0
- igtools_pip-1.0.0/setup.cfg +4 -0
- igtools_pip-1.0.0/setup.py +4 -0
- igtools_pip-1.0.0/tests/test_igtools.py +89 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 spyboy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: igtools-pip
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Instagram Privacy & OSINT Tool: Hide followers/following lists and find user comments via Google Dork queries.
|
|
5
|
+
Author-email: spyboy <spyboy.co@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/IM-SPYBOY/igtools
|
|
8
|
+
Project-URL: Telegram, https://t.me/spytube
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: instagrapi
|
|
16
|
+
Requires-Dist: googlesearch-python
|
|
17
|
+
Requires-Dist: colorama
|
|
18
|
+
Requires-Dist: termcolor
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# IGTools - Instagram Privacy & OSINT Tool 🔍🔒
|
|
22
|
+
|
|
23
|
+
**IGTools** (`igtools`) is a Python CLI tool and library built by **SPYBOY** that combines:
|
|
24
|
+
1. **Hide / Unhide Followers & Following List**: Enables or disables the `hide_followers_following_list` privacy setting on your Instagram account via Instagram's GraphQL Pando API.
|
|
25
|
+
2. **Find Comments on Posts via Google Query (Dork Search)**: Unlimited real-time Google Dork search (`site:instagram.com intext:<username>`) to discover posts where a target user has left comments or been mentioned.
|
|
26
|
+
3. **Session Persistence (`session.json`)**: Log in once with your Instagram username & password (using `instagrapi`, just like `instaspy-pip`) or with a `sessionid` cookie, and automatically reuse `session.json` across runs.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
Install directly from PyPI:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install igtools-pip
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or install from source:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/IM-SPYBOY/igtools.git
|
|
42
|
+
cd igtools
|
|
43
|
+
pip install .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### 1. Interactive Menu (like `instaspy-pip`)
|
|
51
|
+
|
|
52
|
+
Simply run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
igtools
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
You will see the interactive menu:
|
|
59
|
+
1. **Hide Followers & Following List (Enable)**
|
|
60
|
+
2. **Unhide Followers & Following List (Disable)**
|
|
61
|
+
3. **Find Comments on Posts via Google Query (Dork Search)**
|
|
62
|
+
4. **Login / Switch Instagram Session**
|
|
63
|
+
5. **Exit**
|
|
64
|
+
|
|
65
|
+
### 2. Direct CLI Subcommands
|
|
66
|
+
|
|
67
|
+
- **Log in & save `session.json`:**
|
|
68
|
+
```bash
|
|
69
|
+
igtools login
|
|
70
|
+
```
|
|
71
|
+
- **Hide your followers & following list (uses saved `session.json` or prompts to log in):**
|
|
72
|
+
```bash
|
|
73
|
+
igtools hide-lists
|
|
74
|
+
```
|
|
75
|
+
- **Unhide your followers & following list:**
|
|
76
|
+
```bash
|
|
77
|
+
igtools hide-lists --disable
|
|
78
|
+
```
|
|
79
|
+
- **Find posts where a user commented via Google Dork query:**
|
|
80
|
+
```bash
|
|
81
|
+
igtools dork <target_username>
|
|
82
|
+
# or
|
|
83
|
+
igtools find-comments <target_username>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Configuration & Security
|
|
89
|
+
|
|
90
|
+
- Your Instagram session is saved locally in `session.json`. Keep this file secure and never commit it to public repositories.
|
|
91
|
+
- You can also specify a custom session file path via `igtools --session-file /path/to/session.json`.
|
|
92
|
+
|
|
93
|
+
## Author
|
|
94
|
+
- **SPYBOY**
|
|
95
|
+
- [Telegram Channel](https://t.me/spytube)
|
|
96
|
+
- [GitHub](https://github.com/IM-SPYBOY)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# IGTools - Instagram Privacy & OSINT Tool 🔍🔒
|
|
2
|
+
|
|
3
|
+
**IGTools** (`igtools`) is a Python CLI tool and library built by **SPYBOY** that combines:
|
|
4
|
+
1. **Hide / Unhide Followers & Following List**: Enables or disables the `hide_followers_following_list` privacy setting on your Instagram account via Instagram's GraphQL Pando API.
|
|
5
|
+
2. **Find Comments on Posts via Google Query (Dork Search)**: Unlimited real-time Google Dork search (`site:instagram.com intext:<username>`) to discover posts where a target user has left comments or been mentioned.
|
|
6
|
+
3. **Session Persistence (`session.json`)**: Log in once with your Instagram username & password (using `instagrapi`, just like `instaspy-pip`) or with a `sessionid` cookie, and automatically reuse `session.json` across runs.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Install directly from PyPI:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install igtools-pip
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install from source:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/IM-SPYBOY/igtools.git
|
|
22
|
+
cd igtools
|
|
23
|
+
pip install .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### 1. Interactive Menu (like `instaspy-pip`)
|
|
31
|
+
|
|
32
|
+
Simply run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
igtools
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
You will see the interactive menu:
|
|
39
|
+
1. **Hide Followers & Following List (Enable)**
|
|
40
|
+
2. **Unhide Followers & Following List (Disable)**
|
|
41
|
+
3. **Find Comments on Posts via Google Query (Dork Search)**
|
|
42
|
+
4. **Login / Switch Instagram Session**
|
|
43
|
+
5. **Exit**
|
|
44
|
+
|
|
45
|
+
### 2. Direct CLI Subcommands
|
|
46
|
+
|
|
47
|
+
- **Log in & save `session.json`:**
|
|
48
|
+
```bash
|
|
49
|
+
igtools login
|
|
50
|
+
```
|
|
51
|
+
- **Hide your followers & following list (uses saved `session.json` or prompts to log in):**
|
|
52
|
+
```bash
|
|
53
|
+
igtools hide-lists
|
|
54
|
+
```
|
|
55
|
+
- **Unhide your followers & following list:**
|
|
56
|
+
```bash
|
|
57
|
+
igtools hide-lists --disable
|
|
58
|
+
```
|
|
59
|
+
- **Find posts where a user commented via Google Dork query:**
|
|
60
|
+
```bash
|
|
61
|
+
igtools dork <target_username>
|
|
62
|
+
# or
|
|
63
|
+
igtools find-comments <target_username>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Configuration & Security
|
|
69
|
+
|
|
70
|
+
- Your Instagram session is saved locally in `session.json`. Keep this file secure and never commit it to public repositories.
|
|
71
|
+
- You can also specify a custom session file path via `igtools --session-file /path/to/session.json`.
|
|
72
|
+
|
|
73
|
+
## Author
|
|
74
|
+
- **SPYBOY**
|
|
75
|
+
- [Telegram Channel](https://t.me/spytube)
|
|
76
|
+
- [GitHub](https://github.com/IM-SPYBOY)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .cli import (
|
|
2
|
+
hide_lists,
|
|
3
|
+
collect_google_dork_data,
|
|
4
|
+
load_session,
|
|
5
|
+
save_session,
|
|
6
|
+
extract_sessionid,
|
|
7
|
+
main,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__version__ = "1.0.0"
|
|
11
|
+
__author__ = "SPYBOY"
|
|
12
|
+
__all__ = [
|
|
13
|
+
"hide_lists",
|
|
14
|
+
"collect_google_dork_data",
|
|
15
|
+
"load_session",
|
|
16
|
+
"save_session",
|
|
17
|
+
"extract_sessionid",
|
|
18
|
+
"main",
|
|
19
|
+
]
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import base64
|
|
3
|
+
import getpass
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import random
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from colorama import Fore, Style, init
|
|
16
|
+
from termcolor import colored
|
|
17
|
+
|
|
18
|
+
# All Copyright (C) 2024-2026 by the author(s) of this script. All rights reserved.
|
|
19
|
+
# Telegram channel: https://t.me/spytube
|
|
20
|
+
|
|
21
|
+
# Initialize colorama
|
|
22
|
+
init(autoreset=True)
|
|
23
|
+
|
|
24
|
+
IS_WINDOWS = os.name == "nt"
|
|
25
|
+
DEFAULT_SESSION_FILE = "session.json"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def clear_screen() -> None:
|
|
29
|
+
os.system("cls" if IS_WINDOWS else "clear")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def show_banner() -> None:
|
|
33
|
+
clear_screen()
|
|
34
|
+
print(
|
|
35
|
+
colored(
|
|
36
|
+
r"""
|
|
37
|
+
@@@ @@@@@@@@ @@@@@@@ @@@@@@ @@@@@@ @@@ @@@@@@
|
|
38
|
+
@@@ @@@@@@@@@ @@@@@@@ @@@@@@@@ @@@@@@@@ @@@ @@@@@@@
|
|
39
|
+
@@! !@@ @@! @@! @@@ @@! @@@ @@! !@@
|
|
40
|
+
!@! !@! !@! !@! @!@ !@! @!@ !@! !@!
|
|
41
|
+
!!@ !@! @!@!@ @!! @!@ !@! @!@ !@! @!! !!@@!!
|
|
42
|
+
!!! !!! !!@!! !!! !@! !!! !@! !!! !!! !!@!!!
|
|
43
|
+
!!: :!! !!: !!: !!: !!! !!: !!! !!: !:!
|
|
44
|
+
:!: :!: !:: :!: :!: !:! :!: !:! :!: !:!
|
|
45
|
+
:: ::: :::: :: ::::: :: ::::: :: :: :::: :::: ::
|
|
46
|
+
: :: :: : : : : : : : : : :: : : :: : :
|
|
47
|
+
|
|
48
|
+
IGTools v1.0.0 by SPYBOY
|
|
49
|
+
""",
|
|
50
|
+
"cyan",
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
print(f"{Fore.YELLOW}Platform: {sys.platform}")
|
|
54
|
+
print(f"{Fore.GREEN}Python: {sys.version.split()[0]}")
|
|
55
|
+
print(f"{Fore.CYAN}Telegram: https://t.me/spytube\n")
|
|
56
|
+
print(f"{Fore.MAGENTA}Instagram Privacy & OSINT Suite | Session-backed | Real-time output\n")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ==================== SESSION MANAGEMENT ====================
|
|
60
|
+
def save_session(client, session_file: str = DEFAULT_SESSION_FILE, raw_sessionid: Optional[str] = None) -> bool:
|
|
61
|
+
"""Save session data from instagrapi Client (or raw sessionid) to file."""
|
|
62
|
+
try:
|
|
63
|
+
settings = client.get_settings() if client is not None else {}
|
|
64
|
+
if raw_sessionid:
|
|
65
|
+
settings.setdefault("authorization_data", {})["sessionid"] = raw_sessionid
|
|
66
|
+
match = re.fullmatch(r"([1-9][0-9]*)(?::|%3[aA]).+", raw_sessionid)
|
|
67
|
+
if match:
|
|
68
|
+
settings["authorization_data"]["ds_user_id"] = match.group(1)
|
|
69
|
+
settings.setdefault("cookies", {})["sessionid"] = raw_sessionid
|
|
70
|
+
with open(session_file, "w", encoding="utf-8") as f:
|
|
71
|
+
json.dump(settings, f, indent=2)
|
|
72
|
+
print(f"{Fore.GREEN}✓ Session saved successfully to {session_file}")
|
|
73
|
+
return True
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(f"{Fore.RED}✗ Error saving session: {e}")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def extract_sessionid_from_dict(data: dict) -> Optional[str]:
|
|
80
|
+
"""Extract sessionid cookie value from instagrapi settings dict or custom dict."""
|
|
81
|
+
if not isinstance(data, dict):
|
|
82
|
+
return None
|
|
83
|
+
# 1. Direct sessionid key
|
|
84
|
+
if isinstance(data.get("sessionid"), str) and data["sessionid"].strip():
|
|
85
|
+
return data["sessionid"].strip()
|
|
86
|
+
# 2. instagrapi authorization_data.sessionid
|
|
87
|
+
auth_data = data.get("authorization_data")
|
|
88
|
+
if isinstance(auth_data, dict):
|
|
89
|
+
sid = auth_data.get("sessionid")
|
|
90
|
+
if isinstance(sid, str) and sid.strip():
|
|
91
|
+
return sid.strip()
|
|
92
|
+
# 3. cookies.sessionid
|
|
93
|
+
cookies = data.get("cookies")
|
|
94
|
+
if isinstance(cookies, dict):
|
|
95
|
+
sid = cookies.get("sessionid")
|
|
96
|
+
if isinstance(sid, str) and sid.strip():
|
|
97
|
+
return sid.strip()
|
|
98
|
+
# 4. Pre-built Bearer IGT:2:<base64> header in authorization_data or headers
|
|
99
|
+
for container in (data, auth_data if isinstance(auth_data, dict) else {}):
|
|
100
|
+
auth_header = container.get("Authorization") or container.get("authorization")
|
|
101
|
+
if isinstance(auth_header, str) and "IGT:2:" in auth_header:
|
|
102
|
+
b64_part = auth_header.split("IGT:2:", 1)[1].strip()
|
|
103
|
+
try:
|
|
104
|
+
decoded = json.loads(base64.b64decode(b64_part).decode("utf-8"))
|
|
105
|
+
if isinstance(decoded, dict) and isinstance(decoded.get("sessionid"), str):
|
|
106
|
+
return decoded["sessionid"].strip()
|
|
107
|
+
except Exception:
|
|
108
|
+
pass
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def extract_sessionid(client=None, session_file: str = DEFAULT_SESSION_FILE) -> Optional[str]:
|
|
113
|
+
"""Extract sessionid from an active instagrapi Client or from session_file."""
|
|
114
|
+
if client is not None:
|
|
115
|
+
# Check client.sessionid attribute if present
|
|
116
|
+
sid = getattr(client, "sessionid", None)
|
|
117
|
+
if isinstance(sid, str) and sid.strip():
|
|
118
|
+
return sid.strip()
|
|
119
|
+
try:
|
|
120
|
+
settings = client.get_settings()
|
|
121
|
+
sid = extract_sessionid_from_dict(settings)
|
|
122
|
+
if sid:
|
|
123
|
+
return sid
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
if session_file and os.path.exists(session_file):
|
|
127
|
+
try:
|
|
128
|
+
with open(session_file, "r", encoding="utf-8") as f:
|
|
129
|
+
data = json.load(f)
|
|
130
|
+
return extract_sessionid_from_dict(data)
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def load_session(client=None, session_file: str = DEFAULT_SESSION_FILE) -> Optional[str]:
|
|
137
|
+
"""Load session data from session.json into instagrapi client and return sessionid."""
|
|
138
|
+
if not os.path.exists(session_file):
|
|
139
|
+
return None
|
|
140
|
+
try:
|
|
141
|
+
with open(session_file, "r", encoding="utf-8") as f:
|
|
142
|
+
settings = json.load(f)
|
|
143
|
+
if client is not None and isinstance(settings, dict):
|
|
144
|
+
try:
|
|
145
|
+
client.set_settings(settings)
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
sid = extract_sessionid_from_dict(settings)
|
|
149
|
+
if sid:
|
|
150
|
+
print(f"{Fore.GREEN}✓ Session restored from {session_file}")
|
|
151
|
+
return sid
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f"{Fore.RED}✗ Error loading session: {e}")
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def login_and_get_session(
|
|
158
|
+
session_file: str = DEFAULT_SESSION_FILE,
|
|
159
|
+
force_login: bool = False,
|
|
160
|
+
username: Optional[str] = None,
|
|
161
|
+
password: Optional[str] = None,
|
|
162
|
+
sessionid_arg: Optional[str] = None,
|
|
163
|
+
) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Authenticate with Instagram (like instaspy-pip) using session.json,
|
|
166
|
+
instagrapi username/password login, or a direct sessionid cookie value.
|
|
167
|
+
Returns the active sessionid string.
|
|
168
|
+
"""
|
|
169
|
+
if sessionid_arg:
|
|
170
|
+
sid = sessionid_arg.strip()
|
|
171
|
+
save_session(None, session_file=session_file, raw_sessionid=sid)
|
|
172
|
+
return sid
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
from instagrapi import Client
|
|
176
|
+
client = Client()
|
|
177
|
+
client.delay_range = [1, 3]
|
|
178
|
+
except Exception:
|
|
179
|
+
client = None
|
|
180
|
+
|
|
181
|
+
if not force_login:
|
|
182
|
+
existing_sid = load_session(client, session_file=session_file)
|
|
183
|
+
if existing_sid:
|
|
184
|
+
return existing_sid
|
|
185
|
+
|
|
186
|
+
print(f"\n{Fore.CYAN}➤ Login required to access Instagram API")
|
|
187
|
+
print(f"{Fore.YELLOW} [1] Login with Instagram Username & Password (saves to {session_file})")
|
|
188
|
+
print(f"{Fore.YELLOW} [2] Login with Instagram sessionid cookie directly")
|
|
189
|
+
|
|
190
|
+
mode = "1"
|
|
191
|
+
if not username and not password:
|
|
192
|
+
choice = input(f"\n{Fore.GREEN}Select login method (1 or 2) [default: 1]: {Style.RESET_ALL}").strip()
|
|
193
|
+
if choice == "2":
|
|
194
|
+
mode = "2"
|
|
195
|
+
|
|
196
|
+
if mode == "2":
|
|
197
|
+
sid = getpass.getpass(f"{Fore.GREEN}Your Instagram sessionid value: {Style.RESET_ALL}").strip()
|
|
198
|
+
if not sid:
|
|
199
|
+
raise RuntimeError("No sessionid provided.")
|
|
200
|
+
if client is not None:
|
|
201
|
+
try:
|
|
202
|
+
client.login_by_sessionid(sid)
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
save_session(client, session_file=session_file, raw_sessionid=sid)
|
|
206
|
+
return sid
|
|
207
|
+
|
|
208
|
+
# Mode 1: Username & Password via instagrapi (like instaspy-pip)
|
|
209
|
+
if client is None:
|
|
210
|
+
raise RuntimeError("instagrapi is not installed. Run 'pip install instagrapi' or use sessionid login.")
|
|
211
|
+
|
|
212
|
+
if not username:
|
|
213
|
+
username = input(f"{Fore.GREEN}Enter your Instagram username: {Style.RESET_ALL}").strip()
|
|
214
|
+
if not password:
|
|
215
|
+
password = getpass.getpass(f"{Fore.GREEN}Enter your Instagram password: {Style.RESET_ALL}").strip()
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
print(f"{Fore.CYAN}[•] Attempting login as @{username}...")
|
|
219
|
+
try:
|
|
220
|
+
client.login(username, password)
|
|
221
|
+
except Exception as login_err:
|
|
222
|
+
err_msg = str(login_err).lower()
|
|
223
|
+
if "two-factor" in err_msg or "two_factor" in err_msg or "verification_code" in err_msg:
|
|
224
|
+
code = input(f"{Fore.YELLOW}Enter 2FA verification code: {Style.RESET_ALL}").strip()
|
|
225
|
+
client.login(username, password, verification_code=code)
|
|
226
|
+
else:
|
|
227
|
+
raise
|
|
228
|
+
print(f"{Fore.GREEN}✓ Login successful")
|
|
229
|
+
save_session(client, session_file=session_file)
|
|
230
|
+
sid = extract_sessionid(client, session_file=session_file)
|
|
231
|
+
if not sid:
|
|
232
|
+
raise RuntimeError("Logged in, but could not locate sessionid in client settings.")
|
|
233
|
+
return sid
|
|
234
|
+
except Exception as e:
|
|
235
|
+
print(f"{Fore.RED}✗ Username/password login failed: {e}")
|
|
236
|
+
fallback = input(
|
|
237
|
+
f"{Fore.YELLOW}Would you like to paste your 'sessionid' cookie instead? (y/n): {Style.RESET_ALL}"
|
|
238
|
+
).strip().lower()
|
|
239
|
+
if fallback == "y":
|
|
240
|
+
sid = getpass.getpass("Your Instagram sessionid value: ").strip()
|
|
241
|
+
save_session(None, session_file=session_file, raw_sessionid=sid)
|
|
242
|
+
return sid
|
|
243
|
+
raise RuntimeError(f"Login failed: {e}") from None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ==================== HIDE FOLLOWERS / FOLLOWING LIST ====================
|
|
247
|
+
def hide_lists(sessionid: str, enable: bool = True) -> dict:
|
|
248
|
+
"""
|
|
249
|
+
Enable (or disable) the "hide followers/following list" setting on Instagram
|
|
250
|
+
for the user associated with the provided sessionid cookie value.
|
|
251
|
+
"""
|
|
252
|
+
if not sessionid or any(ord(c) <= 32 or ord(c) >= 127 or c in ';,"\\' for c in sessionid):
|
|
253
|
+
raise ValueError("Enter only the sessionid cookie value, without 'sessionid='.")
|
|
254
|
+
match = re.fullmatch(r"([1-9][0-9]*)(?::|%3[aA]).+", sessionid)
|
|
255
|
+
if not match:
|
|
256
|
+
raise ValueError("Invalid sessionid format: expected a numeric user ID followed by ':' or '%3A'.")
|
|
257
|
+
user_id = match.group(1)
|
|
258
|
+
token = base64.b64encode(
|
|
259
|
+
json.dumps(
|
|
260
|
+
{
|
|
261
|
+
"ds_user_id": user_id,
|
|
262
|
+
"sessionid": sessionid,
|
|
263
|
+
"should_use_header_over_cookies": True,
|
|
264
|
+
},
|
|
265
|
+
separators=(",", ":"),
|
|
266
|
+
).encode()
|
|
267
|
+
).decode("ascii")
|
|
268
|
+
authorization = "Bearer IGT:2:" + token
|
|
269
|
+
name = "IGSettingsBooleanMutation"
|
|
270
|
+
form = {
|
|
271
|
+
"method": "post",
|
|
272
|
+
"format": "json",
|
|
273
|
+
"server_timestamps": "true",
|
|
274
|
+
"locale": "user",
|
|
275
|
+
"fb_api_req_friendly_name": name,
|
|
276
|
+
"client_doc_id": "2669261950526978627082629536",
|
|
277
|
+
"variables": json.dumps(
|
|
278
|
+
{
|
|
279
|
+
"setting_id": "hide_followers_following_list",
|
|
280
|
+
"allow_error_codes": True,
|
|
281
|
+
"callsite": "account_privacy",
|
|
282
|
+
"value": bool(enable),
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
}
|
|
286
|
+
request = urllib.request.Request(
|
|
287
|
+
"https://i.instagram.com/graphql/query",
|
|
288
|
+
data=urllib.parse.urlencode(form).encode(),
|
|
289
|
+
headers={
|
|
290
|
+
"Authorization": authorization,
|
|
291
|
+
"Cookie": f"sessionid={sessionid}; ds_user_id={user_id}",
|
|
292
|
+
"IG-INTENDED-USER-ID": user_id,
|
|
293
|
+
"X-IG-App-ID": "124024574287414",
|
|
294
|
+
"X-Auth-Type": "instagram",
|
|
295
|
+
"User-Agent": "Instagram 453.0.0",
|
|
296
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
297
|
+
"Accept-Encoding": "identity",
|
|
298
|
+
"X-GraphQL-Client-Library": "pando",
|
|
299
|
+
"X-FB-Friendly-Name": name,
|
|
300
|
+
},
|
|
301
|
+
method="POST",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
305
|
+
def redirect_request(self, *args, **kwargs):
|
|
306
|
+
raise RuntimeError("Redirect refused; credentials were not forwarded.")
|
|
307
|
+
|
|
308
|
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())
|
|
309
|
+
try:
|
|
310
|
+
with opener.open(request, timeout=15) as response:
|
|
311
|
+
raw = response.read(1024 * 1024 + 1)
|
|
312
|
+
if len(raw) > 1024 * 1024:
|
|
313
|
+
raise RuntimeError("Response too large.")
|
|
314
|
+
return json.loads(raw)
|
|
315
|
+
except urllib.error.HTTPError as error:
|
|
316
|
+
code = error.code
|
|
317
|
+
with error:
|
|
318
|
+
raw = error.read(16385)
|
|
319
|
+
details = {}
|
|
320
|
+
try:
|
|
321
|
+
body = json.loads(raw) if len(raw) <= 16384 else {}
|
|
322
|
+
if isinstance(body, dict):
|
|
323
|
+
for key in ("message", "error_type", "status", "code"):
|
|
324
|
+
if isinstance(body.get(key), (str, int, bool)):
|
|
325
|
+
details[key] = body[key]
|
|
326
|
+
except (ValueError, UnicodeError):
|
|
327
|
+
pass
|
|
328
|
+
text = json.dumps(details) if details else "No selected JSON error details."
|
|
329
|
+
secrets = {
|
|
330
|
+
authorization,
|
|
331
|
+
token,
|
|
332
|
+
sessionid,
|
|
333
|
+
urllib.parse.unquote(sessionid),
|
|
334
|
+
urllib.parse.quote(sessionid, safe=""),
|
|
335
|
+
}
|
|
336
|
+
for secret in sorted(secrets, key=len, reverse=True):
|
|
337
|
+
if secret:
|
|
338
|
+
text = text.replace(secret, "<REDACTED>")
|
|
339
|
+
raise RuntimeError(f"HTTP {code}: {text[:1500]}. No retry performed.") from None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def run_hide_lists(sessionid: str, enable: bool = True) -> int:
|
|
343
|
+
"""Execute hide_lists and validate the GraphQL response."""
|
|
344
|
+
try:
|
|
345
|
+
action_label = "Enabling" if enable else "Disabling"
|
|
346
|
+
print(f"{Fore.CYAN}[•] {action_label} 'hide_followers_following_list' setting on Instagram...")
|
|
347
|
+
body = hide_lists(sessionid, enable=enable)
|
|
348
|
+
data = body.get("data") if isinstance(body, dict) else None
|
|
349
|
+
root = "xdt_api__v1__settings__set_bool"
|
|
350
|
+
matches = (
|
|
351
|
+
[value for key, value in data.items() if key == root or key.startswith(root + "(")]
|
|
352
|
+
if isinstance(data, dict)
|
|
353
|
+
else []
|
|
354
|
+
)
|
|
355
|
+
result = matches[0] if len(matches) == 1 and isinstance(matches[0], dict) else {}
|
|
356
|
+
expected_val = bool(enable)
|
|
357
|
+
if (
|
|
358
|
+
not isinstance(body, dict)
|
|
359
|
+
or body.get("errors")
|
|
360
|
+
or body.get("error")
|
|
361
|
+
or body.get("status") == "fail"
|
|
362
|
+
or result.get("value") is not expected_val
|
|
363
|
+
or any(
|
|
364
|
+
result.get(key)
|
|
365
|
+
for key in (
|
|
366
|
+
"error_name_value",
|
|
367
|
+
"errorNameValue",
|
|
368
|
+
"fallback_message",
|
|
369
|
+
"fallbackMessage",
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
):
|
|
373
|
+
raise RuntimeError(
|
|
374
|
+
f"Instagram did not confirm setting value={expected_val}. No retry performed."
|
|
375
|
+
)
|
|
376
|
+
state_str = "Enabled" if enable else "Disabled"
|
|
377
|
+
print(f"{Fore.GREEN}✓ {state_str}: Instagram returned value={str(expected_val).lower()}.")
|
|
378
|
+
return 0
|
|
379
|
+
except RuntimeError as error:
|
|
380
|
+
print(f"{Fore.RED}✗ {error}", file=sys.stderr)
|
|
381
|
+
except (urllib.error.URLError, TimeoutError, OSError):
|
|
382
|
+
print(f"{Fore.RED}✗ Network/TLS failure. No retry performed.", file=sys.stderr)
|
|
383
|
+
except json.JSONDecodeError:
|
|
384
|
+
print(f"{Fore.RED}✗ Invalid JSON response. No retry performed.", file=sys.stderr)
|
|
385
|
+
except ValueError as error:
|
|
386
|
+
print(f"{Fore.RED}✗ {error}", file=sys.stderr)
|
|
387
|
+
return 1
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# ==================== GOOGLE DORK SEARCH (FIND COMMENTS ON POSTS) ====================
|
|
391
|
+
def _iter_google_search(query: str):
|
|
392
|
+
"""Support both googlesearch-python and google package signatures."""
|
|
393
|
+
from googlesearch import search
|
|
394
|
+
import inspect
|
|
395
|
+
|
|
396
|
+
sig = inspect.signature(search)
|
|
397
|
+
if "num_results" in sig.parameters:
|
|
398
|
+
# googlesearch-python (e.g., search(term, num_results=100, sleep_interval=5))
|
|
399
|
+
for url in search(query, num_results=100, sleep_interval=2):
|
|
400
|
+
yield url
|
|
401
|
+
else:
|
|
402
|
+
# classic google package (search(query, num=100, stop=None, pause=5))
|
|
403
|
+
for url in search(query, num=100, stop=None, pause=5):
|
|
404
|
+
yield url
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def collect_google_dork_data(username: str) -> int:
|
|
408
|
+
"""Find posts where the target user has commented/appeared using Google Dork queries."""
|
|
409
|
+
username = username.lstrip("@").strip()
|
|
410
|
+
if not username:
|
|
411
|
+
print(f"{Fore.RED}✗ Target username cannot be empty.", file=sys.stderr)
|
|
412
|
+
return 1
|
|
413
|
+
try:
|
|
414
|
+
print(f"\n{Fore.CYAN}➤ Starting Google dork search for comments/mentions of @{username}...")
|
|
415
|
+
query = f"site:instagram.com intext:{username}"
|
|
416
|
+
results = []
|
|
417
|
+
|
|
418
|
+
print(f"{Fore.YELLOW}⚡ Search query: {query}")
|
|
419
|
+
print(f"{Fore.YELLOW}⚠ This may take several minutes. Press Ctrl+C to stop early.\n")
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
for idx, url in enumerate(_iter_google_search(query), 1):
|
|
423
|
+
results.append(url)
|
|
424
|
+
print(f"{Fore.GREEN}[{idx}] {url}")
|
|
425
|
+
|
|
426
|
+
# Save progress every 10 results
|
|
427
|
+
if idx % 10 == 0:
|
|
428
|
+
with open(f"{username}_dork_results.txt", "w", encoding="utf-8") as f:
|
|
429
|
+
f.write("\n".join(results))
|
|
430
|
+
print(f"{Fore.YELLOW}⚡ Saved {idx} results so far...")
|
|
431
|
+
|
|
432
|
+
delay = random.uniform(1, 5)
|
|
433
|
+
time.sleep(delay)
|
|
434
|
+
|
|
435
|
+
except KeyboardInterrupt:
|
|
436
|
+
print(f"\n{Fore.YELLOW}⚠ Search interrupted by user")
|
|
437
|
+
except Exception as e:
|
|
438
|
+
print(f"{Fore.RED}✗ Search error: {e}")
|
|
439
|
+
|
|
440
|
+
results_file = f"{username}_dork_results.txt"
|
|
441
|
+
with open(results_file, "w", encoding="utf-8") as f:
|
|
442
|
+
f.write(f"Google Dork Results for @{username}\n")
|
|
443
|
+
f.write("=" * 70 + "\n")
|
|
444
|
+
f.write(f"Search Query: {query}\n")
|
|
445
|
+
f.write(f"Total Results: {len(results)}\n\n")
|
|
446
|
+
f.write("\n".join(results))
|
|
447
|
+
|
|
448
|
+
print(f"\n{Fore.MAGENTA}★ Search Complete ★")
|
|
449
|
+
print(f"{Fore.CYAN}→ Total results collected: {len(results)}")
|
|
450
|
+
print(f"{Fore.CYAN}→ Results saved to: {results_file}")
|
|
451
|
+
return 0
|
|
452
|
+
except Exception as e:
|
|
453
|
+
print(f"{Fore.RED}✗ Fatal error during search: {e}", file=sys.stderr)
|
|
454
|
+
return 1
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
# ==================== INTERACTIVE MENU & CLI ====================
|
|
458
|
+
def display_menu(session_file: str) -> str:
|
|
459
|
+
has_session = os.path.exists(session_file)
|
|
460
|
+
status = f"{Fore.GREEN}Active ({session_file})" if has_session else f"{Fore.RED}Not logged in"
|
|
461
|
+
print(f"\n{Fore.CYAN}┌{'─' * 62}┐")
|
|
462
|
+
print(f"│{Fore.MAGENTA} IGTOOLS MENU {Fore.CYAN}{' ' * 48}│")
|
|
463
|
+
print(f"├{'─' * 62}┤")
|
|
464
|
+
print(f"│ Session Status: {status}{' ' * max(1, 45 - len(session_file) - (9 if has_session else 13))}{Fore.CYAN}│")
|
|
465
|
+
print(f"├{'─' * 62}┤")
|
|
466
|
+
print(f"│ {Fore.GREEN}1. Hide Followers & Following List (Enable){Fore.CYAN} │")
|
|
467
|
+
print(f"│ {Fore.GREEN}2. Unhide Followers & Following List (Disable){Fore.CYAN} │")
|
|
468
|
+
print(f"│ {Fore.GREEN}3. Find Comments on Posts via Google Query (Dork Search){Fore.CYAN} │")
|
|
469
|
+
print(f"│ {Fore.GREEN}4. Login / Switch Instagram Session{Fore.CYAN} │")
|
|
470
|
+
print(f"│ {Fore.GREEN}5. Exit{Fore.CYAN} │")
|
|
471
|
+
print(f"└{'─' * 62}┘")
|
|
472
|
+
return input(f"{Fore.YELLOW}Select option (1-5): {Style.RESET_ALL}").strip()
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
476
|
+
parser = argparse.ArgumentParser(
|
|
477
|
+
prog="igtools",
|
|
478
|
+
description="IGTools: Hide Instagram followers/following lists & find user comments via Google Dork queries.",
|
|
479
|
+
)
|
|
480
|
+
parser.add_argument(
|
|
481
|
+
"--session-file",
|
|
482
|
+
default=DEFAULT_SESSION_FILE,
|
|
483
|
+
help=f"Path to session JSON file (default: {DEFAULT_SESSION_FILE})",
|
|
484
|
+
)
|
|
485
|
+
parser.add_argument(
|
|
486
|
+
"--sessionid",
|
|
487
|
+
help="Directly pass Instagram sessionid cookie value (saves to session.json)",
|
|
488
|
+
)
|
|
489
|
+
parser.add_argument(
|
|
490
|
+
"-u", "--username",
|
|
491
|
+
help="Instagram username for login",
|
|
492
|
+
)
|
|
493
|
+
parser.add_argument(
|
|
494
|
+
"-p", "--password",
|
|
495
|
+
help="Instagram password for login",
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
subparsers = parser.add_subparsers(dest="command", help="Optional subcommand (runs interactive menu if omitted)")
|
|
499
|
+
|
|
500
|
+
# login subcommand
|
|
501
|
+
subparsers.add_parser("login", help="Log in to Instagram and save session to session.json")
|
|
502
|
+
|
|
503
|
+
# hide-lists subcommand
|
|
504
|
+
hide_parser = subparsers.add_parser(
|
|
505
|
+
"hide-lists",
|
|
506
|
+
help="Enable or disable hiding your followers/following list on Instagram",
|
|
507
|
+
)
|
|
508
|
+
hide_parser.add_argument(
|
|
509
|
+
"--disable",
|
|
510
|
+
action="store_true",
|
|
511
|
+
help="Disable hiding (unhide followers/following list)",
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# dork / find-comments subcommand
|
|
515
|
+
dork_parser = subparsers.add_parser(
|
|
516
|
+
"dork",
|
|
517
|
+
aliases=["find-comments"],
|
|
518
|
+
help="Find posts where a target user commented using Google Dork search",
|
|
519
|
+
)
|
|
520
|
+
dork_parser.add_argument(
|
|
521
|
+
"target",
|
|
522
|
+
help="Target Instagram username to search for",
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
return parser
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def main(argv: Optional[list] = None) -> int:
|
|
529
|
+
parser = build_parser()
|
|
530
|
+
args = parser.parse_args(argv)
|
|
531
|
+
session_file = args.session_file
|
|
532
|
+
|
|
533
|
+
# Subcommand mode
|
|
534
|
+
if args.command == "login":
|
|
535
|
+
try:
|
|
536
|
+
login_and_get_session(
|
|
537
|
+
session_file=session_file,
|
|
538
|
+
force_login=True,
|
|
539
|
+
username=args.username,
|
|
540
|
+
password=args.password,
|
|
541
|
+
sessionid_arg=args.sessionid,
|
|
542
|
+
)
|
|
543
|
+
return 0
|
|
544
|
+
except Exception as e:
|
|
545
|
+
print(f"{Fore.RED}✗ {e}", file=sys.stderr)
|
|
546
|
+
return 1
|
|
547
|
+
|
|
548
|
+
if args.command == "hide-lists":
|
|
549
|
+
try:
|
|
550
|
+
sid = login_and_get_session(
|
|
551
|
+
session_file=session_file,
|
|
552
|
+
force_login=False,
|
|
553
|
+
username=args.username,
|
|
554
|
+
password=args.password,
|
|
555
|
+
sessionid_arg=args.sessionid,
|
|
556
|
+
)
|
|
557
|
+
return run_hide_lists(sid, enable=not args.disable)
|
|
558
|
+
except Exception as e:
|
|
559
|
+
print(f"{Fore.RED}✗ {e}", file=sys.stderr)
|
|
560
|
+
return 1
|
|
561
|
+
|
|
562
|
+
if args.command in ("dork", "find-comments"):
|
|
563
|
+
return collect_google_dork_data(args.target)
|
|
564
|
+
|
|
565
|
+
# Interactive mode (like instaspy-pip)
|
|
566
|
+
show_banner()
|
|
567
|
+
|
|
568
|
+
while True:
|
|
569
|
+
choice = display_menu(session_file)
|
|
570
|
+
|
|
571
|
+
if choice == "1":
|
|
572
|
+
try:
|
|
573
|
+
sid = login_and_get_session(session_file=session_file, sessionid_arg=args.sessionid)
|
|
574
|
+
run_hide_lists(sid, enable=True)
|
|
575
|
+
except Exception as e:
|
|
576
|
+
print(f"{Fore.RED}✗ {e}", file=sys.stderr)
|
|
577
|
+
elif choice == "2":
|
|
578
|
+
try:
|
|
579
|
+
sid = login_and_get_session(session_file=session_file, sessionid_arg=args.sessionid)
|
|
580
|
+
run_hide_lists(sid, enable=False)
|
|
581
|
+
except Exception as e:
|
|
582
|
+
print(f"{Fore.RED}✗ {e}", file=sys.stderr)
|
|
583
|
+
elif choice == "3":
|
|
584
|
+
target = input(f"\n{Fore.CYAN}➤ Enter target Instagram username: {Style.RESET_ALL}").strip()
|
|
585
|
+
if target:
|
|
586
|
+
collect_google_dork_data(target)
|
|
587
|
+
else:
|
|
588
|
+
print(f"{Fore.RED}✗ Username cannot be empty.")
|
|
589
|
+
elif choice == "4":
|
|
590
|
+
try:
|
|
591
|
+
login_and_get_session(session_file=session_file, force_login=True)
|
|
592
|
+
except Exception as e:
|
|
593
|
+
print(f"{Fore.RED}✗ {e}", file=sys.stderr)
|
|
594
|
+
elif choice == "5":
|
|
595
|
+
print(f"\n{Fore.GREEN}✓ Thank you for using IGTools. Goodbye!")
|
|
596
|
+
return 0
|
|
597
|
+
else:
|
|
598
|
+
print(f"{Fore.RED}✗ Invalid option. Please select 1-5.")
|
|
599
|
+
|
|
600
|
+
input(f"\n{Fore.YELLOW}Press Enter to return to menu...")
|
|
601
|
+
show_banner()
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
if __name__ == "__main__":
|
|
605
|
+
try:
|
|
606
|
+
raise SystemExit(main())
|
|
607
|
+
except KeyboardInterrupt:
|
|
608
|
+
raise SystemExit("\nInterrupted. If an update was in progress, check before retrying.")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: igtools-pip
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Instagram Privacy & OSINT Tool: Hide followers/following lists and find user comments via Google Dork queries.
|
|
5
|
+
Author-email: spyboy <spyboy.co@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/IM-SPYBOY/igtools
|
|
8
|
+
Project-URL: Telegram, https://t.me/spytube
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: instagrapi
|
|
16
|
+
Requires-Dist: googlesearch-python
|
|
17
|
+
Requires-Dist: colorama
|
|
18
|
+
Requires-Dist: termcolor
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# IGTools - Instagram Privacy & OSINT Tool 🔍🔒
|
|
22
|
+
|
|
23
|
+
**IGTools** (`igtools`) is a Python CLI tool and library built by **SPYBOY** that combines:
|
|
24
|
+
1. **Hide / Unhide Followers & Following List**: Enables or disables the `hide_followers_following_list` privacy setting on your Instagram account via Instagram's GraphQL Pando API.
|
|
25
|
+
2. **Find Comments on Posts via Google Query (Dork Search)**: Unlimited real-time Google Dork search (`site:instagram.com intext:<username>`) to discover posts where a target user has left comments or been mentioned.
|
|
26
|
+
3. **Session Persistence (`session.json`)**: Log in once with your Instagram username & password (using `instagrapi`, just like `instaspy-pip`) or with a `sessionid` cookie, and automatically reuse `session.json` across runs.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
Install directly from PyPI:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install igtools-pip
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or install from source:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/IM-SPYBOY/igtools.git
|
|
42
|
+
cd igtools
|
|
43
|
+
pip install .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### 1. Interactive Menu (like `instaspy-pip`)
|
|
51
|
+
|
|
52
|
+
Simply run:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
igtools
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
You will see the interactive menu:
|
|
59
|
+
1. **Hide Followers & Following List (Enable)**
|
|
60
|
+
2. **Unhide Followers & Following List (Disable)**
|
|
61
|
+
3. **Find Comments on Posts via Google Query (Dork Search)**
|
|
62
|
+
4. **Login / Switch Instagram Session**
|
|
63
|
+
5. **Exit**
|
|
64
|
+
|
|
65
|
+
### 2. Direct CLI Subcommands
|
|
66
|
+
|
|
67
|
+
- **Log in & save `session.json`:**
|
|
68
|
+
```bash
|
|
69
|
+
igtools login
|
|
70
|
+
```
|
|
71
|
+
- **Hide your followers & following list (uses saved `session.json` or prompts to log in):**
|
|
72
|
+
```bash
|
|
73
|
+
igtools hide-lists
|
|
74
|
+
```
|
|
75
|
+
- **Unhide your followers & following list:**
|
|
76
|
+
```bash
|
|
77
|
+
igtools hide-lists --disable
|
|
78
|
+
```
|
|
79
|
+
- **Find posts where a user commented via Google Dork query:**
|
|
80
|
+
```bash
|
|
81
|
+
igtools dork <target_username>
|
|
82
|
+
# or
|
|
83
|
+
igtools find-comments <target_username>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Configuration & Security
|
|
89
|
+
|
|
90
|
+
- Your Instagram session is saved locally in `session.json`. Keep this file secure and never commit it to public repositories.
|
|
91
|
+
- You can also specify a custom session file path via `igtools --session-file /path/to/session.json`.
|
|
92
|
+
|
|
93
|
+
## Author
|
|
94
|
+
- **SPYBOY**
|
|
95
|
+
- [Telegram Channel](https://t.me/spytube)
|
|
96
|
+
- [GitHub](https://github.com/IM-SPYBOY)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
igtools/__init__.py
|
|
6
|
+
igtools/cli.py
|
|
7
|
+
igtools_pip.egg-info/PKG-INFO
|
|
8
|
+
igtools_pip.egg-info/SOURCES.txt
|
|
9
|
+
igtools_pip.egg-info/dependency_links.txt
|
|
10
|
+
igtools_pip.egg-info/entry_points.txt
|
|
11
|
+
igtools_pip.egg-info/requires.txt
|
|
12
|
+
igtools_pip.egg-info/top_level.txt
|
|
13
|
+
tests/test_igtools.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
igtools
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "igtools-pip"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Instagram Privacy & OSINT Tool: Hide followers/following lists and find user comments via Google Dork queries."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "spyboy", email = "spyboy.co@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
requires-python = ">=3.7"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"requests",
|
|
21
|
+
"instagrapi",
|
|
22
|
+
"googlesearch-python",
|
|
23
|
+
"colorama",
|
|
24
|
+
"termcolor",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/IM-SPYBOY/igtools"
|
|
29
|
+
Telegram = "https://t.me/spytube"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
igtools = "igtools.cli:main"
|
|
33
|
+
igtools-pip = "igtools.cli:main"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest.mock import patch, MagicMock
|
|
7
|
+
|
|
8
|
+
from igtools.cli import (
|
|
9
|
+
extract_sessionid,
|
|
10
|
+
extract_sessionid_from_dict,
|
|
11
|
+
hide_lists,
|
|
12
|
+
load_session,
|
|
13
|
+
run_hide_lists,
|
|
14
|
+
save_session,
|
|
15
|
+
build_parser,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestIGTools(unittest.TestCase):
|
|
20
|
+
def test_extract_sessionid_from_dict_variants(self):
|
|
21
|
+
# 1. Direct sessionid
|
|
22
|
+
self.assertEqual(
|
|
23
|
+
extract_sessionid_from_dict({"sessionid": "12345:abcde"}),
|
|
24
|
+
"12345:abcde",
|
|
25
|
+
)
|
|
26
|
+
# 2. instagrapi authorization_data
|
|
27
|
+
self.assertEqual(
|
|
28
|
+
extract_sessionid_from_dict(
|
|
29
|
+
{"authorization_data": {"ds_user_id": "12345", "sessionid": "12345%3Aabcde"}}
|
|
30
|
+
),
|
|
31
|
+
"12345%3Aabcde",
|
|
32
|
+
)
|
|
33
|
+
# 3. cookies dict
|
|
34
|
+
self.assertEqual(
|
|
35
|
+
extract_sessionid_from_dict({"cookies": {"sessionid": "99999:xyz"}}),
|
|
36
|
+
"99999:xyz",
|
|
37
|
+
)
|
|
38
|
+
# 4. Bearer IGT:2:<base64>
|
|
39
|
+
token = base64.b64encode(json.dumps({"sessionid": "77777:tok"}).encode()).decode()
|
|
40
|
+
self.assertEqual(
|
|
41
|
+
extract_sessionid_from_dict({"Authorization": f"Bearer IGT:2:{token}"}),
|
|
42
|
+
"77777:tok",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def test_save_and_load_session(self):
|
|
46
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
47
|
+
session_path = os.path.join(tmpdir, "session.json")
|
|
48
|
+
saved = save_session(None, session_file=session_path, raw_sessionid="123456789:testsecret")
|
|
49
|
+
self.assertTrue(saved)
|
|
50
|
+
loaded = load_session(None, session_file=session_path)
|
|
51
|
+
self.assertEqual(loaded, "123456789:testsecret")
|
|
52
|
+
extracted = extract_sessionid(None, session_file=session_path)
|
|
53
|
+
self.assertEqual(extracted, "123456789:testsecret")
|
|
54
|
+
|
|
55
|
+
def test_hide_lists_validation(self):
|
|
56
|
+
with self.assertRaises(ValueError):
|
|
57
|
+
hide_lists("")
|
|
58
|
+
with self.assertRaises(ValueError):
|
|
59
|
+
hide_lists("sessionid=12345:abc")
|
|
60
|
+
with self.assertRaises(ValueError):
|
|
61
|
+
hide_lists("invalid_format_no_colon")
|
|
62
|
+
|
|
63
|
+
@patch("urllib.request.build_opener")
|
|
64
|
+
def test_run_hide_lists_success(self, mock_build_opener):
|
|
65
|
+
mock_response = MagicMock()
|
|
66
|
+
mock_response.__enter__.return_value = mock_response
|
|
67
|
+
mock_response.read.return_value = json.dumps(
|
|
68
|
+
{"data": {"xdt_api__v1__settings__set_bool": {"value": True}}}
|
|
69
|
+
).encode("utf-8")
|
|
70
|
+
mock_opener = MagicMock()
|
|
71
|
+
mock_opener.open.return_value = mock_response
|
|
72
|
+
mock_build_opener.return_value = mock_opener
|
|
73
|
+
|
|
74
|
+
rc = run_hide_lists("12345678:validtoken", enable=True)
|
|
75
|
+
self.assertEqual(rc, 0)
|
|
76
|
+
|
|
77
|
+
def test_cli_parser(self):
|
|
78
|
+
parser = build_parser()
|
|
79
|
+
args = parser.parse_args(["hide-lists", "--disable"])
|
|
80
|
+
self.assertEqual(args.command, "hide-lists")
|
|
81
|
+
self.assertTrue(args.disable)
|
|
82
|
+
|
|
83
|
+
args_dork = parser.parse_args(["dork", "target_user"])
|
|
84
|
+
self.assertEqual(args_dork.command, "dork")
|
|
85
|
+
self.assertEqual(args_dork.target, "target_user")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
unittest.main()
|