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.
@@ -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,3 @@
1
+ [console_scripts]
2
+ igtools = igtools.cli:main
3
+ igtools-pip = igtools.cli:main
@@ -0,0 +1,5 @@
1
+ requests
2
+ instagrapi
3
+ googlesearch-python
4
+ colorama
5
+ termcolor
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+ setup()
4
+
@@ -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()