hedau

Untitled

Jan 31st, 2025 (edited)
387
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.08 KB | None | 0 0
  1. import os
  2. import sqlite3
  3. import requests
  4. import re
  5. import concurrent.futures # Import for concurrency
  6. import multiprocessing
  7. import time # For rate limiting
  8. import logging # For logging
  9. from bs4 import BeautifulSoup
  10. from urllib.parse import urlparse, urljoin
  11. import asyncio # Import asyncio if you intend to use key checker functions later
  12.  
  13. # Import key checking functions from the other file
  14. from deepseek_key_checker import APIKey, check_keys, pretty_print_deepseek_keys, fetch_keys_from_db, update_key_status_in_db
  15.  
  16. # Configure logging
  17. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
  18.  
  19. DATABASE_NAME = "findings_db.sqlite3" # Changed database name to 'findings_db' to be more general
  20. COOKIES_DATABASE_NAME = "cookies.db"
  21.  
  22. # WARNING: Consider using environment variables or secure configuration for API token in production
  23. GITHUB_API_TOKEN = os.environ.get("GITHUB_API_TOKEN") # Use environment variable - recommended
  24. # GITHUB_API_TOKEN = "YOUR_GITHUB_API_TOKEN_HERE" # If you must hardcode for testing (INSECURE!)
  25. GITHUB_API_HEADERS = {"User-Agent": "Mozilla/5.0"}
  26. if GITHUB_API_TOKEN:
  27. GITHUB_API_HEADERS["Authorization"] = f"token {GITHUB_API_TOKEN}"
  28.  
  29. RATE_LIMIT_DELAY = 1 # Reduced initial delay, can adjust dynamically if needed - important for concurrency to be effective
  30. SEARCH_RATE_LIMIT_DELAY = 2 # Separate rate limit for search to be less aggressive
  31.  
  32.  
  33. def init_db():
  34. """Initializes the SQLite database for found findings (keys, strings, etc.)."""
  35. conn = sqlite3.connect(DATABASE_NAME)
  36. c = conn.cursor()
  37. c.execute("""
  38. CREATE TABLE IF NOT EXISTS findings (
  39. id INTEGER PRIMARY KEY AUTOINCREMENT,
  40. url TEXT UNIQUE,
  41. finding_type TEXT, -- e.g., 'api_key', 'go_ty_string'
  42. finding_value TEXT,
  43. context_url TEXT,
  44. checked INTEGER DEFAULT 0, -- Added for checker
  45. balance TEXT, -- Added for checker
  46. available INTEGER DEFAULT 0, -- Added for checker
  47. rate_limited INTEGER DEFAULT 0, -- Added for checker
  48. timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
  49. )
  50. """)
  51. conn.commit()
  52. conn.close()
  53. logging.info(f"Database '{DATABASE_NAME}' initialized.")
  54.  
  55.  
  56. def init_cookies_db():
  57. """Initializes the SQLite database for cookies (optional)."""
  58. conn = sqlite3.connect(COOKIES_DATABASE_NAME)
  59. c = conn.cursor()
  60. c.execute("""
  61. CREATE TABLE IF NOT EXISTS cookies (
  62. id INTEGER PRIMARY KEY AUTOINCREMENT,
  63. name TEXT UNIQUE,
  64. value TEXT
  65. )
  66. """)
  67. conn.commit()
  68. conn.close()
  69. logging.info(f"Cookies database '{COOKIES_DATABASE_NAME}' initialized.")
  70.  
  71.  
  72. def load_netscape_cookies(cookie_file):
  73. """Loads cookies from a Netscape format cookie file."""
  74. cookies = {}
  75. try:
  76. with open(cookie_file, "r") as f:
  77. for line in f:
  78. if line.startswith("#") or not line.strip():
  79. continue
  80. parts = line.strip().split("\t")
  81. if len(parts) >= 7:
  82. cookies[parts[5]] = parts[6]
  83. save_cookie(parts[5], parts[6])
  84. logging.info(f"Cookies loaded from '{cookie_file}'.")
  85. except FileNotFoundError:
  86. logging.warning(f"Cookie file '{cookie_file}' not found. Proceeding without cookies.")
  87. return cookies
  88.  
  89.  
  90. def save_cookie(name, value):
  91. """Saves a cookie to the cookies database (optional)."""
  92. conn = sqlite3.connect(COOKIES_DATABASE_NAME)
  93. c = conn.cursor()
  94. try:
  95. c.execute("INSERT OR IGNORE INTO cookies (name, value) VALUES (?, ?)", (name, value))
  96. conn.commit()
  97. logging.debug(f"Cookie '{name}' saved to cookies database.")
  98. except sqlite3.Error as e:
  99. logging.error(f"Database error saving cookie: {e}")
  100. finally:
  101. conn.close()
  102.  
  103.  
  104. def search_github_repos(query, cookies, max_pages=10):
  105. """Searches GitHub code and extracts repository URLs."""
  106. base_url = "https://github.com/search?q={}&type=code&p={}"
  107. headers = {"User-Agent": "Mozilla/5.0"}
  108. repo_urls = set()
  109.  
  110. for page in range(1, max_pages + 1):
  111. url = base_url.format(query, page)
  112. logging.info(f"Searching page {page} for repos: {url}")
  113. try:
  114. response = requests.get(url, headers=headers, cookies=cookies, timeout=10)
  115. response.raise_for_status()
  116. except requests.exceptions.RequestException as e:
  117. logging.error(f"Request error on page {page}: {e}")
  118. break
  119.  
  120. soup = BeautifulSoup(response.text, "html.parser")
  121. found_on_page = 0
  122.  
  123. for link in soup.find_all("a", href=True):
  124. href = link["href"]
  125. if "/blob/" in href:
  126. repo_url_part = href.split("/blob/")[0]
  127. repo_url = f"https://github.com{repo_url_part}" # Corrected to get the repo URL not blob URL
  128. if repo_url not in repo_urls:
  129. repo_urls.add(repo_url)
  130. found_on_page += 1
  131.  
  132. logging.info(f"Page {page}: Found {found_on_page} new repo URLs, Total unique repos: {len(repo_urls)}")
  133. if found_on_page == 0:
  134. logging.info(f"No new repo URLs on page {page}. Stopping search.")
  135. break
  136. time.sleep(SEARCH_RATE_LIMIT_DELAY) # Use separate rate limit for search
  137.  
  138. return list(repo_urls)
  139.  
  140.  
  141. def crawl_repository_for_findings(repo_url, key_pattern=r"sk-[A-Za-z0-9]{32}", string_to_find="go ty"):
  142. """Crawls a GitHub repository and extracts API keys and specific strings."""
  143. owner_repo_match = re.search(r"github\.com/([^/]+)/([^/]+)", repo_url)
  144. if not owner_repo_match:
  145. logging.warning(f"Invalid repository URL: {repo_url}")
  146. return {"api_keys": [], "go_ty_strings": []} # Return dict to hold both types of findings
  147.  
  148. owner, repo = owner_repo_match.groups()
  149. api_url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/HEAD?recursive=1"
  150.  
  151. findings = {"api_keys": [], "go_ty_strings": []} # Dictionary to store findings
  152. try:
  153. response = requests.get(api_url, headers=GITHUB_API_HEADERS, timeout=20)
  154. response.raise_for_status()
  155. repo_content = response.json()
  156.  
  157. if 'truncated' in repo_content and repo_content['truncated']:
  158. logging.warning(f"Repository tree for {repo_url} is truncated. May not get all files.")
  159.  
  160. files = [item for item in repo_content.get('tree', []) if item['type'] == 'blob']
  161. logging.info(f"Crawling repository: {repo_url}, Files to check: {len(files)}")
  162.  
  163. with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: # Increased max_workers for concurrency within repo
  164. file_futures = [executor.submit(process_file, file_info, owner, repo, repo_url, key_pattern, string_to_find) for file_info in files]
  165. for future in concurrent.futures.as_completed(file_futures):
  166. file_findings = future.result() # Get findings from each file processing
  167. findings["api_keys"].extend(file_findings["api_keys"])
  168. findings["go_ty_strings"].extend(file_findings["go_ty_strings"])
  169.  
  170.  
  171. except requests.exceptions.RequestException as e:
  172. logging.error(f"Error accessing repository tree for {repo_url}: {e}")
  173. return findings # Return findings even if repo crawl fails partially
  174.  
  175. logging.info(f"Found {len(findings['api_keys'])} API keys and {len(findings['go_ty_strings'])} 'go ty' strings in repository: {repo_url}")
  176. return findings
  177.  
  178.  
  179. def process_file(file_info, owner, repo, repo_url, key_pattern, string_to_find):
  180. """Processes a single file to find API keys and strings."""
  181. file_path = file_info['path']
  182. raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/HEAD/{file_path}"
  183. file_findings = {"api_keys": [], "go_ty_strings": []} # Findings for this file
  184.  
  185. try:
  186. raw_response = requests.get(raw_url, timeout=15)
  187. raw_response.raise_for_status()
  188. file_content = raw_response.text
  189.  
  190. # Find API Keys
  191. api_keys = re.findall(key_pattern, file_content)
  192. for key in api_keys:
  193. if save_finding_repo_crawl(repo_url, raw_url, key, "api_key"): # Save as 'api_key' type
  194. file_findings["api_keys"].append(key)
  195.  
  196. # Find "go ty" strings (case-insensitive)
  197. go_ty_matches = re.findall(re.escape(string_to_find), file_content, re.IGNORECASE) # Escape string and use case-insensitive search
  198. for match in go_ty_matches:
  199. if save_finding_repo_crawl(repo_url, raw_url, string_to_find, "go_ty_string"): # Save as 'go_ty_string' type
  200. file_findings["go_ty_strings"].append(string_to_find) # Storing the search string itself, not the match object
  201.  
  202. time.sleep(RATE_LIMIT_DELAY) # Keep rate limit delay, but concurrency makes it less impactful on total time
  203.  
  204. except requests.exceptions.RequestException as e:
  205. logging.warning(f"Error fetching raw content for {raw_url}: {e}")
  206. return file_findings # Return findings for this file
  207.  
  208.  
  209. def save_finding_repo_crawl(repo_url, raw_url, finding_value, finding_type):
  210. """Saves a finding (key or string) found during repository crawl to the database."""
  211. conn = sqlite3.connect(DATABASE_NAME)
  212. c = conn.cursor()
  213. try:
  214. c.execute(
  215. "INSERT OR IGNORE INTO findings (url, finding_type, finding_value, context_url) VALUES (?, ?, ?, ?)",
  216. (raw_url, finding_type, finding_value, repo_url),
  217. )
  218. saved = c.rowcount > 0
  219. if saved:
  220. logging.debug(f"Finding '{finding_value}' (type: {finding_type}) from URL '{raw_url}' (repo: {repo_url}) saved to database.")
  221. return saved
  222. except sqlite3.Error as e:
  223. logging.error(f"Database error saving finding '{finding_value}' (type: {finding_type}) from URL '{raw_url}' (repo: {repo_url}): {e}")
  224. return False
  225. finally:
  226. conn.close()
  227.  
  228.  
  229. def main():
  230. """Main function to execute GitHub search and repository crawl for findings."""
  231. init_db()
  232. # init_cookies_db()
  233. cookies = load_netscape_cookies("../scraping/cookies.txt")
  234. query = "deepseek AND /sk-[A-Za-z0-9]{32}/"
  235.  
  236. # 1. Search GitHub and get repository URLs
  237. repo_search_results = search_github_repos(query, cookies)
  238. logging.info(f"Found {len(repo_search_results)} repository URLs from search.")
  239.  
  240. # 2. Crawl repositories for findings (keys and "go ty") - CONCURRENT CRAWLING
  241. total_api_keys_crawled = 0
  242. total_go_ty_strings_crawled = 0
  243. all_api_keys = [] # To keep track of all API keys found
  244. all_go_ty_strings = [] # To keep track of all "go ty" strings found
  245.  
  246. with concurrent.futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor: # Use ThreadPoolExecutor for concurrent crawling
  247. futures = [executor.submit(crawl_repository_for_findings, repo_url, string_to_find="go ty") for repo_url in repo_search_results] # Submit crawl tasks
  248.  
  249. for future in concurrent.futures.as_completed(futures): # Process results as they become available
  250. repo_findings = future.result()
  251. total_api_keys_crawled += len(repo_findings["api_keys"])
  252. total_go_ty_strings_crawled += len(repo_findings["go_ty_strings"])
  253. all_api_keys.extend(repo_findings["api_keys"])
  254. all_go_ty_strings.extend(repo_findings["go_ty_strings"])
  255.  
  256.  
  257. logging.info(f"Total API keys found during repository crawling: {total_api_keys_crawled}")
  258. logging.info(f"Total 'go ty' strings found during repository crawling: {total_go_ty_strings_crawled}")
  259.  
  260. unique_api_keys_found = list(set(all_api_keys)) # Get unique keys for checking
  261.  
  262. unique_go_ty_strings_found = len(set(all_go_ty_strings)) # Technically always 1, but kept for consistency if searching for multiple strings later
  263.  
  264. print("-" * 50)
  265. print(f"Finding Extraction Summary:")
  266. print(f" Total repositories crawled: {len(repo_search_results)}")
  267. print(f" Total API keys found: {total_api_keys_crawled}")
  268. print(f" Unique API keys found: {len(unique_api_keys_found)}") # Print count of unique keys
  269. print(f" Total 'go ty' strings found: {total_go_ty_strings_crawled}")
  270. print(f" Unique 'go ty' strings found (string searched): {unique_go_ty_strings_found}") # Clarified what unique strings means
  271. print("-" * 50)
  272.  
  273. # 3. Check and Validate Unique API Keys (using functions from deepseek_key_checker.py)
  274. if unique_api_keys_found:
  275. logging.info("Starting API key validation...")
  276. api_key_objects = [APIKey(key) for key in unique_api_keys_found] # Create APIKey objects
  277. checked_keys = asyncio.run(check_keys(api_key_objects)) # Run async key check
  278. update_key_status_in_db(checked_keys, DATABASE_NAME) # Update status in DB, pass DB_NAME
  279. pretty_print_deepseek_keys(checked_keys) # Print formatted key check results
  280. else:
  281. logging.info("No unique API keys found to check.")
  282.  
  283.  
  284. logging.info("Script finished.")
  285.  
  286.  
  287. if __name__ == "__main__":
  288. main()
Advertisement
Add Comment
Please, Sign In to add comment