├── json_files └── put ur credentials.json file here ├── .gitignore ├── requirements.txt ├── executables └── README.md ├── utils ├── text_splitter.py ├── email_utils.py └── automation_utils.py ├── file_processing ├── reader.py └── save_paraphrased_doc.py ├── ai_scanner.py ├── README.md ├── api_grabber.py ├── paraphraser.py └── gui.py /json_files/put ur credentials.json file here: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | file_processing/__pycache__/ 3 | file_processing/__pycache__/ 4 | utils/__pycache__/ 5 | json_files/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | colorama 2 | pyperclip 3 | selenium 4 | PyQt6 5 | fake-useragent 6 | nltk 7 | plyer 8 | pypdf 9 | python-docx 10 | reportlab 11 | google-auth 12 | google-auth-oauthlib 13 | google-auth-httplib2 14 | google-api-python-client 15 | -------------------------------------------------------------------------------- /executables/README.md: -------------------------------------------------------------------------------- 1 | The exe file is made for non-tech users or lazy tech users to make things simple and easy to use. 2 | The executable is large so it cannot be uploaded here. You can download it from here: 3 | 4 | Download: 5 | 1. https://www.mediafire.com/file/7t1vn65wrvu81nz/gui.rar/file 6 | 7 | Password: github.com/obaskly 8 | 9 | Note: The script is compiled using Pyinstaller. It might get flagged as a virus, but it's just a false positive. 10 | -------------------------------------------------------------------------------- /utils/text_splitter.py: -------------------------------------------------------------------------------- 1 | from nltk.tokenize import sent_tokenize 2 | import re 3 | 4 | def sanitize_text_for_chromedriver(text): 5 | """ 6 | Remove characters outside the Basic Multilingual Plane (BMP) that ChromeDriver doesn't support. 7 | This includes emojis and certain rare characters. 8 | """ 9 | # This regex pattern matches any character outside the BMP range (U+0000 to U+FFFF) 10 | return re.sub(r'[^\u0000-\uFFFF]', '', text) 11 | 12 | def split_text_preserve_sentences(text, max_words): 13 | # Sanitize text first 14 | text = sanitize_text_for_chromedriver(text) 15 | 16 | sentences = sent_tokenize(text) 17 | chunks = [] 18 | current_chunk = [] 19 | current_word_count = 0 20 | 21 | for sentence in sentences: 22 | words_in_sentence = len(sentence.split()) 23 | if current_word_count + words_in_sentence > max_words: 24 | chunks.append(' '.join(current_chunk)) 25 | current_chunk = [] 26 | current_word_count = 0 27 | current_chunk.append(sentence) 28 | current_word_count += words_in_sentence 29 | 30 | if current_chunk: 31 | chunks.append(' '.join(current_chunk)) 32 | 33 | return chunks 34 | -------------------------------------------------------------------------------- /file_processing/reader.py: -------------------------------------------------------------------------------- 1 | from docx import Document 2 | from pypdf import PdfReader 3 | import nltk 4 | import re 5 | 6 | try: 7 | nltk.download('punkt') 8 | except: 9 | nltk.download('punkt') 10 | 11 | def extract_text_from_docx(file_path): 12 | try: 13 | doc = Document(file_path) 14 | raw_text = '\n'.join([paragraph.text for paragraph in doc.paragraphs]) 15 | return format_text(raw_text) 16 | except Exception as e: 17 | return f"Error reading DOCX file: {e}" 18 | 19 | def extract_text_from_pdf(file_path): 20 | try: 21 | reader = PdfReader(file_path) 22 | raw_text = '\n'.join([page.extract_text() for page in reader.pages]) 23 | return format_text(raw_text) 24 | except Exception as e: 25 | raise ValueError(f"Error reading PDF file: {e}") 26 | 27 | def format_text(text): 28 | text = remove_unnecessary_line_breaks(text) 29 | sentences = nltk.sent_tokenize(text) 30 | formatted_text = ' '.join(sentences) 31 | return formatted_text 32 | 33 | def remove_unnecessary_line_breaks(text): 34 | # Replace line breaks followed by non-uppercase letters with a space 35 | cleaned_text = re.sub(r'\n(?![A-Z])', ' ', text) 36 | # Replace multiple spaces with a single space 37 | cleaned_text = re.sub(r' +', ' ', cleaned_text) 38 | return cleaned_text.strip() 39 | -------------------------------------------------------------------------------- /file_processing/save_paraphrased_doc.py: -------------------------------------------------------------------------------- 1 | from docx import Document 2 | from reportlab.lib.pagesizes import letter 3 | from reportlab.lib.styles import ParagraphStyle 4 | from reportlab.platypus import Paragraph, SimpleDocTemplate 5 | from pypdf import PdfReader, PdfWriter 6 | from io import BytesIO 7 | import os 8 | 9 | def get_output_filename(input_path): 10 | base_dir = os.path.dirname(input_path) 11 | ext = os.path.splitext(input_path)[1] 12 | return os.path.join(base_dir, f"paraphrased{ext}") 13 | 14 | def save_as_docx(input_path, paraphrased_text, save_path=None): 15 | try: 16 | output_path = save_path or get_output_filename(input_path) 17 | 18 | if os.path.exists(output_path): 19 | doc = Document(output_path) 20 | else: 21 | doc = Document() 22 | 23 | doc.add_paragraph(paraphrased_text) 24 | doc.save(output_path) 25 | print(f"Successfully saved paraphrased text to {output_path}") 26 | except Exception as e: 27 | raise ValueError(f"Error saving DOCX: {str(e)}") 28 | 29 | def save_as_txt(input_path, paraphrased_text, save_path=None): 30 | try: 31 | output_path = save_path or get_output_filename(input_path) # Use custom path if provided 32 | 33 | with open(output_path, 'a', encoding='utf-8') as f: 34 | f.write(paraphrased_text + '\n') 35 | 36 | print(f"Successfully saved paraphrased text to {output_path}") 37 | except Exception as e: 38 | raise ValueError(f"Error saving TXT: {str(e)}") 39 | 40 | def save_as_pdf(input_path, paraphrased_text, save_path=None): 41 | try: 42 | output_path = save_path or get_output_filename(input_path) # Use custom path if provided 43 | 44 | existing_text = "" 45 | if os.path.exists(output_path): 46 | reader = PdfReader(output_path) 47 | for page in reader.pages: 48 | existing_text += page.extract_text() + "\n" 49 | 50 | combined_text = existing_text.strip() + "\n\n" + paraphrased_text.strip() 51 | 52 | doc = SimpleDocTemplate( 53 | output_path, 54 | pagesize=letter, 55 | rightMargin=72, 56 | leftMargin=72, 57 | topMargin=72, 58 | bottomMargin=72 59 | ) 60 | 61 | style = ParagraphStyle( 62 | 'Normal', 63 | fontSize=12, 64 | leading=16, 65 | spaceBefore=12, 66 | spaceAfter=12, 67 | firstLineIndent=24 68 | ) 69 | 70 | story = [] 71 | for paragraph in combined_text.split("\n\n"): 72 | if paragraph.strip(): 73 | story.append(Paragraph(paragraph.strip(), style)) 74 | 75 | doc.build(story) 76 | 77 | print(f"Successfully saved paraphrased text to {output_path}") 78 | except Exception as e: 79 | raise ValueError(f"Error saving PDF: {str(e)}") 80 | -------------------------------------------------------------------------------- /ai_scanner.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import os 4 | import time 5 | 6 | def detect_ai_in_text(api_key, text): 7 | url = "https://ai-detect.undetectable.ai/detect" 8 | headers = { 9 | 'accept': 'application/json', 10 | 'Content-Type': 'application/json' 11 | } 12 | payload = { 13 | 'text': text, 14 | 'key': api_key, 15 | 'model': 'detector_v2' 16 | } 17 | 18 | try: 19 | response = requests.post(url, headers=headers, data=json.dumps(payload)) 20 | response.raise_for_status() # Raises HTTPError for bad responses 21 | return response.json() 22 | except requests.exceptions.HTTPError as http_err: 23 | print(f"HTTP error occurred: {http_err}") 24 | print("Response:", response.text) # Print the response for more details 25 | return None 26 | except requests.exceptions.RequestException as req_err: 27 | print(f"Request error occurred: {req_err}") 28 | return None 29 | 30 | def get_detection_result(api_key, document_id): 31 | url = "https://ai-detect.undetectable.ai/query" 32 | headers = { 33 | 'accept': 'application/json', 34 | 'Content-Type': 'application/json' 35 | } 36 | payload = {'id': document_id} 37 | 38 | try: 39 | response = requests.post(url, headers=headers, data=json.dumps(payload)) 40 | response.raise_for_status() 41 | return response.json() 42 | except requests.exceptions.HTTPError as http_err: 43 | print(f"HTTP error occurred while querying: {http_err}") 44 | return None 45 | except requests.exceptions.RequestException as req_err: 46 | print(f"Request error occurred while querying: {req_err}") 47 | return None 48 | 49 | def scan_text_for_ai(api_key, text): 50 | # Detect AI in the text 51 | detection_response = detect_ai_in_text(api_key, text) 52 | 53 | if detection_response is None or detection_response.get("status") != "pending": 54 | print("Failed to detect AI in text.") 55 | return None, None 56 | 57 | # Get the document ID from the response 58 | document_id = detection_response.get("id") 59 | if not document_id: 60 | print("No document ID returned from detection.") 61 | return None, None 62 | 63 | print("Detection is processing... querying result now.") 64 | 65 | max_retries = 10 # Maximum number of retries (for 30 seconds total if checking every 3 seconds) 66 | retries = 0 67 | 68 | # Poll the result every 3 seconds until it's ready 69 | while retries < max_retries: 70 | result_response = get_detection_result(api_key, document_id) 71 | 72 | if result_response and result_response.get("status") == "done": 73 | result_details = result_response.get("result_details") 74 | if result_details: 75 | human_percentage = result_details.get("human", 0) 76 | ai_percentage = 100 - human_percentage 77 | 78 | # Collect individual detector results 79 | detector_results = {} 80 | for detector, score in result_details.items(): 81 | if detector != "human": # Skip the human score 82 | human_score = 100 - score # Calculate human percentage 83 | detector_results[detector] = { 84 | "ai_percentage": score, 85 | "human_percentage": human_score 86 | } 87 | 88 | return ai_percentage, detector_results 89 | else: 90 | print("No result details available.") 91 | return None, None 92 | 93 | elif result_response: 94 | print("Detection still pending... retrying in 3 seconds...") 95 | time.sleep(3) # Wait for 3 seconds before retrying 96 | retries += 1 97 | else: 98 | print("Failed to query the result.") 99 | return None, None 100 | 101 | print("Max retries reached. The document is still processing.") 102 | return None, None 103 | 104 | def scan_text(api_key, text): 105 | return scan_text_for_ai(api_key, text) 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📝 Ai Text Detection Bypass Script 2 | 3 | ParaGenie is a script that automates the process of paraphrasing articles using the undetectable.ai platform. It enables users to transform long-form content into unique paraphrased versions by splitting the input text into manageable chunks and processing each chunk individually. 4 | 5 | ## ✨ What's New? 6 | 7 | You can Try the Other tool as well https://github.com/obaskly/AiDetectionBypass 8 | 9 | ## 🛑 Disclaimer 10 | 11 | This script automates text paraphrasing but is **NOT responsible for the quality or accuracy of the output**. Please review and verify results independently. 12 | 13 | ## 🔥 Features 14 | 15 | - Automated Paraphrasing: Automatically paraphrase long articles by breaking them into chunks. 16 | - Multi-File Support: Handles text extraction and processing for TXT, DOCX, and PDF file formats. (NEW!) 17 | - Customizable Chunk Splitting: Choose between the default word-based splitting method or a more advanced NLTK-powered sentence-preserving approach. (NEW!) 18 | - Gmail-Based Registration: Automates Gmail registration and verification for seamless paraphrasing. (NEW!) 19 | - Smart Email Management: Saves generated Gmail variations in a JSON file to avoid redundancy. (NEW!) 20 | - API Extraction: The script now retrieves API keys stored in the Gmail variations JSON file. It iterates through the entries and extracts each API key, storing them in an apis.json file for easy access. (NEW!) 21 | - AI Detection Percentage: The script now scans a given text and returns the percentage likelihood of the content being generated by AI. (NEW!) **Put your API in line 280 in gui.py** 22 | - Purpose-Specific Writing: Supports a variety of writing purposes such as essays, articles, marketing material, and more. 23 | - Readability Options: Tailor the readability level of the output, from high school to professional marketing standards. 24 | - Tone selection: Support three different kinds of tones. 25 | - Anonymity Features: Leverages Chrome's incognito mode and a random user agent to protect your identity. 26 | - Error Handling and Recovery: Automatically retries chunks if any errors occur during the paraphrasing process. 27 | - Output Management: Saves paraphrased content into a file for easy access and organization. 28 | 29 | ## Usage 30 | 31 | 1. **Clone this repository to your local machine**. 32 | 33 | ```bash 34 | git clone https://github.com/obaskly/AiTextDetectionBypass.git 35 | cd AiTextDetectionBypass 36 | ``` 37 | 38 | 2. **Install the required Python packages**. 39 | 40 | ```bash 41 | pip install -r requirements.txt 42 | ``` 43 | 44 | 3. **GMAIL Setup**. 45 | 46 | - go to https://console.cloud.google.com 47 | - create new project 48 | - click on 'api and services' 49 | - type "Gmail API" and select it from the results. 50 | - Click the Enable button. 51 | 52 | #### Set Up OAuth Consent Screen 53 | 54 | - In the left-hand menu, go to APIs & Services > OAuth consent screen. 55 | - Choose External 56 | - Add the necessary scopes: https://www.googleapis.com/auth/gmail.readonly 57 | - Go to the Test users section. 58 | - Add the Gmail address you want to use for paraphrasing. 59 | - Click Save and Continue. 60 | 61 | #### Create OAuth 2.0 Credentials 62 | 63 | - Go to APIs & Services > Credentials. 64 | - Click Create Credentials > OAuth 2.0 Client IDs. 65 | - Choose Desktop App as the application type. 66 | - Download the **credentials.json** file once it’s created and put it in the **json_files** folder. 67 | 68 | 4. **Run the script**. 69 | 70 | ```bash 71 | python gui.py 72 | ``` 73 | 74 | #### *Sit back and relax while the script paraphrases your article!* 75 | 76 | ## Prerequisites 77 | 78 | - Python 3.x 79 | - Google Chrome installed (chromedriver is used by Selenium) 80 | 81 | ## TODO List 82 | 83 | - [x] ~~Handles text extraction and processing for TXT, DOCX, and PDF file formats.~~ 84 | - [x] ~~Choose between the default word-based splitting method or a more advanced NLTK-powered sentence-preserving approach.~~ 85 | - [x] ~~Automates Gmail registration and verification for seamless paraphrasing.~~ 86 | - [x] ~~Saves generated Gmail variations in a JSON file to avoid redundancy.~~ 87 | - [x] ~~Extract API keys from Gmail variations JSON and store in `apis.json`~~ 88 | - [x] ~~Implement AI detection percentage feature~~ 89 | - [ ] Automatically retrieve API keys from apis.json file and integrate them for AI scanning functionality. 90 | - [ ] Multiple files paraphrasing. 91 | 92 | ## Script in action 93 | 94 | https://github.com/obaskly/AiTextDetectionBypass/assets/11092871/7fc4ccf0-d97f-43dd-a987-9b7e6812c174 95 | 96 | ## Contact 97 | 98 | - Telegram: **https://t.me/Anon040** 99 | -------------------------------------------------------------------------------- /utils/email_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import base64 4 | import random 5 | import json 6 | from googleapiclient.discovery import build 7 | from google.oauth2.credentials import Credentials 8 | from google_auth_oauthlib.flow import InstalledAppFlow 9 | from google.auth.transport.requests import Request 10 | 11 | # Gmail API scope 12 | SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'] 13 | 14 | # File to persist generated variations 15 | VARIATIONS_FILE = os.path.join('json_files', 'generated_variations.json') 16 | 17 | def authenticate_gmail(): 18 | creds = None 19 | # The token.json file stores the user's access and refresh tokens. 20 | token_path = os.path.join('json_files', 'token.json') 21 | if os.path.exists(token_path): 22 | creds = Credentials.from_authorized_user_file(token_path, SCOPES) 23 | 24 | # If there are no valid credentials available, let the user log in. 25 | if not creds or not creds.valid: 26 | if creds and creds.expired and creds.refresh_token: 27 | creds.refresh(Request()) # This was causing the error 28 | else: 29 | credentials_path = os.path.join('json_files', 'credentials.json') 30 | flow = InstalledAppFlow.from_client_secrets_file(credentials_path, SCOPES) 31 | creds = flow.run_local_server(port=0) 32 | # Save the credentials for the next run. 33 | token_path = os.path.join('json_files', 'token.json') 34 | with open(token_path, 'w') as token: 35 | token.write(creds.to_json()) 36 | 37 | return creds 38 | 39 | def calculate_email_variations(email): 40 | username, domain = email.split("@") 41 | positions = len(username) - 1 42 | total_variations = 2 ** positions 43 | return total_variations 44 | 45 | def email_exists_in_file(email): 46 | if not os.path.exists(VARIATIONS_FILE): 47 | return False 48 | try: 49 | with open(VARIATIONS_FILE, 'r') as f: 50 | variations = json.load(f) 51 | return email in variations 52 | except (json.JSONDecodeError, ValueError): 53 | # If the file is empty or corrupted, return False 54 | return False 55 | 56 | def save_email_to_file(email): 57 | if os.path.exists(VARIATIONS_FILE): 58 | try: 59 | with open(VARIATIONS_FILE, 'r') as f: 60 | variations = json.load(f) 61 | except (json.JSONDecodeError, ValueError): 62 | # If the file is empty or corrupted, start with an empty list 63 | variations = [] 64 | else: 65 | variations = [] 66 | 67 | variations.append(email) 68 | with open(VARIATIONS_FILE, 'w') as f: 69 | json.dump(variations, f, indent=4) 70 | 71 | def generate_gmail_variation(base_email, max_attempts=20): 72 | local_part, domain = base_email.split('@') 73 | 74 | if len(local_part) <= 2: 75 | return base_email # Not enough characters to add dots 76 | 77 | for _ in range(max_attempts): 78 | local_part_variants = list(local_part) 79 | max_dots = len(local_part) - 2 80 | num_dots = random.randint(1, max_dots) if max_dots > 0 else 0 81 | dot_positions = sorted(random.sample(range(1, len(local_part) - 1), num_dots)) 82 | 83 | for i, pos in enumerate(dot_positions): 84 | local_part_variants.insert(pos + i, '.') 85 | 86 | new_email = ''.join(local_part_variants).strip('.') + '@' + domain 87 | 88 | # Check if the email already exists in the file 89 | if not email_exists_in_file(new_email): 90 | save_email_to_file(new_email) 91 | return new_email 92 | 93 | raise ValueError("Unable to generate a unique Gmail variation after multiple attempts.") 94 | 95 | def extract_verify_link(text): 96 | pattern = r'https://undetectable\.ai/api/auth/callback/sendgrid\?[^"]+' 97 | matches = re.findall(pattern, text) 98 | return matches[0] if matches else None 99 | 100 | def get_gmail_service(creds): 101 | return build('gmail', 'v1', credentials=creds) 102 | 103 | def search_for_confirmation_email(service, query='subject:Sign in to Undetectable AI'): 104 | try: 105 | results = service.users().messages().list(userId='me', q=query, maxResults=1).execute() 106 | messages = results.get('messages', []) 107 | 108 | if not messages: 109 | print("No confirmation email found.") 110 | return None 111 | 112 | # Get the first (most recent) message 113 | message = service.users().messages().get(userId='me', id=messages[0]['id'], format='full').execute() 114 | return message 115 | except Exception as e: 116 | print(f"Error retrieving confirmation email: {e}") 117 | return None 118 | 119 | def get_message_body(message): 120 | if 'parts' in message['payload']: 121 | # Iterate through all parts of the email 122 | for part in message['payload']['parts']: 123 | # Check if the part is HTML 124 | if part['mimeType'] == 'text/html': 125 | msg_data = part['body'].get('data', '') 126 | decoded_data = base64.urlsafe_b64decode(msg_data).decode('utf-8') 127 | print(f"Extracted HTML part: {decoded_data[:500]}") 128 | return decoded_data 129 | # Fallback to plain text if no HTML part found 130 | elif part['mimeType'] == 'text/plain': 131 | msg_data = part['body'].get('data', '') 132 | decoded_data = base64.urlsafe_b64decode(msg_data).decode('utf-8') 133 | print(f"Extracted plain text part: {decoded_data[:500]}") 134 | return decoded_data 135 | else: 136 | # Handle case where the message does not have parts (e.g., it's a single text/plain or text/html email) 137 | msg_data = message['payload']['body'].get('data', '') 138 | decoded_data = base64.urlsafe_b64decode(msg_data).decode('utf-8') 139 | print(f"Extracted single body message: {decoded_data[:500]}") 140 | return decoded_data 141 | return '' 142 | -------------------------------------------------------------------------------- /api_grabber.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from utils.email_utils import authenticate_gmail, get_gmail_service, extract_verify_link, get_message_body 4 | from utils.automation_utils import initialize_driver, automate_sign_in, process_confirmation_link, wait_for_confirmation_email 5 | from selenium.common.exceptions import TimeoutException 6 | from selenium.webdriver.support.ui import WebDriverWait 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.support import expected_conditions as EC 9 | from fake_useragent import UserAgent 10 | 11 | ua = UserAgent() 12 | fake_user_agent = ua.random 13 | 14 | def load_existing_apis(output_file): 15 | #Load existing APIs from the JSON file, if it exists. 16 | if os.path.exists(output_file): 17 | with open(output_file, 'r') as f: 18 | return json.load(f) 19 | return {} 20 | 21 | def get_new_emails(existing_apis, email_variations): 22 | #Get emails that have not been processed yet 23 | if not existing_apis: 24 | return email_variations # Process all emails if no existing APIs 25 | 26 | last_processed_email = list(existing_apis.keys())[-1] 27 | if last_processed_email not in email_variations: 28 | return email_variations # Process all if last processed email not in the variations 29 | 30 | # Find the position of the last processed email and return emails after it 31 | start_index = email_variations.index(last_processed_email) + 1 32 | return email_variations[start_index:] 33 | 34 | def main(update_current_email=None, update_result=None): 35 | variations_file_path = os.path.join('json_files', 'generated_variations.json') 36 | output_file_path = os.path.join('json_files', 'apis.json') 37 | 38 | # Check if the JSON file with email variations exists 39 | if not os.path.exists(variations_file_path): 40 | error_message = f"{variations_file_path} does not exist. Please provide the file with email variations." 41 | print(error_message) 42 | return error_message # Return the error message to the caller 43 | 44 | # Load email variations 45 | with open(variations_file_path, 'r') as f: 46 | email_variations = json.load(f) 47 | 48 | # Load existing APIs and determine new emails to process 49 | existing_apis = load_existing_apis(output_file_path) 50 | new_emails = get_new_emails(existing_apis, email_variations) 51 | 52 | if not new_emails: 53 | print("No new emails to process. Exiting.") 54 | return "No new emails to process." 55 | 56 | # Open the output file in append mode 57 | with open(output_file_path, 'w') as f: 58 | f.write(json.dumps(existing_apis, indent=4)) 59 | 60 | # Process new emails 61 | apis = existing_apis.copy() 62 | for email in new_emails: 63 | if update_current_email: 64 | update_current_email(email) # Emit current email being processed 65 | 66 | api_key = None # Initialize API key as None 67 | try: 68 | print(f"Processing email: {email}") 69 | driver = initialize_driver(fake_user_agent) 70 | 71 | retry_attempts = 3 # Maximum retry attempts 72 | for attempt in range(retry_attempts): 73 | try: 74 | # Sign in 75 | automate_sign_in(driver, email, email) 76 | 77 | # Wait for confirmation email and process link 78 | creds = authenticate_gmail() 79 | service = get_gmail_service(creds) 80 | confirmation_email = wait_for_confirmation_email(service) 81 | 82 | if confirmation_email: 83 | message_body = get_message_body(confirmation_email) 84 | verify_link = extract_verify_link(message_body) 85 | if verify_link: 86 | success = process_confirmation_link(driver, verify_link, service, email) 87 | if success: 88 | break # Exit retry loop if successful 89 | else: 90 | print(f"Verification failed for {email}. Retrying.") 91 | else: 92 | print(f"Verify link not found in email for {email}. Retrying.") 93 | else: 94 | print(f"No confirmation email found for {email}. Retrying.") 95 | 96 | except Exception as e: 97 | print(f"Error during processing email {email}: {e}") 98 | finally: 99 | if attempt == retry_attempts - 1: 100 | print(f"Maximum retry attempts reached for {email}. Skipping.") 101 | break 102 | 103 | # Navigate to the API page and extract API key 104 | driver.get('https://undetectable.ai/develop') 105 | 106 | wait = WebDriverWait(driver, 10) 107 | api_element = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="apiKey"]'))) 108 | api_key = api_element.get_attribute('value') 109 | print(f'API key for {email}: {api_key}') 110 | apis[email] = api_key 111 | 112 | except Exception as e: 113 | print(f"Error during getting API for {email}: {e}") 114 | finally: 115 | driver.quit() 116 | 117 | # Notify final result 118 | if update_result: 119 | if api_key: 120 | update_result(f"{email}: {api_key}") # Notify about the grabbed API key 121 | else: 122 | update_result(f"{email}: Failed to grab API") 123 | 124 | # Append the current progress to the file 125 | with open(output_file_path, 'w') as f: 126 | json.dump(apis, f, indent=4) 127 | 128 | return "APIs grabbed successfully." 129 | 130 | def grab_apis(progress_callback=None): 131 | try: 132 | print("Starting API grabber...") 133 | grabbed_apis = {} 134 | 135 | def update_progress(email, api_key=None): 136 | if api_key: 137 | grabbed_apis[email] = api_key # Store API key 138 | if progress_callback: 139 | progress_callback(email, api_key) # Notify GUI 140 | 141 | result = main(update_progress) # Pass the callback to main 142 | if result == "No new emails to process.": 143 | print(result) 144 | return {"success": False, "message": "No new emails to process."} # No new emails 145 | elif result == "APIs grabbed successfully.": 146 | print(result) 147 | return {"success": True, "message": grabbed_apis} # APIs grabbed 148 | else: 149 | print(f"Unexpected result: {result}") 150 | return {"success": False, "message": "Unexpected result from grab_apis."} 151 | except Exception as e: 152 | error_message = str(e) 153 | print(f"Error occurred in API grabbing: {error_message}") 154 | return {"success": False, "message": error_message} 155 | -------------------------------------------------------------------------------- /utils/automation_utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | import re 3 | from selenium import webdriver 4 | from selenium.webdriver.common.by import By 5 | from selenium.webdriver.support.ui import WebDriverWait 6 | from selenium.webdriver.support import expected_conditions as EC 7 | from selenium.webdriver.support.ui import Select 8 | from selenium.common.exceptions import TimeoutException 9 | from colorama import Fore 10 | from utils.email_utils import calculate_email_variations, get_message_body, search_for_confirmation_email 11 | 12 | def automate_sign_in(driver, temp_email, base_email): 13 | try: 14 | # Go to the Undetectable AI login page 15 | driver.get('https://undetectable.ai/login') 16 | 17 | # Enter the Gmail address into the email input field 18 | email_input = WebDriverWait(driver, 10).until( 19 | EC.presence_of_element_located((By.XPATH, "//input[@name='email']")) 20 | ) 21 | email_input.send_keys(temp_email) 22 | 23 | # Click on the "continue" button 24 | sign_in_button = WebDriverWait(driver, 10).until( 25 | EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='Continue']")) 26 | ) 27 | driver.execute_script("arguments[0].click();", sign_in_button) 28 | 29 | print(f"\nSign-in attempt made with email: {temp_email}") 30 | print(f"{Fore.GREEN}Total variations for {base_email}: {calculate_email_variations(base_email)}") 31 | 32 | except Exception as e: 33 | print(f"Error during sign-in process: {e}") 34 | 35 | def wait_for_confirmation_email(service, max_wait_time=300, poll_interval=10): 36 | time.sleep(7) 37 | start_time = time.time() 38 | while time.time() - start_time < max_wait_time: 39 | confirmation_email = search_for_confirmation_email(service) 40 | if confirmation_email: 41 | print(f"{Fore.GREEN}Confirmation email found!") 42 | return confirmation_email 43 | print(f"{Fore.YELLOW}Waiting for the confirmation email... polling again in {poll_interval} seconds.") 44 | time.sleep(poll_interval) # Wait for the next poll 45 | print(f"{Fore.RED}Timeout: Confirmation email not received within {max_wait_time} seconds.") 46 | return None 47 | 48 | def monitor_url_change(driver, target_success_url, error_url, timeout=10): 49 | start_time = time.time() 50 | while time.time() - start_time < timeout: 51 | current_url = driver.current_url 52 | 53 | if error_url in current_url: 54 | print(f"{Fore.RED}Magic link expired detected in URL: {current_url}") 55 | return False # Error detected 56 | 57 | if target_success_url in current_url: 58 | print(f"{Fore.GREEN}Magic link verified successfully. Redirected to: {current_url}") 59 | return True # Success detected 60 | 61 | time.sleep(1) # Poll every 1s 62 | 63 | print(f"{Fore.YELLOW}Timeout reached while monitoring URL.") 64 | return None # Timeout 65 | 66 | def process_confirmation_link(driver, verify_link, service, email): 67 | try: 68 | # Open the magic link 69 | driver.get(verify_link) 70 | 71 | # Monitor for success or error 72 | result = monitor_url_change(driver, 73 | target_success_url="https://undetectable.ai/pricing", 74 | error_url="?error=Verification", 75 | timeout=10) 76 | 77 | if result is True: 78 | print(f"{Fore.GREEN}Verification successful.") 79 | return True 80 | elif result is False: 81 | print(f"{Fore.RED}Magic link expired. Switching to OTP mode.") 82 | # Re-enter the email for OTP 83 | email_input = WebDriverWait(driver, 10).until( 84 | EC.presence_of_element_located((By.XPATH, "//input[@name='email']")) 85 | ) 86 | email_input.clear() 87 | email_input.send_keys(email) 88 | 89 | continue_button = WebDriverWait(driver, 10).until( 90 | EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='Continue']")) 91 | ) 92 | driver.execute_script("arguments[0].click();", continue_button) 93 | 94 | print(f"{Fore.YELLOW}Waiting for OTP input fields...") 95 | 96 | # Wait for the OTP fields to appear 97 | otp_fields = WebDriverWait(driver, 20).until( 98 | EC.presence_of_all_elements_located((By.XPATH, "//input[starts-with(@id, 'otp-input-')]")) 99 | ) 100 | 101 | # Fetch the OTP from email 102 | confirmation_email = wait_for_confirmation_email(service) 103 | if not confirmation_email: 104 | print(f"{Fore.RED}Failed to retrieve OTP email.") 105 | return False 106 | 107 | message_body = get_message_body(confirmation_email) 108 | otp_code_match = re.search(r"Your OTP code is: (\d{6})", message_body) 109 | if not otp_code_match: 110 | print(f"{Fore.RED}OTP not found in the email.") 111 | return False 112 | 113 | otp_code = otp_code_match.group(1) 114 | print(f"{Fore.GREEN}Retrieved OTP: {otp_code}") 115 | 116 | # Input the OTP into the fields 117 | for i, digit in enumerate(otp_code): 118 | try: 119 | driver.execute_script("arguments[0].focus();", otp_fields[i]) 120 | 121 | # Clear any pre-existing input (just in case) 122 | otp_fields[i].clear() 123 | 124 | # Send the digit 125 | otp_fields[i].send_keys(digit) 126 | 127 | # Add a small delay between inputs to prevent race conditions 128 | time.sleep(0.2) 129 | except Exception as e: 130 | print(f"{Fore.RED}Error inputting OTP at position {i}: {e}") 131 | 132 | # Submit the OTP 133 | continue_button2 = WebDriverWait(driver, 10).until( 134 | EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='Continue']")) 135 | ) 136 | driver.execute_script("arguments[0].click();", continue_button2) 137 | 138 | # Verify if the OTP submission is successful 139 | result = monitor_url_change(driver, 140 | target_success_url="https://undetectable.ai/pricing", 141 | error_url="?error=Verification", 142 | timeout=10) 143 | 144 | if result: 145 | print(f"{Fore.GREEN}OTP verification successful.") 146 | return True 147 | else: 148 | print(f"{Fore.RED}OTP verification failed.") 149 | return False 150 | except Exception as e: 151 | print(f"{Fore.RED}Error during OTP handling: {e}") 152 | return False 153 | 154 | def initialize_driver(fake_user_agent): 155 | options = webdriver.ChromeOptions() 156 | options.add_argument("--disable-dev-shm-usage") 157 | options.add_argument("--no-sandbox") 158 | options.add_argument("--log-level=3") 159 | options.add_argument('--no-proxy-server') 160 | options.add_argument(f"user-agent={fake_user_agent}") 161 | options.add_argument("--incognito") 162 | options.add_argument("--start-maximized") 163 | options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"]) 164 | return webdriver.Chrome(options=options) 165 | -------------------------------------------------------------------------------- /paraphraser.py: -------------------------------------------------------------------------------- 1 | import random 2 | import pyperclip 3 | import time 4 | import re 5 | from selenium.common.exceptions import TimeoutException, StaleElementReferenceException 6 | from selenium.webdriver.support.ui import WebDriverWait 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.support import expected_conditions as EC 9 | from selenium.webdriver.support.ui import Select 10 | from selenium.webdriver.common.action_chains import ActionChains 11 | from fake_useragent import UserAgent 12 | from colorama import Fore 13 | from plyer import notification 14 | 15 | from utils.email_utils import authenticate_gmail, generate_gmail_variation, get_gmail_service, extract_verify_link, get_message_body 16 | from utils.automation_utils import initialize_driver, automate_sign_in, process_confirmation_link, wait_for_confirmation_email 17 | from utils.text_splitter import split_text_preserve_sentences 18 | from file_processing.reader import extract_text_from_docx, extract_text_from_pdf 19 | from file_processing.save_paraphrased_doc import save_as_docx, save_as_txt, save_as_pdf 20 | 21 | tone_xpath = { 22 | "BALANCED": "//div[contains(text(),'BALANCED')]", 23 | "MORE_HUMAN": "//div[contains(text(),'MORE HUMAN')]", 24 | "MORE_READABLE": "//div[contains(text(),'MORE READABLE')]" 25 | } 26 | 27 | # Add this new function to sanitize text 28 | def sanitize_text_for_chromedriver(text): 29 | """ 30 | Remove characters outside the Basic Multilingual Plane (BMP) that ChromeDriver doesn't support. 31 | This includes emojis and certain rare characters. 32 | """ 33 | # This regex pattern matches any character outside the BMP range (U+0000 to U+FFFF) 34 | return re.sub(r'[^\u0000-\uFFFF]', '', text) 35 | 36 | def main(purpose_choice, readability_choice, article_file_path, base_email, use_nltk, save_same_format, tone_choice): 37 | driver = None 38 | try: 39 | if article_file_path.lower().endswith('.docx'): 40 | article_text = extract_text_from_docx(article_file_path) 41 | elif article_file_path.lower().endswith('.pdf'): 42 | article_text = extract_text_from_pdf(article_file_path) 43 | elif article_file_path.lower().endswith('.txt'): 44 | with open(article_file_path, 'r', encoding="utf8") as article_file: 45 | article_text = article_file.read() 46 | else: 47 | print(f"{Fore.RED}Unsupported file format. Please provide a TXT, DOCX, or PDF file.") 48 | return 49 | 50 | # Sanitize the input text to remove non-BMP characters 51 | article_text = sanitize_text_for_chromedriver(article_text) 52 | 53 | # Split the article into chunks based on user choice 54 | if use_nltk: 55 | article_chunks = split_text_preserve_sentences(article_text, 250) 56 | else: 57 | words = article_text.split() 58 | chunk_size = 250 59 | article_chunks = [' '.join(words[i:i + chunk_size]) for i in range(0, len(words), chunk_size)] 60 | 61 | if not article_chunks: 62 | print(f"{Fore.RED}Your file is empty.") 63 | return 64 | 65 | # Initialize fake user agent generator 66 | ua = UserAgent() 67 | 68 | # Process each chunk 69 | while article_chunks: 70 | fake_user_agent = ua.random 71 | driver = initialize_driver(fake_user_agent) 72 | 73 | try: 74 | # Generate a Gmail variation and sign in 75 | email_variant = generate_gmail_variation(base_email) 76 | automate_sign_in(driver, email_variant, base_email) 77 | 78 | # Wait for confirmation email and process link 79 | creds = authenticate_gmail() 80 | service = get_gmail_service(creds) 81 | confirmation_email = wait_for_confirmation_email(service) 82 | 83 | if confirmation_email: 84 | message_body = get_message_body(confirmation_email) 85 | verify_link = extract_verify_link(message_body) 86 | if verify_link: 87 | success = process_confirmation_link(driver, verify_link, service, email_variant) 88 | if not success: 89 | print(f"{Fore.RED}Verification failed. Retrying with a new account.") 90 | continue # Retry with a new email 91 | else: 92 | print(f"{Fore.RED}Verify link not found in email.") 93 | continue # Retry with a new email 94 | else: 95 | print(f"{Fore.RED}No confirmation email found.") 96 | continue # Retry with a new email 97 | 98 | # Retry current chunk until successfully paraphrased 99 | while True: 100 | try: 101 | chunk = article_chunks[0] # Keep the current chunk until success 102 | driver.get('https://undetectable.ai/ai-humanizer') 103 | 104 | # Remove the banner if it exists 105 | try: 106 | banner = WebDriverWait(driver, 10).until( 107 | EC.presence_of_element_located((By.CLASS_NAME, 'iubenda-cs-content')) 108 | ) 109 | if banner: 110 | print(f"{Fore.YELLOW}Banner detected. Removing it.") 111 | driver.execute_script("document.querySelector('.iubenda-cs-content').remove();") 112 | except TimeoutException: 113 | # Banner not present, continue normally 114 | pass 115 | 116 | wait = WebDriverWait(driver, 10) 117 | 118 | readability = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="scrollElement"]/div/div/div[1]/div[1]/div/div[1]/div[1]/select'))) 119 | purpose = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="scrollElement"]/div/div/div[1]/div[1]/div/div[1]/div[2]/select'))) 120 | 121 | readability_select = Select(readability) 122 | purpose_select = Select(purpose) 123 | 124 | readability_select.select_by_visible_text(readability_choice) 125 | time.sleep(0.3) 126 | purpose_select.select_by_visible_text(purpose_choice) 127 | time.sleep(0.3) 128 | 129 | textarea = driver.find_element(By.CSS_SELECTOR, 'textarea[aria-label="input-detector-textarea"]') 130 | textarea.clear() 131 | # Sanitize the chunk again before sending to textarea 132 | sanitized_chunk = sanitize_text_for_chromedriver(chunk) 133 | textarea.send_keys(sanitized_chunk) 134 | 135 | time.sleep(0.5) 136 | 137 | try: 138 | terms = wait.until(EC.element_to_be_clickable((By.ID, 'terms-tooltip'))) 139 | driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", terms) 140 | driver.execute_script("arguments[0].click();", terms) 141 | except StaleElementReferenceException: 142 | print("Terms box became stale, retrying...") 143 | terms = driver.find_element(By.ID, 'terms-tooltip') 144 | ActionChains(driver).move_to_element(terms).click().perform() 145 | 146 | time.sleep(0.3) 147 | 148 | try: 149 | tone_element = WebDriverWait(driver, 10).until( 150 | EC.presence_of_element_located((By.XPATH, tone_xpath[tone_choice])) 151 | ) 152 | driver.execute_script("arguments[0].click();", tone_element) 153 | print(f"{Fore.GREEN}Selected tone: {tone_choice.replace('_', ' ').title()}") 154 | except Exception as e: 155 | print(f"{Fore.RED}Error selecting tone: {e}. Defaulting to More Human.") 156 | 157 | humanize = driver.find_element(By.ID, 'detector-humanize-btn') 158 | driver.execute_script("arguments[0].click();", humanize) 159 | 160 | # Check for the "not enough words" popup 161 | try: 162 | popup_present = WebDriverWait(driver, 10).until( 163 | EC.presence_of_element_located((By.XPATH, "//button[@aria-label='View Paid Plans']")) 164 | ) 165 | if popup_present: 166 | print(f"{Fore.YELLOW}Not enough words left. Switching to a new email session.") 167 | raise Exception("Switch to new email session") 168 | except TimeoutException: 169 | # Popup did not appear, proceed normally 170 | pass 171 | 172 | # Successfully paraphrased the chunk 173 | paraphrased = WebDriverWait(driver, 60).until(EC.element_to_be_clickable((By.ID, 'document-copy-to-clipboard-btn'))) 174 | paraphrased.click() 175 | 176 | copied_content = pyperclip.paste() 177 | # Save in same format as input 178 | if save_same_format: 179 | if article_file_path.lower().endswith('.docx'): 180 | save_as_docx(article_file_path, copied_content) 181 | elif article_file_path.lower().endswith('.pdf'): 182 | save_as_pdf(article_file_path, copied_content) 183 | else: 184 | save_as_txt(article_file_path, copied_content) 185 | else: 186 | save_as_txt('paraphrased.txt', copied_content) 187 | 188 | # Remove the successfully paraphrased chunk 189 | article_chunks.pop(0) 190 | print(f"{Fore.GREEN}Successfully paraphrased chunk. Moving to the next one.") 191 | break # Exit the loop to process the next chunk 192 | 193 | except Exception as e: 194 | print(f"{Fore.RED}Error paraphrasing chunk: {e}. Retrying with a new email.") 195 | break # Exit the loop to start a new email session 196 | 197 | except Exception as e: 198 | print(f"{Fore.RED}Error during chunk processing: {e}") 199 | finally: 200 | if driver: 201 | driver.quit() 202 | 203 | 204 | print(f"{Fore.GREEN}\nArticle has been paraphrased successfully.") 205 | notification.notify( 206 | title="Paraphrasing Complete", 207 | message="Your file has been successfully paraphrased.", 208 | timeout=5 209 | ) 210 | 211 | except Exception as e: 212 | print(f"{Fore.RED}Error during processing: {e}") 213 | 214 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt6.QtWidgets import ( 3 | QApplication, QWidget, QGridLayout, QVBoxLayout, QPushButton, QLabel, QLineEdit, QComboBox, QFileDialog, QMessageBox, QCheckBox, QTabWidget, QTextEdit, QListWidget, QHBoxLayout 4 | ) 5 | from PyQt6.QtGui import QFont, QDragEnterEvent, QDropEvent, QPainter, QColor 6 | from PyQt6.QtCore import Qt, QDir, QThread, pyqtSignal, QRect 7 | from paraphraser import main 8 | from ai_scanner import scan_text 9 | 10 | class CircularProgress(QWidget): 11 | def __init__(self, ai_percentage=0, human_percentage=100, *args, **kwargs): 12 | super().__init__(*args, **kwargs) 13 | self.ai_percentage = ai_percentage 14 | self.human_percentage = human_percentage 15 | self.is_scanned = False # Flag to check if the scan has been performed 16 | self.setMinimumSize(50, 50) # Set smaller size for the circle 17 | 18 | def setPercentages(self, ai_percentage, human_percentage): 19 | self.ai_percentage = ai_percentage 20 | self.human_percentage = human_percentage 21 | self.is_scanned = True # Mark the scan as complete 22 | self.update() # Trigger a repaint to update the circle 23 | 24 | def paintEvent(self, event): 25 | painter = QPainter(self) 26 | painter.setRenderHint(QPainter.RenderHint.Antialiasing) 27 | painter.setPen(Qt.PenStyle.NoPen) 28 | 29 | # Drawing the outer circle 30 | rect = self.rect().adjusted(5, 5, -5, -5) # Adjust for better circle size 31 | painter.setBrush(QColor(255, 255, 255)) # White background 32 | painter.drawEllipse(rect) 33 | 34 | # Draw the AI part (red) 35 | painter.setBrush(QColor(255, 0, 0)) # Red for AI 36 | ai_angle = int((self.ai_percentage * 360 / 100)) # Convert angle to integer 37 | painter.drawPie(rect, 90 * 16, ai_angle * 16) # Angle is in 1/16ths of a degree 38 | 39 | # Draw the Human part (green) 40 | painter.setBrush(QColor(0, 255, 0)) # Green for Human 41 | human_angle = int((self.human_percentage * 360 / 100)) # Convert angle to integer 42 | painter.drawPie(rect, (90 + ai_angle) * 16, human_angle * 16) # Angle is in 1/16ths of a degree 43 | 44 | # Only display percentage text if the scan has completed 45 | if self.is_scanned: 46 | painter.setPen(Qt.GlobalColor.black) # Text color (black) 47 | painter.setFont(QFont('Arial', 10, QFont.Weight.Bold)) 48 | painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, f"{self.ai_percentage}% AI\n{self.human_percentage}% Human") 49 | 50 | painter.end() 51 | 52 | class CircularProgressGrid(QWidget): 53 | def __init__(self, detectors, *args, **kwargs): 54 | super().__init__(*args, **kwargs) 55 | 56 | grid_layout = QGridLayout() 57 | grid_layout.setSpacing(20) # Adjust the spacing between items 58 | 59 | row = 0 60 | col = 0 61 | self.progress_bars = {} 62 | 63 | for detector in detectors: 64 | progress_bar = CircularProgress() 65 | self.progress_bars[detector] = progress_bar 66 | grid_layout.addWidget(progress_bar, row, col) 67 | 68 | col += 1 69 | if col > 2: # After 3 columns, move to the next row 70 | col = 0 71 | row += 1 72 | 73 | self.setLayout(grid_layout) 74 | 75 | class APICaptureThread(QThread): 76 | current_email = pyqtSignal(str) # Signal to update the current email being processed 77 | api_grabbed = pyqtSignal(str) # Signal to add to the API results list 78 | result = pyqtSignal(dict) # Signal to send the final result or errors 79 | 80 | def run(self): 81 | try: 82 | from api_grabber import main 83 | 84 | result_message = main( 85 | update_current_email=lambda email: self.current_email.emit(email), 86 | update_result=lambda result: self.api_grabbed.emit(result) 87 | ) 88 | 89 | # Check if the result_message indicates an error 90 | if "does not exist" in result_message or "No new emails" in result_message: 91 | self.result.emit({"success": False, "message": result_message}) 92 | else: 93 | self.result.emit({"success": True, "message": result_message}) 94 | except Exception as e: 95 | self.result.emit({"success": False, "message": str(e)}) 96 | 97 | class ParaphrasingApp(QWidget): 98 | def __init__(self): 99 | super().__init__() 100 | self.resultLabels = {} # This will hold references to CircularProgress widgets 101 | self.detectorLabels = {} # To hold labels for each detector 102 | self.initUI() 103 | 104 | def initUI(self): 105 | self.setWindowTitle('ParaGenie V2.0') 106 | self.setGeometry(300, 300, 800, 600) 107 | 108 | self.setStyleSheet(""" 109 | QWidget { 110 | background-color: #2E2E2E; 111 | color: #FFFFFF; 112 | } 113 | QLabel { 114 | font-size: 14px; 115 | font-weight: bold; 116 | } 117 | QPushButton { 118 | background-color: #4CAF50; 119 | border: none; 120 | padding: 10px; 121 | border-radius: 5px; 122 | font-size: 14px; 123 | } 124 | QPushButton:hover { 125 | background-color: #66BB6A; 126 | } 127 | QLineEdit, QComboBox, QTextEdit, QListWidget { 128 | padding: 5px; 129 | border: 1px solid #555; 130 | border-radius: 4px; 131 | background-color: #333; 132 | color: #DDD; 133 | } 134 | """) 135 | 136 | self.tabs = QTabWidget() 137 | 138 | self.paraphraserTab = self.createParaphraserTab() 139 | self.aiScannerTab = self.createAIScannerTab() 140 | self.apiGrabberTab = self.createAPIGrabberTab() 141 | 142 | self.tabs.addTab(self.paraphraserTab, "Paraphraser") 143 | self.tabs.addTab(self.aiScannerTab, "AI Scanner") 144 | self.tabs.addTab(self.apiGrabberTab, "API Grabber") 145 | 146 | mainLayout = QVBoxLayout() 147 | mainLayout.addWidget(self.tabs) 148 | self.setLayout(mainLayout) 149 | 150 | def createParaphraserTab(self): 151 | tab = QWidget() 152 | layout = QVBoxLayout() 153 | layout.setSpacing(10) 154 | 155 | titleLabel = QLabel('ParaGenie') 156 | titleLabel.setFont(QFont('Orbitron', 29)) 157 | titleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) 158 | titleLabel.setStyleSheet(""" 159 | QLabel { 160 | color: qlineargradient( 161 | spread: pad, 162 | x1: 0, y1: 0, 163 | x2: 1, y2: 0, 164 | stop: 0 #00c6ff, 165 | stop: 1 #0072ff 166 | ); 167 | font-weight: bold; 168 | font-size: 32px; 169 | padding: 20px 0; 170 | } 171 | """) 172 | 173 | purposeLabel = QLabel('Purpose of Writing:') 174 | readabilityLabel = QLabel('Readability Level:') 175 | toneLabel = QLabel('Tone:') 176 | filePathLabel = QLabel('Article File Path:') 177 | emailLabel = QLabel('Email Address:') 178 | 179 | self.purposeComboBox = QComboBox() # Now instance variable 180 | self.purposeComboBox.addItems(['General Writing', 'Essay', 'Article', 'Marketing Material', 'Story', 'Cover letter', 'Report', 'Business Material', 'Legal Material']) 181 | 182 | self.readabilityComboBox = QComboBox() # Now instance variable 183 | self.readabilityComboBox.addItems(['High School', 'University', 'Doctorate', 'Journalist', 'Marketing']) 184 | 185 | self.toneComboBox = QComboBox() # Now instance variable 186 | self.toneComboBox.addItems(['Balanced', 'More Human', 'More Readable']) 187 | 188 | self.filePathLineEdit = QLineEdit() # Now instance variable 189 | self.filePathLineEdit.setPlaceholderText("Drag and drop a file here or click 'Browse'") 190 | self.filePathLineEdit.setAcceptDrops(True) 191 | self.filePathLineEdit.dragEnterEvent = self.dragEnterEvent 192 | self.filePathLineEdit.dropEvent = self.dropEvent 193 | 194 | self.browseButton = QPushButton('Browse') # Now instance variable 195 | self.browseButton.clicked.connect(self.browseFile) 196 | 197 | self.emailLineEdit = QLineEdit() # Now instance variable 198 | self.emailLineEdit.setPlaceholderText('Enter your email address') 199 | 200 | self.useNltkCheckBox = QCheckBox('Use NLTK for splitting chunks') # Now instance variable 201 | self.useNltkCheckBox.setChecked(False) 202 | 203 | self.saveSameFormatCheckBox = QCheckBox('Save file in the same format') # Now instance variable 204 | self.saveSameFormatCheckBox.setChecked(False) 205 | 206 | self.startButton = QPushButton('Start Paraphrasing') # Now instance variable 207 | self.startButton.clicked.connect(self.startParaphrasing) 208 | 209 | layout.addWidget(titleLabel) 210 | layout.addWidget(purposeLabel) 211 | layout.addWidget(self.purposeComboBox) 212 | layout.addWidget(readabilityLabel) 213 | layout.addWidget(self.readabilityComboBox) 214 | layout.addWidget(toneLabel) 215 | layout.addWidget(self.toneComboBox) 216 | layout.addWidget(filePathLabel) 217 | layout.addWidget(self.filePathLineEdit) 218 | layout.addWidget(self.browseButton) 219 | layout.addWidget(emailLabel) 220 | layout.addWidget(self.emailLineEdit) 221 | layout.addWidget(self.useNltkCheckBox) 222 | layout.addWidget(self.saveSameFormatCheckBox) 223 | layout.addWidget(self.startButton) 224 | 225 | tab.setLayout(layout) 226 | return tab 227 | 228 | def createAIScannerTab(self): 229 | tab = QWidget() 230 | layout = QVBoxLayout() 231 | 232 | # 1. Text area for input 233 | self.textArea = QTextEdit() 234 | self.textArea.setPlaceholderText("Enter or paste text here...") 235 | layout.addWidget(self.textArea) 236 | 237 | # 2. Scan button 238 | scanButton = QPushButton('Scan') 239 | scanButton.clicked.connect(self.scanText) 240 | layout.addWidget(scanButton) 241 | 242 | # 3. Create CircularProgressGrid to hold the circular progress bars 243 | detectors = [ 244 | 'scoreGptZero', 'scoreOpenAI', 'scoreWriter', 'scoreCrossPlag', 'scoreCopyLeaks', 245 | 'scoreSapling', 'scoreContentAtScale', 'scoreZeroGPT' 246 | ] 247 | 248 | # Add labels and CircularProgress widgets 249 | grid_layout = QGridLayout() 250 | grid_layout.setSpacing(20) 251 | 252 | row = 0 253 | col = 0 254 | for i, detector in enumerate(detectors): 255 | label = QLabel(detector) 256 | progress_bar = CircularProgress(ai_percentage=0, human_percentage=100) 257 | self.resultLabels[detector] = progress_bar 258 | self.detectorLabels[detector] = label # Store label reference 259 | grid_layout.addWidget(label, row, col) 260 | grid_layout.addWidget(progress_bar, row+1, col) 261 | 262 | col += 1 263 | if col > 2: # After 3 columns, move to the next row 264 | col = 0 265 | row += 2 # Skip one row for the label 266 | 267 | layout.addLayout(grid_layout) 268 | 269 | # Set the layout for the tab 270 | tab.setLayout(layout) 271 | return tab 272 | 273 | def scanText(self): 274 | text = self.textArea.toPlainText().strip() 275 | 276 | if not text: 277 | QMessageBox.warning(self, 'Input Error', 'Please enter text to scan.') 278 | return 279 | 280 | api_key = "0e3640c3-7516-4c20-acb7-3fbfef5c1b3a" 281 | 282 | # Run the AI scanner 283 | detection_response = scan_text(api_key, text) 284 | 285 | if detection_response is None: 286 | QMessageBox.critical(self, 'Error', 'Failed to detect AI in the text.') 287 | return 288 | 289 | ai_percentage, detector_results = detection_response # Assuming scan_text returns this properly 290 | 291 | # Update the results in the GUI 292 | for detector, result in detector_results.items(): 293 | ai_percentage = result.get('ai_percentage', 0) 294 | human_percentage = result.get('human_percentage', 100) 295 | 296 | # Ensure the CircularProgress widget exists before trying to update it 297 | if detector in self.resultLabels: 298 | self.resultLabels[detector].setPercentages(ai_percentage, human_percentage) 299 | 300 | # Add final result label after the scan 301 | final_result_label = QLabel(f"The AI detection result: {ai_percentage}% AI content detected in the text.") 302 | final_result_label.setStyleSheet("color: red;" if ai_percentage > 50 else "color: green;") 303 | self.tabs.widget(1).layout().addWidget(final_result_label) # Add to the AI scanner tab layout 304 | 305 | def createAPIGrabberTab(self): 306 | tab = QWidget() 307 | layout = QVBoxLayout() 308 | 309 | currentEmailLabel = QLabel("Current Email:") 310 | currentEmailLabel.setStyleSheet("color: #00FF00; font-weight: bold;") 311 | grabbedAPIsList = QListWidget() 312 | 313 | grabButton = QPushButton('Grab') 314 | grabButton.clicked.connect(lambda: self.runAPICapture(currentEmailLabel, grabbedAPIsList)) 315 | 316 | layout.addWidget(currentEmailLabel) 317 | layout.addWidget(grabbedAPIsList) 318 | layout.addWidget(grabButton) 319 | tab.setLayout(layout) 320 | return tab 321 | 322 | def browseFile(self): 323 | current_directory = QDir.currentPath() 324 | fname, _ = QFileDialog.getOpenFileName( 325 | self, 326 | 'Open file', 327 | current_directory, 328 | 'Text Files (*.txt);;Word Documents (*.docx);;PDF Files (*.pdf)' 329 | ) 330 | self.filePathLineEdit.setText(fname) 331 | 332 | def dragEnterEvent(self, event: QDragEnterEvent): 333 | if event.mimeData().hasUrls(): 334 | urls = event.mimeData().urls() 335 | if any(url.toLocalFile().lower().endswith(('.txt', '.docx', '.pdf')) for url in urls): 336 | event.acceptProposedAction() 337 | 338 | def dropEvent(self, event: QDropEvent): 339 | urls = event.mimeData().urls() 340 | if urls: 341 | file_path = urls[0].toLocalFile() 342 | self.filePathLineEdit.setText(file_path) 343 | 344 | def startParaphrasing(self): 345 | purpose_choice = self.purposeComboBox.currentText() 346 | readability_choice = self.readabilityComboBox.currentText() 347 | tone_choice = self.toneComboBox.currentText().upper().replace(' ', '_') 348 | article_file_path = self.filePathLineEdit.text() 349 | email_address = self.emailLineEdit.text() 350 | use_nltk = self.useNltkCheckBox.isChecked() 351 | save_same_format = self.saveSameFormatCheckBox.isChecked() 352 | 353 | if not email_address: 354 | QMessageBox.warning(self, 'Input Error', 'Please enter an email address.') 355 | return 356 | 357 | try: 358 | main(purpose_choice, readability_choice, article_file_path, email_address, use_nltk, save_same_format, tone_choice) 359 | QMessageBox.information(self, 'Success', 'Article has been paraphrased successfully.') 360 | except Exception as e: 361 | QMessageBox.critical(self, 'Error', f'An error occurred: {e}') 362 | 363 | def runAPICapture(self, currentEmailLabel, grabbedAPIsList): 364 | def updateCurrentEmail(email): 365 | # Update the current email label with the email being processed 366 | currentEmailLabel.setText(f"Current Email: {email}") 367 | 368 | def addGrabbedAPI(api_entry): 369 | # Append the grabbed API or failure message to the list widget 370 | grabbedAPIsList.addItem(api_entry) 371 | 372 | def handleResult(result): 373 | if result["success"]: 374 | QMessageBox.information(self, "Success", "API grabbing completed successfully.") 375 | else: 376 | QMessageBox.warning(self, "Error", result["message"]) # Show the error message in a warning box 377 | 378 | # Start the API capture thread 379 | self.apiThread = APICaptureThread() 380 | self.apiThread.current_email.connect(updateCurrentEmail) # Update the current email label 381 | self.apiThread.api_grabbed.connect(addGrabbedAPI) # Append grabbed APIs to the list 382 | self.apiThread.result.connect(handleResult) # Handle the final result 383 | self.apiThread.start() 384 | 385 | def run_app(): 386 | app = QApplication(sys.argv) 387 | ex = ParaphrasingApp() 388 | ex.show() 389 | sys.exit(app.exec()) 390 | 391 | if __name__ == '__main__': 392 | run_app() 393 | --------------------------------------------------------------------------------