Import source: src/manager
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
+309
@@ -0,0 +1,309 @@
|
|||||||
|
"""
|
||||||
|
Progress and Cookie Management Module
|
||||||
|
|
||||||
|
This module provides functionality for managing application progress, cookies,
|
||||||
|
and database operations.
|
||||||
|
|
||||||
|
Classes:
|
||||||
|
ProgressManager: Handles progress tracking and persistence
|
||||||
|
CookieManager: Manages browser cookie operations
|
||||||
|
DatabaseManager: Handles database interactions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from selenium.common.exceptions import UnableToSetCookieException
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
LOGGER_NAME = "ChatGPT-API-Leakage"
|
||||||
|
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT, datefmt="[%X]")
|
||||||
|
logger = logging.getLogger(LOGGER_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressManagerError(Exception):
|
||||||
|
"""Custom exception for ProgressManager class errors"""
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressManager:
|
||||||
|
"""
|
||||||
|
Manages and persists progress information for long-running operations.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
progress_file (Path): Path to the progress file
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
save: Saves current progress
|
||||||
|
load: Loads saved progress
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, progress_file=".progress.txt"):
|
||||||
|
"""
|
||||||
|
Initialize the ProgressManager with a specified progress file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
progress_file (str): The file where progress data is stored.
|
||||||
|
"""
|
||||||
|
self.progress_file = progress_file
|
||||||
|
|
||||||
|
def save(self, from_iter: int, total: int):
|
||||||
|
"""
|
||||||
|
Saves the current progress to a file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
from_iter (int): The current iteration progress.
|
||||||
|
total (int): The total number of iterations.
|
||||||
|
"""
|
||||||
|
with open(self.progress_file, "w", encoding="utf-8") as file:
|
||||||
|
file.write(f"{from_iter}/{total}/{time.time()}")
|
||||||
|
|
||||||
|
def load(self, total: int) -> int:
|
||||||
|
"""
|
||||||
|
Loads the previously saved progress if available and valid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
total (int): The total number of iterations for the current process.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The iteration number to continue from.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(self.progress_file):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
with open(self.progress_file, "r", encoding="utf-8") as file:
|
||||||
|
last_, totl_, tmst_ = file.read().strip().split("/")
|
||||||
|
last, totl = int(last_), int(totl_)
|
||||||
|
|
||||||
|
if time.time() - float(tmst_) < 3600 and totl == total:
|
||||||
|
action = input(f"🔍 Progress found, do you want to continue from the last progress ({last}/{totl})? [yes] | no: ").lower()
|
||||||
|
if action in {"yes", "y", ""}:
|
||||||
|
return last
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class CookieManager:
|
||||||
|
"""
|
||||||
|
Manages browser cookie operations.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
save: Saves cookies to a file
|
||||||
|
load: Loads cookies from a file
|
||||||
|
verify_user_login: Checks if the user is currently logged in
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
"""
|
||||||
|
Initialize the CookieManager with a Selenium WebDriver instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
driver (WebDriver): The Selenium WebDriver for cookie operations.
|
||||||
|
"""
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""
|
||||||
|
Save cookies from the current browser session to a file.
|
||||||
|
"""
|
||||||
|
cookies = self.driver.get_cookies()
|
||||||
|
with open("cookies.pkl", "wb") as file:
|
||||||
|
pickle.dump(cookies, file)
|
||||||
|
logger.info("🍪 Cookies saved")
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""
|
||||||
|
Load cookies from a file and attempt to add them to the current browser session.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open("cookies.pkl", "rb") as file:
|
||||||
|
cookies = pickle.load(file)
|
||||||
|
for cookie in cookies:
|
||||||
|
try:
|
||||||
|
self.driver.add_cookie(cookie)
|
||||||
|
except UnableToSetCookieException:
|
||||||
|
logger.debug("🟡 Warning, unable to set a cookie %s", cookie)
|
||||||
|
except (EOFError, pickle.UnpicklingError):
|
||||||
|
if os.path.exists("cookies.pkl"):
|
||||||
|
os.remove("cookies.pkl")
|
||||||
|
logger.error("🔴 Error, unable to load cookies, invalid cookies has been removed, please restart.")
|
||||||
|
|
||||||
|
def verify_user_login(self):
|
||||||
|
"""
|
||||||
|
Test if the user is really logged in by navigating to GitHub and checking login status.
|
||||||
|
"""
|
||||||
|
logger.info("🤗 Redirecting ...")
|
||||||
|
self.driver.get("https://github.com/")
|
||||||
|
|
||||||
|
if self.driver.find_elements(by=By.XPATH, value="//*[contains(text(), 'Sign in')]"):
|
||||||
|
if os.path.exists("cookies.pkl"):
|
||||||
|
os.remove("cookies.pkl")
|
||||||
|
logger.error("🔴 Error, you are not logged in, please restart and try again.")
|
||||||
|
sys.exit(1)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManager:
|
||||||
|
"""
|
||||||
|
This class is used to manage the database, including creating tables and handling data interactions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_filename: str):
|
||||||
|
"""
|
||||||
|
Initialize the DatabaseManager with the specified database filename.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_filename (str): Path to the SQLite database file.
|
||||||
|
"""
|
||||||
|
self.db_filename = db_filename
|
||||||
|
self.con = None
|
||||||
|
self.cur = None
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""
|
||||||
|
Enter the runtime context related to this object, initializing the database if needed.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(self.db_filename):
|
||||||
|
logging.info("Creating database github.db")
|
||||||
|
|
||||||
|
self.con = sqlite3.connect(self.db_filename)
|
||||||
|
self.cur = self.con.cursor()
|
||||||
|
|
||||||
|
self.cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='APIKeys'")
|
||||||
|
if self.cur.fetchone() is None:
|
||||||
|
logging.info("Creating table APIKeys")
|
||||||
|
self.cur.execute("CREATE TABLE APIKeys(apiKey, status, lastChecked)")
|
||||||
|
|
||||||
|
self.cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='URLs'")
|
||||||
|
if self.cur.fetchone() is None:
|
||||||
|
logging.info("Creating table URLs")
|
||||||
|
self.cur.execute("CREATE TABLE URLs(url, key)")
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
"""
|
||||||
|
Exit the runtime context and close the database connection.
|
||||||
|
"""
|
||||||
|
if self.con:
|
||||||
|
self.con.close()
|
||||||
|
|
||||||
|
def all_iq_keys(self) -> list:
|
||||||
|
"""
|
||||||
|
Get all keys with the status 'insufficient_quota'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of tuples containing API keys.
|
||||||
|
"""
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
self.cur.execute("SELECT apiKey FROM APIKeys WHERE status='insufficient_quota'")
|
||||||
|
return self.cur.fetchall()
|
||||||
|
|
||||||
|
def all_keys(self) -> list:
|
||||||
|
"""
|
||||||
|
Get all keys with the status 'yes'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of tuples containing API keys.
|
||||||
|
"""
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
self.cur.execute("SELECT apiKey FROM APIKeys WHERE status='yes'")
|
||||||
|
return self.cur.fetchall()
|
||||||
|
|
||||||
|
def deduplicate(self) -> None:
|
||||||
|
"""
|
||||||
|
Deduplicate the 'APIKeys' table by retaining only the latest record for each key.
|
||||||
|
"""
|
||||||
|
if self.con is None:
|
||||||
|
raise ValueError("Connection is not initialized")
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
self.cur.execute("CREATE TABLE temp_table as SELECT apiKey, status, MAX(lastChecked) as lastChecked FROM APIKeys GROUP BY apiKey;")
|
||||||
|
self.cur.execute("DROP TABLE APIKeys;")
|
||||||
|
self.cur.execute("ALTER TABLE temp_table RENAME TO APIKeys;")
|
||||||
|
self.con.commit()
|
||||||
|
|
||||||
|
def delete(self, api_key: str) -> None:
|
||||||
|
"""
|
||||||
|
Delete a specific API key from the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key (str): The unique API key to remove.
|
||||||
|
"""
|
||||||
|
if self.con is None:
|
||||||
|
raise ValueError("Connection is not initialized")
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
self.cur.execute("DELETE FROM APIKeys WHERE apiKey=?", (api_key,))
|
||||||
|
self.con.commit()
|
||||||
|
|
||||||
|
def insert(self, api_key: str, status: str):
|
||||||
|
"""
|
||||||
|
Insert a new API key and status into the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key (str): The API key to insert.
|
||||||
|
status (str): The status of the API key.
|
||||||
|
"""
|
||||||
|
if self.con is None:
|
||||||
|
raise ValueError("Connection is not initialized")
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
today = date.today()
|
||||||
|
self.cur.execute("INSERT INTO APIKeys(apiKey, status, lastChecked) VALUES(?, ?, ?)", (api_key, status, today))
|
||||||
|
self.con.commit()
|
||||||
|
|
||||||
|
def key_exists(self, api_key: str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a given API key exists in the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key (str): The API key to search for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the API key exists, False otherwise.
|
||||||
|
"""
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
self.cur.execute("SELECT apiKey FROM APIKeys WHERE apiKey=?", (api_key,))
|
||||||
|
return self.cur.fetchone() is not None
|
||||||
|
|
||||||
|
def insert_url(self, url: str) -> None:
|
||||||
|
"""
|
||||||
|
Insert a new URL into the 'URLs' table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): The URL to add.
|
||||||
|
"""
|
||||||
|
if self.con is None:
|
||||||
|
raise ValueError("Connection is not initialized")
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
self.cur.execute("INSERT INTO URLs(url, key) VALUES(?, ?)", (url, 1))
|
||||||
|
self.con.commit()
|
||||||
|
|
||||||
|
def get_url(self, url: str) -> str | None:
|
||||||
|
"""
|
||||||
|
Retrieve the 'key' associated with the given URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): The URL to look up.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str | None: The key if it exists, None if not.
|
||||||
|
"""
|
||||||
|
if self.cur is None:
|
||||||
|
raise ValueError("Cursor is not initialized")
|
||||||
|
self.cur.execute("SELECT key FROM URLs WHERE url=?", (url,))
|
||||||
|
fetch = self.cur.fetchone()
|
||||||
|
return fetch[0] if fetch else None
|
||||||
Reference in New Issue
Block a user