diff --git a/.gitignore b/.gitignore index 4a853fa..d9c12f9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ venv/ .vscode/ .idea/ *.swp +*.log +flask_session/ diff --git a/Script/SOCMINT-Twitter/Readme.md b/Script/SOCMINT-Twitter/Readme.md index aee9d56..526f72f 100644 --- a/Script/SOCMINT-Twitter/Readme.md +++ b/Script/SOCMINT-Twitter/Readme.md @@ -22,6 +22,18 @@ Xquik Dashboard image -Jieyab SOCMINT Twitter Dashboard +Dasboard Home -image \ No newline at end of file +image + +Archive + +image + +Graph + +image + +Dir Output + +image \ No newline at end of file diff --git a/Script/SOCMINT-Twitter/app.py b/Script/SOCMINT-Twitter/app.py index 79ee549..5590fe0 100644 --- a/Script/SOCMINT-Twitter/app.py +++ b/Script/SOCMINT-Twitter/app.py @@ -1,6 +1,11 @@ +import json +import secrets +import shutil import threading -from flask import Flask, jsonify, render_template, request +import requests as _req +from flask import Flask, g, jsonify, render_template, request, Response, stream_with_context, send_from_directory +import archive as _archive from xquik_client import XquikClient, XquikError, load_config from cookie_client import ( cookie_tweet_search, @@ -8,6 +13,9 @@ from cookie_client import ( cookie_post_extractor, cookie_article_extractor, cookie_community_post_extractor, + cookie_tweet_replies, + cookie_tweet_retweeters, + cookie_geo_search, CookieClientError, ) @@ -19,12 +27,102 @@ ACQUIRE_TIMEOUT = 15 # seconds to wait before returning 429 _sem = threading.Semaphore(MAX_CONCURRENT_REQUESTS) +# ── Cookie & session security ───────────────────────────────────────────────── + +_https = config.getboolean("server", "https", fallback=False) + +app.config.update( + SECRET_KEY = config.get("server", "secret_key", fallback=secrets.token_hex(32)), + SESSION_COOKIE_HTTPONLY = True, + SESSION_COOKIE_SAMESITE = "Strict", + SESSION_COOKIE_SECURE = _https, # True only when TLS is terminated at Flask +) + +# ── Security headers ────────────────────────────────────────────────────────── + +@app.before_request +def _make_nonce(): + g.csp_nonce = secrets.token_urlsafe(16) + + +@app.after_request +def _set_security_headers(response): + nonce = getattr(g, "csp_nonce", "") + csp = ( + "default-src 'none'; " + f"script-src 'nonce-{nonce}' https://unpkg.com; " + "style-src 'unsafe-inline' https://unpkg.com; " + "img-src 'self' data: blob: " + "https://*.twimg.com " + "https://*.tile.openstreetmap.org " + "https://server.arcgisonline.com " + "https://*.tile.opentopomap.org " + "https://unpkg.com; " + "media-src 'self'; " + "connect-src 'self' https://nominatim.openstreetmap.org; " + "font-src 'none'; " + "frame-src 'none'; " + "object-src 'none'; " + "base-uri 'self'; " + "form-action 'self';" + ) + response.headers["Content-Security-Policy"] = csp + response.headers["X-Content-Type-Options"] = "nosniff" + response.headers["X-Frame-Options"] = "DENY" + response.headers["Referrer-Policy"] = "no-referrer" + response.headers["Permissions-Policy"] = "geolocation=(), camera=(), microphone=()" + response.headers["X-XSS-Protection"] = "0" # disable legacy broken auditor + + # Harden every Set-Cookie header regardless of where it originates + raw_cookies = response.headers.getlist("Set-Cookie") + if raw_cookies: + response.headers.remove("Set-Cookie") + for raw in raw_cookies: + parts = [p.strip() for p in raw.split(";")] + flags = {p.split("=")[0].strip().lower() for p in parts[1:]} + if "httponly" not in flags: + parts.append("HttpOnly") + if "samesite" not in flags: + parts.append("SameSite=Strict") + if "secure" not in flags and _https: + parts.append("Secure") + response.headers.add("Set-Cookie", "; ".join(parts)) + + return response + @app.route("/") def index(): return render_template("index.html") +# Whitelist: only proxy Twitter's video CDN to prevent SSRF +_VIDEO_CDN = ("https://video.twimg.com/",) + +@app.route("/api/video") +def video_proxy(): + url = request.args.get("url", "").strip() + if not any(url.startswith(prefix) for prefix in _VIDEO_CDN): + return jsonify({"ok": False, "error": "URL not allowed"}), 403 + try: + upstream = _req.get( + url, + stream=True, + timeout=20, + headers={"Referer": "https://x.com/", "User-Agent": "Mozilla/5.0"}, + ) + headers = {"Content-Type": upstream.headers.get("Content-Type", "video/mp4")} + if "Content-Length" in upstream.headers: + headers["Content-Length"] = upstream.headers["Content-Length"] + return Response( + stream_with_context(upstream.iter_content(chunk_size=32768)), + status=upstream.status_code, + headers=headers, + ) + except _req.RequestException as e: + return jsonify({"ok": False, "error": str(e)}), 502 + + @app.route("/api/run", methods=["POST"]) def run_tool(): body = request.get_json(silent=True) or {} @@ -74,6 +172,24 @@ def run_tool(): else: data = XquikClient(config).post_extractor(username) + elif tool_type == "tweet_replies_extractor": + tweet_id = body.get("targetTweetId", "") + if mode != "cookie": + return jsonify({"ok": False, "error": "tweet_replies_extractor requires cookie mode"}), 400 + data = cookie_tweet_replies(tweet_id, count=count, config=config) + + elif tool_type == "tweet_retweeters_extractor": + tweet_id = body.get("targetTweetId", "") + if mode != "cookie": + return jsonify({"ok": False, "error": "tweet_retweeters_extractor requires cookie mode"}), 400 + data = cookie_tweet_retweeters(tweet_id, count=count, config=config) + + elif tool_type == "geo_post_extractor": + keyword = body.get("searchQuery", "") + if mode != "cookie": + return jsonify({"ok": False, "error": "geo_post_extractor requires cookie mode"}), 400 + data = cookie_geo_search(keyword, count=count, config=config) + else: return jsonify({"ok": False, "error": f"Unknown toolType: {tool_type}"}), 400 @@ -87,6 +203,72 @@ def run_tool(): _sem.release() +# ── Archive routes ──────────────────────────────────────────────────────────── + +@app.route("/graph") +def graph_viewer(): + return render_template("graph.html") + + +@app.route("/archives") +def archive_viewer(): + return render_template("archive.html") + + +@app.route("/api/archive//results") +def archive_results(archive_id): + base = _archive.ARCHIVE_ROOT / archive_id + results_file = base / "results.json" + meta_file = base / "meta.json" + if not results_file.exists(): + return jsonify({"ok": False, "error": "Archive not found"}), 404 + return jsonify({ + "ok": True, + "results": json.loads(results_file.read_text()), + "meta": json.loads(meta_file.read_text()) if meta_file.exists() else {}, + }) + + +@app.route("/api/archive//media/") +def archive_media(archive_id, filename): + media_dir = _archive.ARCHIVE_ROOT / archive_id / "media" + return send_from_directory(str(media_dir), filename) + + +@app.route("/api/archive/", methods=["DELETE"]) +def archive_delete(archive_id): + archive_dir = _archive.ARCHIVE_ROOT / archive_id + if not archive_dir.exists(): + return jsonify({"ok": False, "error": "Archive not found"}), 404 + shutil.rmtree(archive_dir) + return jsonify({"ok": True}) + + +@app.route("/api/archive", methods=["POST"]) +def archive_start(): + body = request.get_json(silent=True) or {} + tool_type = body.get("toolType", "unknown") + data = body.get("data") + query_info = body.get("queryInfo", {}) + if not data: + return jsonify({"ok": False, "error": "No data provided"}), 400 + archive_id = _archive.start(tool_type, data, query_info) + return jsonify({"ok": True, "archiveId": archive_id}) + + +@app.route("/api/archive//status") +def archive_status(archive_id): + s = _archive.status(archive_id) + if s is None: + return jsonify({"ok": False, "error": "Archive not found"}), 404 + return jsonify({"ok": True, **s}) + + +@app.route("/api/archive/list") +def archive_list(): + return jsonify({"ok": True, "archives": _archive.list_all()}) + + if __name__ == "__main__": host = config.get("server", "host", fallback="127.0.0.1") port = config.getint("server", "port", fallback=5000) diff --git a/Script/SOCMINT-Twitter/archive.py b/Script/SOCMINT-Twitter/archive.py new file mode 100644 index 0000000..ca6c677 --- /dev/null +++ b/Script/SOCMINT-Twitter/archive.py @@ -0,0 +1,180 @@ +import json +import os +import random +import threading +import time +from datetime import datetime +from pathlib import Path + +import requests + +ARCHIVE_ROOT = Path(__file__).parent / "archives" +DELAY_MIN = 1.5 # seconds between media downloads (anti-ban) +DELAY_MAX = 3.5 +REQUEST_TIMEOUT = 25 + +_registry: dict[str, dict] = {} # archive_id → status dict +_lock = threading.Lock() + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def _make_id(tool_type: str) -> str: + return f"{tool_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + + +def _tweet_url(item: dict) -> str | None: + tid = str(item.get("id", "")).strip() + user = str(item.get("user", "")).strip() + if tid and user: + return f"https://x.com/{user}/status/{tid}" + return None + + +def _media_ext(url: str, mtype: str) -> str: + if mtype in ("video", "animated_gif"): + return "mp4" + for ext in (".jpg", ".jpeg", ".png", ".webp", ".gif"): + if ext in url.lower(): + return ext.lstrip(".") + return "jpg" + + +def _download_file(url: str, dest: Path) -> bool: + """Download a single file. Returns True on success.""" + try: + r = requests.get( + url, + timeout=REQUEST_TIMEOUT, + headers={"Referer": "https://x.com/", "User-Agent": "Mozilla/5.0"}, + stream=True, + ) + r.raise_for_status() + dest.parent.mkdir(parents=True, exist_ok=True) + with open(dest, "wb") as f: + for chunk in r.iter_content(chunk_size=65536): + f.write(chunk) + return True + except Exception: + return False + + +def _pick_fields(item: dict) -> dict: + """Keep only the fields we want to archive.""" + keys = ["id", "text", "full_text", "article_text", "user", "user_id", + "created_at", "reply_count", "retweet_count", "favorite_count", + "view_count", "in_reply_to_tweet_id", "name", "screen_name", + "description", "followers_count", "following_count", + "lat", "lon", "place", + "retweeted_text", "retweeted_by_user", "retweeted_by_name", + "retweeted_by_bio", "retweeted_at", "retweeted_tweet_id"] + return {k: item[k] for k in keys if k in item and item[k] is not None} + + +# ── Core archive runner (runs in background thread) ─────────────────────────── + +def _run(archive_id: str, tool_type: str, data, query_info: dict) -> None: + archive_dir = ARCHIVE_ROOT / archive_id + media_dir = archive_dir / "media" + archive_dir.mkdir(parents=True, exist_ok=True) + media_dir.mkdir(exist_ok=True) + + items = data if isinstance(data, list) else [data] + + # Build enriched records + collect media download queue + enriched = [] + media_queue = [] # list of (url, dest_path) + + for item in items: + if not isinstance(item, dict): + enriched.append(item) + continue + + record = _pick_fields(item) + record["tweet_url"] = _tweet_url(item) + local_media = [] + + for midx, m in enumerate(item.get("media", [])): + url = m.get("url") or m.get("thumb", "") + if not url: + continue + mtype = m.get("type", "photo") + ext = _media_ext(url, mtype) + fname = f"{item.get('id', 'unknown')}_{mtype}_{midx}.{ext}" + dest = media_dir / fname + local_media.append(f"media/{fname}") + media_queue.append((url, dest)) + + if local_media: + record["archived_media"] = local_media + enriched.append(record) + + # Write metadata + results immediately (no waiting on media) + meta = { + "tool": tool_type, + "query": query_info, + "archived_at": datetime.now().isoformat(timespec="seconds"), + "total_items": len(items), + "media_count": len(media_queue), + } + (archive_dir / "meta.json").write_text( + json.dumps(meta, indent=2, ensure_ascii=False) + ) + (archive_dir / "results.json").write_text( + json.dumps(enriched, indent=2, ensure_ascii=False) + ) + + with _lock: + _registry[archive_id].update({"status": "downloading", "total": len(media_queue)}) + + # Download media one-by-one with anti-ban delays + for i, (url, dest) in enumerate(media_queue): + if i > 0: + time.sleep(random.uniform(DELAY_MIN, DELAY_MAX)) + _download_file(url, dest) + with _lock: + _registry[archive_id]["progress"] = i + 1 + + with _lock: + _registry[archive_id].update({"status": "done", "path": str(archive_dir)}) + + +# ── Public API ──────────────────────────────────────────────────────────────── + +def start(tool_type: str, data, query_info: dict) -> str: + """Kick off archiving in a background thread. Returns archive_id.""" + archive_id = _make_id(tool_type) + with _lock: + _registry[archive_id] = {"status": "saving", "progress": 0, "total": 0, "path": None} + + t = threading.Thread( + target=_run, + args=(archive_id, tool_type, data, query_info), + daemon=True, + ) + t.start() + return archive_id + + +def status(archive_id: str) -> dict | None: + with _lock: + entry = _registry.get(archive_id) + return dict(entry) if entry else None + + +def list_all() -> list[dict]: + """Read meta.json from every archive folder, newest first.""" + if not ARCHIVE_ROOT.exists(): + return [] + results = [] + for d in sorted(ARCHIVE_ROOT.iterdir(), key=lambda p: p.name, reverse=True): + meta_file = d / "meta.json" + if not meta_file.exists(): + continue + try: + meta = json.loads(meta_file.read_text()) + meta["id"] = d.name + results.append(meta) + except Exception: + pass + return results diff --git a/Script/SOCMINT-Twitter/config.ini.example b/Script/SOCMINT-Twitter/config.ini.example index 42245d8..ab29039 100644 --- a/Script/SOCMINT-Twitter/config.ini.example +++ b/Script/SOCMINT-Twitter/config.ini.example @@ -1,10 +1,8 @@ [xquik] - api_key = xxxxxxxxxxxxxxxxxxxxxxxxxxx base_url = https://xquik.com/api/v1/extractions [twitter_cookies] - auth_token = xxxxxxxxxxxxxxxxxx ct0 = xxxxxxxxxxxx diff --git a/Script/SOCMINT-Twitter/cookie_client.py b/Script/SOCMINT-Twitter/cookie_client.py index f154c99..eec0c23 100644 --- a/Script/SOCMINT-Twitter/cookie_client.py +++ b/Script/SOCMINT-Twitter/cookie_client.py @@ -1,19 +1,3 @@ -""" -cookie_client.py -Cookie-based wrapper for all five xquik tools, hitting X.com directly via -twikit/twifork — no xquik API quota consumed. - -Install: pip install twifork - (NOT the upstream `twikit` package — it is broken since X changed ondemand.s.js. - twifork is a drop-in replacement; imports remain `from twikit import Client`.) - -How to obtain cookies: - 1. Log in to x.com in your browser. - 2. Open DevTools > Application > Cookies > https://x.com - 3. Copy the values of `auth_token` and `ct0`. - 4. Paste them into config.ini under [twitter_cookies]. -""" - import asyncio import configparser import os @@ -82,16 +66,25 @@ def _extract_media(t: object) -> list: return result +def _id_str(val) -> str | None: + """Return ID as string, or None. Prevents 64-bit integer precision loss in JSON/JS.""" + return str(val) if val is not None else None + + def _tweet_to_dict(t: object) -> dict: + user_obj = getattr(t, "user", None) d = { - "id": getattr(t, "id", None), + "id": _id_str(getattr(t, "id", None)), "created_at": getattr(t, "created_at", None), "text": getattr(t, "text", None), - "user": getattr(t.user, "screen_name", None) if getattr(t, "user", None) else None, - "reply_count": getattr(t, "reply_count", None), - "retweet_count": getattr(t, "retweet_count", None), - "favorite_count": getattr(t, "favorite_count", None), - "view_count": getattr(t, "view_count", None), + "user": getattr(user_obj, "screen_name", None) if user_obj else None, + "user_id": _id_str(getattr(user_obj, "id", None)) if user_obj else None, + "user_location": getattr(user_obj, "location", None) if user_obj else None, + "reply_count": getattr(t, "reply_count", None), + "retweet_count": getattr(t, "retweet_count", None), + "favorite_count": getattr(t, "favorite_count", None), + "view_count": getattr(t, "view_count", None), + "in_reply_to_tweet_id": getattr(t, "in_reply_to", None), # returns id_str directly } media = _extract_media(t) if media: @@ -101,7 +94,7 @@ def _tweet_to_dict(t: object) -> dict: def _user_to_dict(u: object) -> dict: return { - "id": getattr(u, "id", None), + "id": _id_str(getattr(u, "id", None)), "name": getattr(u, "name", None), "screen_name": getattr(u, "screen_name", None), "description": getattr(u, "description", None), @@ -116,6 +109,14 @@ def _user_to_dict(u: object) -> dict: # ── Async implementations ───────────────────────────────────────────────────── +async def _resolve_user(client, identifier: str): + """Accept either a screen_name or a numeric user ID string.""" + clean = identifier.lstrip("@").strip() + if clean.isdigit(): + return await client.get_user_by_id(clean) + return await client.get_user_by_screen_name(clean) + + async def _tweet_search_async(query: str, auth_token: str, ct0: str, count: int) -> list: client = await _make_client(auth_token, ct0) results = await client.search_tweet(query, "Latest", count=count) @@ -124,14 +125,14 @@ async def _tweet_search_async(query: str, auth_token: str, ct0: str, count: int) async def _follower_explorer_async(username: str, auth_token: str, ct0: str, count: int) -> list: client = await _make_client(auth_token, ct0) - user = await client.get_user_by_screen_name(username) + user = await _resolve_user(client, username) followers = await user.get_followers(count=count) return [_user_to_dict(u) for u in followers] async def _post_extractor_async(username: str, auth_token: str, ct0: str, count: int) -> list: client = await _make_client(auth_token, ct0) - user = await client.get_user_by_screen_name(username) + user = await _resolve_user(client, username) tweets = await user.get_tweets("Tweets", count=count) return [_tweet_to_dict(t) for t in tweets] @@ -155,6 +156,80 @@ async def _community_posts_async(community_id: str, auth_token: str, ct0: str, c return [_tweet_to_dict(t) for t in posts] +async def _tweet_replies_async(tweet_id: str, auth_token: str, ct0: str, count: int) -> list: + client = await _make_client(auth_token, ct0) + + # Walk up the in_reply_to chain to find the conversation root. + # tweet.in_reply_to → _legacy['in_reply_to_status_id_str'] (parent tweet ID string). + # There is no conversation_id attribute on twikit Tweet objects — the only way + # to reach the root is to follow the chain until in_reply_to is None. + conversation_id = tweet_id + current_id = tweet_id + + for _ in range(6): # guard: max 6 hops up the thread + try: + node = await client.get_tweet_by_id(current_id) + parent_id = getattr(node, "in_reply_to", None) + if not parent_id: + conversation_id = current_id # reached root + break + current_id = str(parent_id) + conversation_id = current_id + except Exception: + break + + # Fetch all tweets in the conversation thread + results = await client.search_tweet( + f"conversation_id:{conversation_id}", "Latest", count=count + ) + + tweets = [_tweet_to_dict(t) for t in results] + + # If the user clicked on a non-root reply, filter to that reply's direct children + if conversation_id != tweet_id: + direct = [d for d in tweets if d.get("in_reply_to_tweet_id") == tweet_id] + return direct if direct else tweets # fallback: full thread + + return tweets + + +async def _tweet_retweeters_async(tweet_id: str, auth_token: str, ct0: str, count: int) -> list: + client = await _make_client(auth_token, ct0) + + # Fetch the original tweet once so every retweeter card shows what was retweeted + rt_info: dict = {} + try: + orig = await client.get_tweet_by_id(tweet_id) + orig_user = getattr(orig, "user", None) + rt_info = { + "retweeted_text": getattr(orig, "text", None), + "retweeted_by_user": getattr(orig_user, "screen_name", None) if orig_user else None, + "retweeted_by_name": getattr(orig_user, "name", None) if orig_user else None, + "retweeted_by_bio": getattr(orig_user, "description", None) if orig_user else None, + "retweeted_at": getattr(orig, "created_at", None), + "retweeted_tweet_id": _id_str(getattr(orig, "id", None)), + } + except Exception: + pass + + retweeters = await client.get_retweeters(tweet_id, count=count) + + result = [] + for u in retweeters: + # retweeted content first → shows prominently in the card + d = {**rt_info, **_user_to_dict(u)} + result.append(d) + return result + + +async def _geo_search_async(keyword: str, auth_token: str, ct0: str, count: int) -> list: + """Keyword search; user_location (profile location string) is included in every + result so the frontend can geocode and plot it on a map.""" + client = await _make_client(auth_token, ct0) + results = await client.search_tweet(keyword, "Latest", count=count) + return [_tweet_to_dict(t) for t in results] + + # ── Public sync wrappers ────────────────────────────────────────────────────── def cookie_tweet_search(query: str, count: int = 20, config: configparser.ConfigParser = None) -> list: @@ -189,6 +264,26 @@ def cookie_community_post_extractor( return asyncio.run(_community_posts_async(community_id, auth, ct0, count)) +def cookie_tweet_replies(tweet_id: str, count: int = 50, config: configparser.ConfigParser = None) -> list: + cfg = config or load_config() + auth, ct0 = _get_creds(cfg) + return asyncio.run(_tweet_replies_async(tweet_id, auth, ct0, count)) + + +def cookie_tweet_retweeters(tweet_id: str, count: int = 50, config: configparser.ConfigParser = None) -> list: + cfg = config or load_config() + auth, ct0 = _get_creds(cfg) + return asyncio.run(_tweet_retweeters_async(tweet_id, auth, ct0, count)) + + +def cookie_geo_search( + keyword: str, count: int = 20, config: configparser.ConfigParser = None, +) -> list: + cfg = config or load_config() + auth, ct0 = _get_creds(cfg) + return asyncio.run(_geo_search_async(keyword, auth, ct0, count)) + + # Legacy alias — kept for any external scripts that import this name directly fetch_user_timeline = cookie_post_extractor diff --git a/Script/SOCMINT-Twitter/templates/archive.html b/Script/SOCMINT-Twitter/templates/archive.html new file mode 100644 index 0000000..2339eae --- /dev/null +++ b/Script/SOCMINT-Twitter/templates/archive.html @@ -0,0 +1,550 @@ + + + + + + + +Jieyab89 SOCMINT X — Archives + + + + +
+

Jieyab89 SOCMINT X

+ | + Saved Archives + + +
+ +
+ + + + + +
+ + +
+
+
Select an archive from the sidebar to view its contents.
+
+
+
+ +
+ + + + + diff --git a/Script/SOCMINT-Twitter/templates/graph.html b/Script/SOCMINT-Twitter/templates/graph.html new file mode 100644 index 0000000..8003478 --- /dev/null +++ b/Script/SOCMINT-Twitter/templates/graph.html @@ -0,0 +1,947 @@ + + + + + + +Jieyab89 SOCMINT X — Graph + + + + +
+

Jieyab89 SOCMINT X

+ | + Graph Visualization + + +
+ +
+ + + + + 0 nodes + + + + +
+ +
+
+ + + + + + + + +
+ Use the toolbar to search
+ Click any node to inspect · Click tweet nodes to expand replies / retweets +
+
+ + +
+
Search root
+
Tweet
+
Reply
+
User / Retweeter
+
+ + + + + diff --git a/Script/SOCMINT-Twitter/xquik_client.py b/Script/SOCMINT-Twitter/xquik_client.py index de90df9..e9abff3 100644 --- a/Script/SOCMINT-Twitter/xquik_client.py +++ b/Script/SOCMINT-Twitter/xquik_client.py @@ -46,8 +46,6 @@ class XquikClient: except ValueError as e: raise XquikError(f"Response bukan JSON valid: {resp.text[:300]}") from e - # --- 5 tool type sesuai docs --- - def tweet_search(self, search_query: str) -> dict: return self._post({"toolType": "tweet_search_extractor", "searchQuery": search_query}) diff --git a/Script/Web-TLD-Enumerate-NS-Check/Domain_tld_enum.py b/Script/Web-TLD-Enumerate-NS-Check/Domain_tld_enum.py index 97c0695..e6cc604 100644 --- a/Script/Web-TLD-Enumerate-NS-Check/Domain_tld_enum.py +++ b/Script/Web-TLD-Enumerate-NS-Check/Domain_tld_enum.py @@ -53,6 +53,7 @@ def green(text): # Wordlist (TLD + ccTLD + gTLD + vanity) COMMON_TLDS = [ + # Arr extentions tld domain lists # All region goverment "gov","mil","go.id","mil.id","gov.au","gov.br", @@ -327,8 +328,7 @@ COMMON_TLDS = [ "xn--t60b56a","xn--tckwe","xn--tiq49xqyj","xn--unup4y","xn--vermgensberater-ctb","xn--vermgensberatung-pwb","xn--vhquv","xn--vuq861b", "xn--w4r85el8fhu5dnra","xn--w4rs40l","xn--wgbh1c","xn--wgbl6a","xn--xhq521b","xn--xkc2al3hye2a","xn--xkc2dl3a5ee0h","xn--y9a3aq","xn--yfro4i67o", "xn--ygbi2ammx","xn--zfr164b","xxx","xyz","yachts","yahoo","yamaxun","yandex","ye","yodobashi","yoga","yokohama","you","youtube","yun","za", - "zappos","zara","zero","zm","zone","zuerich","zw", - + "zappos","zara","zero","zm","zone","zuerich","zw", ] def generate_brute_tlds(max_len=3, min_len=1):