1667 lines
69 KiB
Python
1667 lines
69 KiB
Python
import json
|
||
import shutil
|
||
import socket
|
||
import sys
|
||
import threading
|
||
from multiprocessing import freeze_support
|
||
from DrissionPage import ChromiumOptions, ChromiumPage
|
||
from DrissionPage.errors import *
|
||
import random
|
||
import time
|
||
import os
|
||
from filelock import FileLock
|
||
from openpyxl import load_workbook
|
||
import ctypes
|
||
from ctypes import wintypes
|
||
import uuid
|
||
import multiprocessing
|
||
import tkinter as tk
|
||
from tkinter import filedialog, messagebox, ttk
|
||
import string
|
||
from multiprocessing import Queue, Pool
|
||
from account import AccountManagerSQLite
|
||
from typing import List, Tuple
|
||
import win32gui
|
||
from mail import GoogleCodeReceiver, EmailClient
|
||
from proxy import ProxyManagerSQLite, classifier_smartproxy
|
||
from datetime import datetime, timedelta, timezone
|
||
|
||
# Windows 消息常量
|
||
WM_MOUSEMOVE = 0x0200
|
||
WM_LBUTTONDOWN = 0x0201
|
||
WM_LBUTTONUP = 0x0202
|
||
MK_LBUTTON = 0x0001
|
||
|
||
server = "server-10474.cuiqiu.vip" # 替换为你的 IMAP 服务器地址
|
||
username = "gmailvinted@mailezu.com"
|
||
password = "g1l2o0hld84"
|
||
|
||
# 停止标志
|
||
stop_event = threading.Event()
|
||
|
||
user32 = ctypes.windll.user32
|
||
|
||
root_plugin_dir = os.path.join(os.getcwd(), 'proxy_auth_plugin')
|
||
|
||
proxy_manager = ProxyManagerSQLite(db_path="proxies.db")
|
||
db_manager = AccountManagerSQLite(db_path="accounts.db")
|
||
|
||
|
||
def init_worker():
|
||
global proxy_manager, db_manager
|
||
# 在每个子进程中初始化数据库管理器
|
||
proxy_manager = ProxyManagerSQLite(db_path="proxies.db")
|
||
db_manager = AccountManagerSQLite(db_path="accounts.db")
|
||
|
||
|
||
# 全局写入锁
|
||
write_lock = multiprocessing.Lock()
|
||
|
||
ua_templates = [
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:95.0) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}",
|
||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/{webkit_version} (KHTML, like Gecko) Chrome/{chrome_version} Safari/{safari_version}"
|
||
]
|
||
|
||
# 自定义异常
|
||
class WindowMinimizedError(Exception):
|
||
def __init__(self, message="浏览器窗口已最小化,无法执行操作。"):
|
||
self.message = message
|
||
super().__init__(self.message)
|
||
|
||
class GoogleCaptchaError(Exception):
|
||
"""出现谷歌文字验证错误"""
|
||
pass
|
||
|
||
def is_window_minimized(hwnd):
|
||
"""检查窗口是否最小化"""
|
||
return win32gui.IsIconic(hwnd)
|
||
|
||
|
||
def generate_user_agent():
|
||
# 随机选择一个Chrome的UA模板
|
||
selected_template = random.choice(ua_templates)
|
||
|
||
# 随机生成 WebKit、Safari 和 Chrome 版本号
|
||
webkit_version = f"{random.randint(500, 599)}.{random.randint(0, 99)}"
|
||
safari_version = f"{random.randint(500, 599)}.{random.randint(0, 99)}"
|
||
chrome_version = f"{random.randint(40, 99)}.{random.randint(0, 2999)}.{random.randint(0, 99)}"
|
||
|
||
# 填充模板中的动态部分并生成完整的UA
|
||
user_agent = selected_template.format(
|
||
webkit_version=webkit_version,
|
||
safari_version=safari_version,
|
||
chrome_version=chrome_version
|
||
)
|
||
|
||
return user_agent
|
||
|
||
|
||
def random_sleep(tab, min_seconds=1, max_seconds=3):
|
||
"""在 min_seconds 和 max_seconds 之间随机停顿"""
|
||
tab.wait(random.uniform(min_seconds, max_seconds))
|
||
|
||
|
||
|
||
def get_free_port():
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
s.bind(('localhost', 0)) # 使用端口0让操作系统自动选择一个未占用的端口
|
||
port = s.getsockname()[1]
|
||
s.close()
|
||
return port
|
||
# 设置代理ip
|
||
|
||
def create_proxy_auth_extension(proxy_host, proxy_port, proxy_username, proxy_password, scheme='PROXY', proxy_domains=None):
|
||
# Define a root directory
|
||
root_plugin_dir = os.path.join(os.getcwd(), 'proxy_auth_plugin')
|
||
if not os.path.exists(root_plugin_dir):
|
||
os.makedirs(root_plugin_dir) # Create root directory
|
||
|
||
# Use a unique identifier to generate a separate plugin directory
|
||
plugin_path = os.path.join(root_plugin_dir, f'plugin_{uuid.uuid4().hex}')
|
||
os.makedirs(plugin_path) # Create plugin directory
|
||
|
||
# Contents of manifest.json for the plugin
|
||
manifest_json = """
|
||
{
|
||
"version": "1.0.0",
|
||
"manifest_version": 2,
|
||
"name": "Proxy Auth Extension",
|
||
"permissions": [
|
||
"proxy",
|
||
"tabs",
|
||
"unlimitedStorage",
|
||
"storage",
|
||
"<all_urls>",
|
||
"webRequest",
|
||
"webRequestBlocking"
|
||
],
|
||
"background": {
|
||
"scripts": ["background.js"]
|
||
},
|
||
"minimum_chrome_version":"22.0.0"
|
||
}
|
||
"""
|
||
|
||
# Ensure proxy_domains is a list
|
||
if proxy_domains is None:
|
||
proxy_domains = []
|
||
|
||
# Generate domains_list for PAC script
|
||
# Since we can only match on hostnames, we use the domain names
|
||
domains_list = ', '.join('"{}"'.format(domain) for domain in proxy_domains)
|
||
|
||
# Generate PAC script
|
||
pac_script = string.Template(
|
||
'''
|
||
function FindProxyForURL(url, host) {
|
||
var proxy = "${scheme} ${proxy_host}:${proxy_port}";
|
||
var direct = "DIRECT";
|
||
|
||
var domains = [${domains_list}];
|
||
|
||
for (var i = 0; i < domains.length; i++) {
|
||
if (dnsDomainIs(host, domains[i])) {
|
||
return proxy;
|
||
}
|
||
}
|
||
return direct;
|
||
}
|
||
'''
|
||
).substitute(
|
||
scheme=scheme.upper(),
|
||
proxy_host=proxy_host,
|
||
proxy_port=proxy_port,
|
||
domains_list=domains_list
|
||
)
|
||
|
||
# Since we cannot inspect the full URL in onAuthRequired, we'll listen to all URLs
|
||
# But we'll check if the proxy is challenging for authentication
|
||
# We can do this by checking the 'isProxy' flag in the details
|
||
# However, 'isProxy' is only available in certain contexts
|
||
# Alternatively, we can restrict the listener to the proxy server
|
||
|
||
# Contents of background.js, using string templates to insert dynamic configuration
|
||
background_js = string.Template(
|
||
'''
|
||
var config = {
|
||
mode: "pac_script",
|
||
pacScript: {
|
||
data: $pac_script
|
||
}
|
||
};
|
||
|
||
chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
|
||
|
||
function callbackFn(details) {
|
||
if (details.challenger && details.challenger.host === "${proxy_host}" && details.challenger.port == ${proxy_port}) {
|
||
return {
|
||
authCredentials: {
|
||
username: "${username}",
|
||
password: "${password}"
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
chrome.webRequest.onAuthRequired.addListener(
|
||
callbackFn,
|
||
{urls: ["<all_urls>"]},
|
||
['blocking']
|
||
);
|
||
'''
|
||
).substitute(
|
||
pac_script=json.dumps(pac_script),
|
||
username=proxy_username,
|
||
password=proxy_password,
|
||
proxy_host=proxy_host,
|
||
proxy_port=proxy_port
|
||
)
|
||
|
||
|
||
# Write manifest.json and background.js to the plugin directory
|
||
with open(os.path.join(plugin_path, 'manifest.json'), 'w') as f:
|
||
f.write(manifest_json)
|
||
|
||
with open(os.path.join(plugin_path, 'background.js'), 'w') as f:
|
||
f.write(background_js)
|
||
|
||
# Return the unique plugin path
|
||
return plugin_path
|
||
|
||
|
||
def delete_folder(file_path):
|
||
"""
|
||
删除指定的代理插件文件夹及其内容
|
||
:param plugin_path: 代理插件文件夹路径
|
||
"""
|
||
if os.path.exists(file_path):
|
||
shutil.rmtree(file_path)
|
||
|
||
|
||
# 获取窗口标题
|
||
def get_window_title(hwnd):
|
||
length = user32.GetWindowTextLengthW(hwnd)
|
||
if length > 0:
|
||
buffer = ctypes.create_unicode_buffer(length + 1)
|
||
user32.GetWindowTextW(hwnd, buffer, length + 1)
|
||
return buffer.value
|
||
return ""
|
||
|
||
|
||
# 枚举所有窗口
|
||
def enum_windows():
|
||
windows = []
|
||
|
||
def callback(hwnd, lparam):
|
||
title = get_window_title(hwnd)
|
||
if title and "Google Chrome" in title:
|
||
windows.append((hwnd, title))
|
||
return True
|
||
|
||
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, ctypes.c_void_p)
|
||
user32.EnumWindows(EnumWindowsProc(callback), 0)
|
||
return windows
|
||
|
||
|
||
def find_gmail_window(windows, target_title):
|
||
for hwnd, title in windows:
|
||
if target_title in title: # 判断目标标题是否包含在窗口标题中
|
||
return hwnd
|
||
return None
|
||
|
||
|
||
# 模拟鼠标事件
|
||
def simulate_mouse(hwnd, x, y):
|
||
lParam = (y << 16) | x
|
||
user32.PostMessageW(hwnd, WM_MOUSEMOVE, 0, lParam)
|
||
user32.PostMessageW(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, lParam)
|
||
user32.PostMessageW(hwnd, WM_LBUTTONUP, 0, lParam)
|
||
|
||
|
||
def click_the_login_button(tab, login_a):
|
||
# 获取href属性
|
||
href = login_a.attr('href')
|
||
# 访问href属性指向的页面
|
||
tab.get(href)
|
||
|
||
|
||
def input_email(tab, hwnd, email, email_input, max_retries=3):
|
||
"""
|
||
输入账号并点击“下一步”按钮。如果两次尝试都未成功,退出。
|
||
:param tab: 浏览器对象
|
||
:param email: 用户的邮箱账号
|
||
:param email_input: 输入框对象
|
||
:param max_retries: 最大尝试次数
|
||
"""
|
||
if is_window_minimized(hwnd):
|
||
raise WindowMinimizedError("浏览器窗口已最小化,无法执行操作。")
|
||
|
||
email_input.clear()
|
||
|
||
# 模拟人类输入,每次输入一个字符,并随机延迟
|
||
for char in email:
|
||
email_input.input(char) # 输入一个字符
|
||
tab.wait(random.uniform(0.1, 0.3)) # 随机延迟 0.1 到 0.3 秒
|
||
|
||
tab.wait(1)
|
||
|
||
next_input = tab.ele('@type=button', timeout=15, index=3)
|
||
|
||
# 将按钮拖拽到页面顶部位置
|
||
next_input.drag_to((0, 0), 0)
|
||
next_input.set.style('position', 'absolute')
|
||
next_input.set.style('top', '0')
|
||
next_input.set.style('left', '0')
|
||
next_input.set.style('width', '1000px')
|
||
next_input.set.style('height', '1000px')
|
||
|
||
tab.wait(2)
|
||
|
||
retry_count = 0
|
||
while retry_count < max_retries:
|
||
# 模拟点击操作
|
||
simulate_mouse(hwnd, 650, 650)
|
||
|
||
# 等待页面加载开始
|
||
tab.wait.load_start()
|
||
|
||
# 等待7秒,看按钮是否消失
|
||
flag = tab.wait.ele_deleted(next_input, timeout=7)
|
||
if flag:
|
||
print(f"第 {retry_count + 1} 次尝试成功,按钮已消失。")
|
||
return True
|
||
else:
|
||
retry_count += 1 # 增加尝试次数
|
||
print(f"第 {retry_count} 次尝试失败,按钮未消失,重新尝试...")
|
||
|
||
if retry_count >= max_retries:
|
||
# 两次尝试都失败,调整按钮大小为10px
|
||
next_input.set.style('width', '10px')
|
||
next_input.set.style('height', '10px')
|
||
print(f"尝试 {max_retries} 次仍未成功,调整按钮大小并退出。")
|
||
raise GoogleCaptchaError("出现谷歌文字验证")
|
||
|
||
|
||
# 点击重试按钮
|
||
def recover(tab, recover_a):
|
||
recover_a.click(by_js=True)
|
||
|
||
|
||
def input_password(tab, hwnd, password, password_input, max_retries=2):
|
||
# 检查窗口是否最小化
|
||
if is_window_minimized(hwnd):
|
||
raise WindowMinimizedError("浏览器窗口已最小化,无法执行操作。")
|
||
|
||
password_input.clear()
|
||
"""
|
||
输入密码并点击“下一步”按钮
|
||
:param tab: 浏览器对象
|
||
:param password: 用户的密码
|
||
"""
|
||
# 模拟人类输入密码,每次输入一个字符,并随机延迟
|
||
for char in password:
|
||
password_input.input(char) # 输入一个字符
|
||
tab.wait(random.uniform(0.1, 0.3)) # 随机延迟 0.1 到 0.3 秒
|
||
|
||
random_sleep(tab)
|
||
|
||
next_input = tab.ele('@type=button', timeout=15, index=2)
|
||
|
||
# 将按钮拖拽到页面顶部位置
|
||
next_input.drag_to((0, 0), 0)
|
||
next_input.set.style('position', 'absolute')
|
||
next_input.set.style('top', '0')
|
||
next_input.set.style('left', '0')
|
||
next_input.set.style('width', '1000px')
|
||
next_input.set.style('height', '1000px')
|
||
|
||
tab.wait(2)
|
||
retry_count = 0
|
||
while retry_count < max_retries:
|
||
# 模拟点击操作
|
||
simulate_mouse(hwnd, 700, 700)
|
||
|
||
# 等待页面加载开始
|
||
tab.wait.load_start()
|
||
|
||
# 等待7秒,看按钮是否消失
|
||
flag = tab.wait.ele_deleted(next_input, timeout=7)
|
||
if flag:
|
||
print(f"第 {retry_count + 1} 次尝试成功,按钮已消失。")
|
||
break # 按钮成功消失,跳出循环
|
||
else:
|
||
# 获取页面提示信息
|
||
text = ""
|
||
try:
|
||
text = tab.ele('@aria-live=polite', timeout=1).text
|
||
except Exception as e:
|
||
print("获取页面提示失败:", e)
|
||
|
||
print(f"页面提示: {text}")
|
||
|
||
if text == "":
|
||
retry_count += 1
|
||
print(f"页面提示为空,第 {retry_count} 次尝试失败,重新尝试...")
|
||
else:
|
||
print(f"页面提示不为空:{text}, 不进行重试,退出。")
|
||
break # 页面提示不为空,退出重试流程
|
||
|
||
if retry_count >= max_retries:
|
||
# 两次尝试都失败,调整按钮大小为10px
|
||
next_input.set.style('width', '10px')
|
||
next_input.set.style('height', '10px')
|
||
print(f"尝试 {max_retries} 次仍未成功,调整按钮大小并退出。")
|
||
raise GoogleCaptchaError("输入密码后无法点击下一步")
|
||
|
||
|
||
|
||
|
||
def click_alternate_email_verification_button(tab, email_div):
|
||
email_div.click(by_js=True)
|
||
|
||
tab.wait.load_start()
|
||
|
||
|
||
def input_recovery_code(tab, recovery_code):
|
||
"""
|
||
输入辅助邮箱的验证码并点击“下一步”按钮
|
||
:param tab: 浏览器对象
|
||
:param recovery_code: 辅助邮箱的验证码
|
||
"""
|
||
# 第一步:通过 class 定位到辅助邮箱的输入框
|
||
recovery_input = tab.ele('@aria-label=输入辅助邮箱地址', timeout=15)
|
||
|
||
for char in recovery_code:
|
||
recovery_input.input(char) # 输入一个字符
|
||
tab.wait(random.uniform(0.1, 0.3)) # 随机延迟
|
||
|
||
next_input = tab.ele('@@tag()=span@@text()=下一步', timeout=15)
|
||
next_input.click(by_js=True)
|
||
|
||
print("到这里我已经点完下一步,进入修改辅助邮箱的地方了")
|
||
|
||
tab.wait.load_start()
|
||
|
||
tab.wait(1)
|
||
|
||
retry = tab.ele("@text()=您输入的电子邮件地址不正确,请重试。",timeout=15)
|
||
if retry:
|
||
return True
|
||
|
||
return False
|
||
|
||
|
||
# 找到修改邮箱的按钮
|
||
def click_use_other_account_button2(tab, next_span):
|
||
next_span.click(by_js=True)
|
||
|
||
tab.wait.load_start()
|
||
|
||
|
||
# 修改辅助邮箱账号
|
||
def modify_the_secondary_email1(tab, button, auxiliary_email_account):
|
||
button.click(by_js=True)
|
||
tab.wait(2)
|
||
input = button
|
||
input.clear()
|
||
for char in auxiliary_email_account:
|
||
input.input(char) # Enter one character at a time
|
||
tab.wait(random.uniform(0.1, 0.3)) # Random delay between 0.1 and 0.3 seconds
|
||
|
||
# 点击保存
|
||
save_span = tab.ele('@text()=Save', timeout=15)
|
||
save_span.click(by_js=True)
|
||
|
||
button = tab.ele('@text()=Not now',timeout=15)
|
||
button.click(by_js=True)
|
||
tab.wait.load_start()
|
||
|
||
|
||
def click_continue_button(tab, continue_div1):
|
||
# 点击出现的弹窗
|
||
tab.handle_alert(accept=True)
|
||
tab.wait(1)
|
||
continue_div1.click(by_js=True)
|
||
tab.wait(1)
|
||
next_button = tab.ele('@@tag()=button@@name=data_consent_dialog_next', timeout=15)
|
||
next_button.click(by_js=True)
|
||
tab.wait(1)
|
||
continue_div2 = tab.ele('@text()=Personalize other Google products with my Gmail, Chat, and Meet data', timeout=15)
|
||
continue_div2.click(by_js=True)
|
||
tab.wait(1)
|
||
Done_button = tab.ele('@name=data_consent_dialog_done', timeout=15)
|
||
Done_button.click(by_js=True)
|
||
tab.wait(15)
|
||
Reload = tab.ele('@text()=Reload', timeout=15)
|
||
if Reload:
|
||
Reload.click(by_js=True)
|
||
|
||
|
||
def find_and_visit_href2(tab):
|
||
password_a = tab.ele('@aria-label=Password', timeout=15)
|
||
print(password_a)
|
||
href = password_a.attr('href')
|
||
tab.get(href)
|
||
|
||
|
||
def input_password_new(tab, password):
|
||
"""
|
||
输入密码并点击“下一步”按钮
|
||
:param tab: 浏览器对象
|
||
:param password: 用户的密码
|
||
"""
|
||
|
||
password_input = tab.ele('@@tag()=input@@name=password')
|
||
|
||
# 模拟人类输入密码,每次输入一个字符,并随机延迟
|
||
for char in password:
|
||
password_input.input(char) # 输入一个字符
|
||
tab.wait(random.uniform(0.1, 0.3)) # 随机延迟 0.1 到 0.3 秒
|
||
|
||
confirm_new_password_input = tab.ele("@@tag()=input@@name=confirmation_password")
|
||
# 模拟人类输入密码,每次输入一个字符,并随机延迟
|
||
for char in password:
|
||
confirm_new_password_input.input(char) # 输入一个字符
|
||
tab.wait(random.uniform(0.1, 0.3)) # 随机延迟 0.1 到 0.3 秒
|
||
|
||
|
||
# 点击修改按钮
|
||
def click_span_with_class(tab):
|
||
# 找到 class 为 UywwFc vQzf8d 的元素
|
||
span_element = tab.ele('@text()=Change password', timeout=15)
|
||
|
||
# 用 JavaScript 点击该元素
|
||
span_element.click(by_js=True)
|
||
|
||
# 返回上个页面
|
||
tab.back()
|
||
|
||
|
||
# 修改辅助邮箱账号2
|
||
def modify_the_secondary_email2(tab, new_recovery_email, new_password, hwnd):
|
||
try:
|
||
# 定位恢复邮箱的元素
|
||
recovery_email = tab.ele("@aria-label=Recovery email", timeout=15)
|
||
if not recovery_email:
|
||
raise Exception("未找到恢复邮箱元素")
|
||
|
||
# 获取恢复邮箱的链接并导航
|
||
href = recovery_email.attr('href')
|
||
if not href:
|
||
raise Exception("恢复邮箱链接未找到")
|
||
|
||
tab.get(href)
|
||
|
||
password_input = tab.ele('@@aria-label=输入您的密码@@type=password', timeout=15)
|
||
if password_input:
|
||
input_password(tab, hwnd, new_password, password_input)
|
||
|
||
tab.wait.load_start() # 等待页面加载开始
|
||
|
||
# 定位邮箱输入框并清空内容
|
||
email_input = tab.ele("@type=email", timeout=15)
|
||
|
||
# 查看辅助邮箱账号是否已经被修改了
|
||
if email_input.value == new_recovery_email:
|
||
return True
|
||
|
||
if not email_input:
|
||
raise Exception("未找到邮箱输入框")
|
||
|
||
email_input.clear()
|
||
|
||
for char in new_recovery_email:
|
||
email_input.input(char) # 输入一个字符
|
||
tab.wait(random.uniform(0.1, 0.3)) # 随机延迟 0.1 到 0.3 秒
|
||
|
||
# 定位并点击验证按钮
|
||
verify_button = tab.ele("@text()=Verify", timeout=15)
|
||
print(f"验证按钮已经找到:{verify_button}")
|
||
if not verify_button:
|
||
raise Exception("未找到验证按钮")
|
||
|
||
verify_button.click(by_js=True)
|
||
|
||
return False # 成功返回 True
|
||
except Exception as e:
|
||
print(f"更新恢复邮箱时发生错误: {e}")
|
||
return False # 出现错误返回 False
|
||
|
||
|
||
# 定义保存到本地文件的函数
|
||
def save_to_file(entry, filename="code.txt"):
|
||
"""将条目保存到本地文件,避免重复"""
|
||
# 如果文件不存在,先创建空文件
|
||
if not os.path.exists(filename):
|
||
with open(filename, "w", encoding='utf-8') as file:
|
||
pass # 创建文件
|
||
|
||
# 读取文件内容,检查是否存在相同条目
|
||
with open(filename, "r", encoding='utf-8') as file:
|
||
existing_entries = file.readlines()
|
||
|
||
# 构造条目字符串
|
||
entry_str = f"验证码: {entry['验证码']}, 发送的邮箱账号: {entry['发送的邮箱账号']}, 收到的时间: {entry['收到的时间']}\n"
|
||
|
||
if entry_str not in existing_entries: # 如果条目不在文件中
|
||
# 以追加模式写入文件
|
||
with open(filename, "a", encoding='utf-8') as file:
|
||
file.write(entry_str)
|
||
print("--------------------------------------------------")
|
||
print(f"新条目已保存: {entry_str.strip()}")
|
||
|
||
|
||
def clean(text):
|
||
"""清理邮件主题或发件人内容中的特殊字符"""
|
||
return "".join(c if c.isalnum() else "_" for c in text)
|
||
|
||
|
||
|
||
|
||
# 用来标记账号是否完成
|
||
def update_status_in_db(email: str, status: str):
|
||
"""
|
||
更新数据库中指定账户的状态。
|
||
"""
|
||
# 更新数据库中对应账户的状态
|
||
db_manager.update_record(email=email, change_status=status)
|
||
|
||
# 输出日志信息
|
||
print(f"账号 {email} 的状态已更新为:{status}")
|
||
|
||
|
||
|
||
# 保存日志到指定文件
|
||
def save_log(random_number, log_content):
|
||
logs_dir = "logs"
|
||
|
||
# 创建文件名,使用传入的 random_number
|
||
log_file_path = os.path.join(logs_dir, f"{random_number}.txt")
|
||
|
||
# 将日志内容写入文件
|
||
with open(log_file_path, 'a', encoding='utf-8') as f:
|
||
f.write(log_content)
|
||
f.write("\n")
|
||
|
||
|
||
def retry_with_recovery(tab, hwnd, email_account, MAX_RETRIES=4, wait_time=5):
|
||
"""
|
||
封装重试逻辑,最多尝试 `MAX_RETRIES` 次,检查是否存在重试按钮,并重新输入邮箱地址。
|
||
|
||
:param tab: 页面操作对象,假设具备 `.ele` 方法用于获取元素,`.wait.load_start()` 方法用于等待加载
|
||
:param recover: 处理重试按钮点击的函数
|
||
:param email_account: 需要输入的邮箱账号
|
||
:param MAX_RETRIES: 最大重试次数,默认为4次
|
||
:param wait_time: 等待时间,默认为15秒
|
||
"""
|
||
attempt = 0 # 初始化尝试次数
|
||
|
||
while attempt < MAX_RETRIES:
|
||
recover_a = tab.ele("@aria-label=重试", timeout=wait_time) # 检查是否有 '重试' 按钮
|
||
print(f"Attempt {attempt + 1}: {recover_a}")
|
||
tab.wait.load_start() # 等待加载开始
|
||
|
||
if recover_a:
|
||
recover(tab, recover_a) # 调用 recover 函数处理重试按钮
|
||
email_input = tab.ele('@aria-label=电子邮件地址或电话号码', timeout=wait_time) # 找到邮箱输入框
|
||
input_email(tab, hwnd, email_account, email_input) # 重新输入邮箱账号
|
||
random_sleep(tab) # 随机等待,避免被检测到自动化
|
||
attempt += 1 # 增加尝试次数
|
||
else:
|
||
break # 如果没有 '重试' 按钮,则跳出循环
|
||
|
||
# 如果达到最大尝试次数,跳出循环
|
||
if attempt == MAX_RETRIES:
|
||
print("Reached maximum retries")
|
||
break
|
||
|
||
|
||
def get_absolute_path(relative_path):
|
||
"""获取绝对路径"""
|
||
# 获取当前执行文件的路径(脚本或.exe)
|
||
if getattr(sys, 'frozen', False): # 检测是否为打包后的exe
|
||
base_path = sys._MEIPASS # PyInstaller打包后资源文件临时路径
|
||
exe_path = os.path.dirname(sys.executable)
|
||
else:
|
||
base_path = os.path.abspath(".")
|
||
exe_path = os.path.dirname(os.path.abspath(__file__))
|
||
|
||
# 如果需要拼接在exe路径上:
|
||
absolute_path = os.path.join(exe_path, relative_path)
|
||
return absolute_path
|
||
|
||
|
||
def logutGoogle(tab) -> bool:
|
||
tab.get('https://accounts.google.com/Logout?hl=zh-CN')
|
||
if tab.ele('@@tag()=input@@type=email', timeout=0) != None: # 已注销就会出登录页元素
|
||
print("当前浏览器已退出,无需再次退出")
|
||
return True
|
||
|
||
# 还有没注销移除的账号就会显示已退出和账号元素
|
||
try:
|
||
tab.ele('@text()=移除账号', timeout=0).click()
|
||
tab.wait(2).ele('@text()=已退出', timeout=0).parent().parent().next().click() # 移除图标
|
||
tab.wait(3).ele('@@text()=移除@@tag()=button').click() # 移除确认框
|
||
return (tab.ele('@@tag()=input@@type=email', timeout=8) != None) # 检查最后界面是否是登录
|
||
except Exception as e:
|
||
print(f'发生错误:{e}')
|
||
return False
|
||
|
||
return False # 无用分支,不过为了避免函数太长回头修改时候忘记,还是写个 False 以防万一
|
||
|
||
|
||
def main(email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host, proxy_port, proxy_username, proxy_password,region):
|
||
update_status_in_db(email_account, '初始化中')
|
||
|
||
print("邮箱账户:", email_account) # 邮箱账户
|
||
print("邮箱密码:", email_password) # 邮箱密码
|
||
print("旧的恢复邮箱:", old_recovery_email) # 旧的恢复邮箱
|
||
print("新的密码:", new_password) # 新的密码
|
||
print("新的恢复邮箱:", new_recovery_email) # 新的恢复邮箱
|
||
print("代理主机:", proxy_host) # 代理主机
|
||
print("代理端口:", proxy_port) # 代理端口
|
||
print("代理用户名:", proxy_username) # 代理用户名
|
||
print("代理密码:", proxy_password) # 代理密码
|
||
print("代理区域:", region)
|
||
global browser, plugin_path, user_dir, attempt
|
||
# 生成一个 6 位的随机数
|
||
global random_number
|
||
|
||
|
||
|
||
try:
|
||
random_number = str(random.randint(100000000, 999999999))
|
||
"""
|
||
主函数,用于自动登录谷歌邮箱并修改账户和密码。
|
||
|
||
参数:
|
||
email_account (str): 邮箱账号
|
||
email_password (str): 邮箱密码
|
||
old_recovery_email (str): 旧的辅助邮箱账号
|
||
new_recovery_email (str): 新的辅助邮箱账号
|
||
new_password (str): 新密码
|
||
"""
|
||
if stop_event.is_set():
|
||
print("任务被强制停止,退出主函数")
|
||
return None # 直接返回 None,跳过当前任务
|
||
|
||
if not proxy_host:
|
||
save_log(random_number, f"没有{region}区域的代理")
|
||
update_status_in_db(email_account, f"没有{region}区域代理")
|
||
return False
|
||
|
||
|
||
lock = FileLock("proxy_auth_extension.lock")
|
||
|
||
with lock: # 加锁,确保其他进程在解锁前无法操作
|
||
plugin_path = create_proxy_auth_extension(
|
||
proxy_host=proxy_host,
|
||
proxy_port=proxy_port,
|
||
proxy_username=proxy_username,
|
||
proxy_password=proxy_password,
|
||
proxy_domains=[
|
||
"accounts.google.com",
|
||
"accounts.youtube.com",
|
||
"content-autofill.googleapis.com",
|
||
"myaccount.google.com",
|
||
"apis.google.com",
|
||
"www.google.com",
|
||
"ogs.google.com",
|
||
"jsonip.com"
|
||
]
|
||
)
|
||
save_log(random_number, f"已经创建好代理插件:{plugin_path}")
|
||
|
||
# 创建ChromiumOptions对象并配置
|
||
options = ChromiumOptions()
|
||
|
||
options.add_extension(plugin_path)
|
||
|
||
# 使用随机生成的User-Agent
|
||
random_user_agent = generate_user_agent()
|
||
options.set_user_agent(user_agent=random_user_agent)
|
||
|
||
# 随机分配一个端口号
|
||
random_port = get_free_port()
|
||
print(f"现在占用的端口号是:{random_port}")
|
||
save_log(random_number, f"现在占用的端口号是:{random_port}")
|
||
options.set_paths(local_port=random_port)
|
||
|
||
# 示例使用
|
||
browser_path = get_absolute_path(r"chrome\chrome.exe")
|
||
options.set_paths(f"{browser_path}")
|
||
|
||
options.no_imgs(True).mute(True)
|
||
|
||
cache_path = os.path.join(os.getcwd(), 'cache')
|
||
temp_path = os.path.join(os.getcwd(), 'tempfile')
|
||
user_dir = os.path.join(os.getcwd(), 'userdata', f'{uuid.uuid4().hex}')
|
||
|
||
options.set_cache_path(cache_path)
|
||
options.set_tmp_path(temp_path)
|
||
options.set_user_data_path(user_dir)
|
||
|
||
print(cache_path)
|
||
print(temp_path)
|
||
print(user_dir)
|
||
|
||
options.set_pref(arg='profile.default_content_settings.popups', value='0')
|
||
options.set_pref('translate.enabled', False) # 禁用翻译功能
|
||
options.set_pref('translate.newtld', False) # 禁用新的翻译扩展功能
|
||
|
||
# 确保不显示翻译提示
|
||
options.set_pref('profile.content_settings.exceptions.translate', '{"pattern":"*","setting":2}')
|
||
|
||
browser = ChromiumPage(options)
|
||
|
||
# 清除缓存和cookies
|
||
browser.clear_cache(cookies=True)
|
||
# browser = Chromium(29581)
|
||
|
||
# 获取标签页对象
|
||
tab = browser.latest_tab
|
||
save_log(random_number, "已获取谷歌的最后一个页面")
|
||
|
||
# 访问谷歌邮箱页面
|
||
flag = tab.get('https://workspace.google.com/intl/zh-CN/gmail/')
|
||
save_log(random_number, f"访问谷歌邮箱页面的状态:{flag}")
|
||
if not flag:
|
||
update_status_in_db(email_account, "无法访问谷歌邮箱页面,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
save_log(random_number, "已经访问谷歌页面")
|
||
|
||
# 执行JavaScript清除localStorage
|
||
tab.run_js("window.localStorage.clear();")
|
||
|
||
|
||
# 进入登录流程
|
||
login_a = tab.ele('@aria-label=登录 Gmail', timeout=15)
|
||
if login_a:
|
||
click_the_login_button(tab, login_a)
|
||
random_sleep(tab)
|
||
|
||
login = tab.ele('@@tag()=span@@text()=登录', timeout=10)
|
||
if login:
|
||
random_sleep(tab)
|
||
# 将随机数设置为窗口标题
|
||
script = f"document.title = '{random_number}';"
|
||
# 执行 JS 脚本
|
||
tab.run_js(script)
|
||
print("登录运行完毕")
|
||
save_log(random_number, "已经进入登录界面")
|
||
else:
|
||
update_status_in_db(email_account, '登录超时')
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
random_sleep(tab)
|
||
|
||
actual_title = tab.run_js("return document.title;")
|
||
|
||
save_log(random_number, f"设置标题为: {random_number}, 实际标题为: {actual_title}\n")
|
||
|
||
# 获取所有 Chrome 窗口
|
||
chrome_windows = enum_windows()
|
||
save_log(random_number, f"当前谷歌浏览器的所有窗口为:{chrome_windows}")
|
||
hwnd = None
|
||
for win in chrome_windows:
|
||
if random_number in win[1]: # 假设 `win['title']` 是窗口标题
|
||
hwnd = win[0]
|
||
print(f"窗口句柄:{win[0]} 窗口标题:{win[1]}")
|
||
save_log(random_number, f"获取的窗口的句柄:{win[0]} 和标题:{win[1]}")
|
||
break
|
||
|
||
if hwnd is None:
|
||
print("无法找到窗口句柄")
|
||
save_log(random_number, "无法找到窗口句柄")
|
||
update_status_in_db(email_account, '无法找到窗口句柄')
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
try:
|
||
# 输入邮箱账号
|
||
email_input = tab.ele('@aria-label=电子邮件地址或电话号码', timeout=15)
|
||
if email_input:
|
||
flag = input_email(tab, hwnd, email_account, email_input) # 使用传入的邮箱账号
|
||
if not flag:
|
||
update_status_in_db(random_number, "出现谷歌文字验证")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
random_sleep(tab)
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到输入邮箱账号的元素{e}")
|
||
save_log(random_number, f"找不到输入邮箱账号的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
print("输入邮箱账号运行完毕")
|
||
save_log(random_number, "输入邮箱账号运行完毕")
|
||
|
||
wrong = tab.ele('@text()=出了点问题', timeout=5)
|
||
if wrong:
|
||
next_span = tab.ele("@@tag()=span@@text()=下一步", timeout=15)
|
||
next_span.click(by_js=True)
|
||
tab.wait.load_start()
|
||
try:
|
||
# 输入邮箱账号
|
||
email_input = tab.ele('@aria-label=电子邮件地址或电话号码', timeout=15)
|
||
if email_input:
|
||
input_email(tab, hwnd, email_account, email_input) # 使用传入的邮箱账号
|
||
random_sleep(tab)
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到输入邮箱账号的元素{e}")
|
||
save_log(random_number, f"找不到输入邮箱账号的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
retry_with_recovery(tab, hwnd, email_account)
|
||
|
||
print("检查是否出现了wrong运行完毕")
|
||
save_log(random_number, "检查是否出现了wrong运行完毕")
|
||
|
||
# 看下是否出现了手机号
|
||
telephone = tab.ele("@text()=请输入电话号码,以便通过短信接收验证码。", timeout=5)
|
||
if telephone:
|
||
update_status_in_db(email_account, '接码')
|
||
browser.quit()
|
||
return
|
||
|
||
print("检查出现手机号运行完毕")
|
||
save_log(random_number, "检查出现手机号运行完毕")
|
||
|
||
try:
|
||
# 输入密码
|
||
password_input = tab.ele('@@aria-label=输入您的密码@@type=password', timeout=15)
|
||
if password_input:
|
||
flag = input_password(tab, hwnd, email_password, password_input) # 使用传入的邮箱密码
|
||
if flag:
|
||
update_status_in_db(email_account, "密码的下一步无法点击")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
random_sleep(tab)
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到密码输入框的元素:{e}")
|
||
save_log(random_number, f"找不到密码输入框的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
print("输入旧密码运行完毕")
|
||
save_log(random_number, "输入旧密码运行完毕")
|
||
|
||
wrong = tab.ele("@text()=抱歉,出了点问题。请重试。", timeout=15)
|
||
save_log(random_number, f"谷歌验证按钮是否出现:{wrong}")
|
||
if wrong:
|
||
next_span = tab.ele("@@tag()=span@@text()=下一步", timeout=15)
|
||
next_span.click(by_js=True)
|
||
tab.wait.load_start()
|
||
try:
|
||
# 输入邮箱账号
|
||
email_input = tab.ele('@aria-label=电子邮件地址或电话号码', timeout=15)
|
||
if email_input:
|
||
input_email(tab, hwnd, email_account, email_input) # 使用传入的邮箱账号
|
||
random_sleep(tab)
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到输入邮箱账号的元素{e}")
|
||
save_log(random_number, f"找不到输入邮箱账号的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_recovery_email, new_password, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password)
|
||
|
||
try:
|
||
# 输入密码
|
||
password_input = tab.ele('@@aria-label=输入您的密码@@type=password', timeout=15)
|
||
if password_input:
|
||
input_password(tab, hwnd, email_password, password_input) # 使用传入的邮箱密码
|
||
random_sleep(tab)
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到密码输入框的元素:{e}")
|
||
save_log(random_number, f"找不到密码输入框的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
print("如果出现错误,输入密码运行完毕")
|
||
save_log(random_number, "如果出现错误后,输入密码运行完毕")
|
||
|
||
# 确定密码是否被修改的开关
|
||
password_change = False
|
||
|
||
try:
|
||
# 查看密码是否更改
|
||
if tab.wait.ele_deleted('#passwordNext', timeout=5) == False:
|
||
save_log(random_number, f"旧密码出错,使用新密码")
|
||
password_input = tab.ele('@@aria-label=输入您的密码@@type=password', timeout=15)
|
||
input_password(tab, hwnd, new_password, password_input)
|
||
save_log(random_number, f"输入新密码完毕")
|
||
tab.wait(7)
|
||
if tab.wait.ele_deleted('#passwordNext', timeout=10) == False:
|
||
update_status_in_db(email_account, '被盗')
|
||
return email_account
|
||
password_change = True
|
||
print("密码已经被更改过")
|
||
save_log(random_number, "密码已经被更改过")
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到密码输入框的元素{e}")
|
||
save_log(random_number, f"找不到密码输入框的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
print("输入新密码运行完毕")
|
||
save_log(random_number, "输入新密码运行完毕")
|
||
|
||
# 看下是否出现了手机号
|
||
telephone = tab.ele("@text()=请输入电话号码,以便通过短信接收验证码。", timeout=5)
|
||
if telephone:
|
||
save_log(random_number, '接码')
|
||
update_status_in_db(email_account, '接码')
|
||
return email_account
|
||
|
||
print("检查手机号2运行完毕")
|
||
save_log(random_number, "检查手机号2运行完毕")
|
||
|
||
wrong = tab.ele('@@tag()=a@@text()=了解详情', timeout=5)
|
||
alternate_email_button = tab.ele('@text()=确认您的辅助邮箱', timeout=5)
|
||
if wrong and not alternate_email_button:
|
||
save_log(random_number, '出现账号状态异常')
|
||
update_status_in_db(email_account, '出现账号状态异常')
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
print("检查谷歌账号是否存在异常活动完毕")
|
||
save_log(random_number, "检查谷歌账号是否存在异常活动完毕")
|
||
|
||
|
||
try:
|
||
if alternate_email_button:
|
||
click_alternate_email_verification_button(tab, alternate_email_button)
|
||
random_sleep(tab)
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到输入辅助邮箱的元素:{e}" )
|
||
save_log(random_number, f"找不到输入辅助邮箱的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
print("点击辅助邮箱验证运行完毕")
|
||
save_log(random_number, "点击辅助邮箱验证运行完毕")
|
||
|
||
# 确定辅助邮箱账号是否被更改
|
||
auxiliary_email_account_change = False
|
||
|
||
# 输入辅助邮箱账号
|
||
recovery_input = tab.ele('@aria-label=输入辅助邮箱地址', timeout=15)
|
||
if recovery_input:
|
||
auxiliary_email_account_change = input_recovery_code(tab, old_recovery_email) # 使用传入的旧辅助邮箱
|
||
print(f"是否有修改辅助邮箱账号:{auxiliary_email_account_change}")
|
||
tab.wait(5)
|
||
# 如果没有被修改则进行修改
|
||
if not auxiliary_email_account_change:
|
||
|
||
print("我要开始修改辅助邮箱账号了")
|
||
|
||
try:
|
||
button = tab.ele('@type=email', timeout=15)
|
||
if button:
|
||
modify_the_secondary_email1(tab,button, new_recovery_email) # 使用传入的新辅助邮箱
|
||
print("修改完成")
|
||
auxiliary_email_account_change = True
|
||
else:
|
||
print("没有修改辅助邮箱的界面,跳过")
|
||
# auxiliary_email_account_change = True
|
||
except ElementNotFoundError as e:
|
||
# 捕获并打印异常,不中断后续代码执行
|
||
print(f"修改辅助邮箱的时候找不到元素: {e}")
|
||
save_log(random_number, f"修改辅助邮箱的时候找不到元素: {e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password)
|
||
else:
|
||
print("辅助邮箱账号已经被更改过")
|
||
save_log(random_number, "辅助邮箱账号已经被更改过")
|
||
recovery_input.clear()
|
||
input_recovery_code(tab, new_recovery_email)
|
||
|
||
print("修改辅助邮箱1运行完毕")
|
||
save_log(random_number, "修改辅助邮箱1运行完毕")
|
||
|
||
if password_change and auxiliary_email_account_change:
|
||
logutGoogle(tab)
|
||
tab.wait(3)
|
||
update_status_in_db(email_account, '已更改')
|
||
return email_account
|
||
|
||
tab.handle_alert(accept=True)
|
||
|
||
# # 点击继续按钮
|
||
# continue_div1 = tab.ele('@text()=Continue with smart features', timeout=15)
|
||
# if continue_div1:
|
||
# try:
|
||
# click_continue_button(tab, continue_div1)
|
||
# except ElementNotFoundError as e:
|
||
# print(f"找不到继续按钮的元素:{e}")
|
||
# save_log(random_number, f"找不到继续按钮的元素:{e}")
|
||
# update_status_in_db(email_account, "请求错误,请重试")
|
||
# return (
|
||
# email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
# proxy_port,
|
||
# proxy_username, proxy_password, region)
|
||
|
||
try:
|
||
# 安全设置
|
||
tab.handle_alert(accept=True)
|
||
tab.get("https://ogs.google.com/u/0/widget/account?cn=account")
|
||
tab.handle_alert(accept=True)
|
||
tab.get("https://myaccount.google.com/security?gar=WzEyMF0")
|
||
except ElementNotFoundError as e:
|
||
save_log(random_number, f"进入安全设置后出错:{e}")
|
||
update_status_in_db(email_account,"进入安全设置的时候请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
tab.wait(3)
|
||
|
||
wrong = tab.ele("@text()=要查看和调整您的安全设置并获取有助于确保您账号安全的建议,请登录您的账号", timeout=5)
|
||
if wrong:
|
||
print("进入安全设置后仍然未登录")
|
||
save_log(random_number, '进入安全设置后仍然未登录')
|
||
update_status_in_db(email_account, '登录失败')
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
try:
|
||
# 如果密码没被修改
|
||
if not password_change:
|
||
# 找到修改密码的按钮
|
||
find_and_visit_href2(tab)
|
||
# 修改密码
|
||
input_password_new(tab, new_password) # 使用传入的新密码
|
||
# 点击确认修改
|
||
click_span_with_class(tab)
|
||
except ElementNotFoundError as e:
|
||
print(f"找不到修改密码的元素:{e}")
|
||
save_log(random_number, f"找不到修改密码的元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
tab.wait(1)
|
||
|
||
try:
|
||
if auxiliary_email_account_change:
|
||
logutGoogle(tab)
|
||
tab.wait(3)
|
||
update_status_in_db(email_account, '已更改')
|
||
return email_account
|
||
# 修改辅助邮箱2
|
||
flag = modify_the_secondary_email2(tab, new_recovery_email, new_password, hwnd)
|
||
save_log(random_number, f"辅助邮箱是否被修改:{flag}")
|
||
if flag:
|
||
logutGoogle(tab)
|
||
tab.wait(3)
|
||
update_status_in_db(email_account, '已更改')
|
||
return email_account
|
||
else:
|
||
# 初始化
|
||
client = EmailClient(server, username, password)
|
||
client.connect()
|
||
code_receiver = GoogleCodeReceiver(client)
|
||
|
||
# 最多尝试3次循环
|
||
for attempt in range(3):
|
||
print(f"第 {attempt + 1} 次尝试检查验证码...")
|
||
save_log(random_number, f"第 {attempt + 1} 次尝试检查验证码...")
|
||
|
||
# 获取当前程序运行的时间
|
||
current_time = datetime.now(timezone.utc)
|
||
print(f"当前时间为{current_time}")
|
||
save_log(random_number, f"当前时间为{current_time}")
|
||
|
||
# 检查验证码,超时时间为300秒,使用当前时间作为 start_time
|
||
try:
|
||
code = code_receiver.wait_code(username=f"{email_account}", timeout=300, interval=5,
|
||
start_time=datetime(2024, 11, 27))
|
||
print(f"检查的验证码是{code}")
|
||
save_log(random_number, f"检查的验证码是{code}")
|
||
if code:
|
||
# 如果找到验证码,填入验证码
|
||
verification_input = tab.eles("@type=text", timeout=15)
|
||
if len(verification_input) >= 1:
|
||
verification_input[1].clear()
|
||
for char in code:
|
||
verification_input[1].input(char)
|
||
tab.wait(random.uniform(0.1, 0.3)) # 随机延迟 0.1 到 0.3 秒
|
||
|
||
print("输入完验证码了")
|
||
save_log(random_number, "输入完验证码了")
|
||
|
||
# 点击 Verify 按钮
|
||
verify_button = tab.eles("@text()=Verify", timeout=15)
|
||
if verify_button and len(verify_button) > 1:
|
||
verify_button[1].click(by_js=True)
|
||
tab.wait(5)
|
||
|
||
print("点击确定")
|
||
save_log(random_number, "点击确定")
|
||
|
||
# 检查是否有报错提示
|
||
is_visible = tab.ele('@@id=c4@@tag()=p@@role=alert', timeout=1)
|
||
save_log(random_number, f"是否有报错提示:{is_visible}")
|
||
if is_visible:
|
||
# 如果有报错提示,点击重新发送按钮并进入下一次循环
|
||
sent_code_button = tab.ele('@text():Send a new code.', timeout=15)
|
||
if sent_code_button:
|
||
print(f"重新发送邮件的按钮为:{sent_code_button}")
|
||
save_log(random_number, f"重新发送邮件的按钮为:{sent_code_button}")
|
||
sent_code_button.click(by_js=True)
|
||
continue
|
||
else:
|
||
# 如果没有报错,退出
|
||
print("辅助邮箱账号已经更改完毕")
|
||
save_log(random_number, "辅助邮箱账号已经更改完毕")
|
||
logutGoogle(tab)
|
||
tab.wait(3)
|
||
update_status_in_db(email_account, "已更改")
|
||
return email_account
|
||
|
||
else:
|
||
# 如果未找到验证码,点击重新发送按钮并进入下一次循环
|
||
print("未找到验证码,点击重新发送按钮")
|
||
sent_code_button = tab.ele('@text():Send a new code.', timeout=15)
|
||
if sent_code_button:
|
||
sent_code_button.click(by_js=True)
|
||
continue
|
||
except TimeoutError:
|
||
print("超时未收到验证码,重新发送")
|
||
save_log(random_number, "超时未收到验证码,重新发送")
|
||
sent_code_button = tab.ele('@text():Send a new code.', timeout=15)
|
||
if sent_code_button:
|
||
sent_code_button.click(by_js=True)
|
||
except ElementNotFoundError as e:
|
||
print(f"更改辅助邮箱账号的时候找不到元素:{e}")
|
||
save_log(random_number, f"更改辅助邮箱账号的时候找不到元素:{e}")
|
||
update_status_in_db(email_account, "请求错误,请重试")
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
except Exception as e:
|
||
print(f"出现未知错误:{e}")
|
||
save_log(random_number, f"出现未知错误:{e}")
|
||
update_status_in_db(email_account, f'出现未知错误:{e}请重试')
|
||
return (
|
||
email_account, email_password, old_recovery_email, new_password, new_recovery_email, proxy_host,
|
||
proxy_port,
|
||
proxy_username, proxy_password, region)
|
||
|
||
finally:
|
||
try:
|
||
browser.quit()
|
||
# 释放资源
|
||
time.sleep(5)
|
||
delete_folder(plugin_path)
|
||
delete_folder(user_dir)
|
||
except Exception:
|
||
print("出现没有代理")
|
||
save_log(random_number, "出现没有代理")
|
||
|
||
|
||
def run_gui():
|
||
stop_flag = threading.Event() # 用于控制任务中止的标志位
|
||
exec_thread = None # 用于存储后台线程的引用
|
||
|
||
|
||
def handle_file_select():
|
||
file_path = filedialog.askopenfilename(
|
||
title="选择 Excel 文件",
|
||
filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")]
|
||
)
|
||
if file_path:
|
||
entry_file_path.delete(0, tk.END)
|
||
entry_file_path.insert(0, file_path)
|
||
|
||
def update_proxy_stats():
|
||
try:
|
||
# 国家代码列表
|
||
countries = {
|
||
"AT": "奥地利", "BE": "比利时", "CZ": "捷克", "DE": "德国", "DK": "丹麦",
|
||
"ES": "西班牙", "FI": "芬兰", "FR": "法国", "GB": "英国", "HR": "克罗地亚",
|
||
"HU": "匈牙利", "IT": "意大利", "LT": "立陶宛", "LU": "卢森堡", "NL": "荷兰",
|
||
"PL": "波兰", "PT": "葡萄牙", "RO": "罗马尼亚", "SE": "瑞典", "SK": "斯洛伐克"
|
||
}
|
||
|
||
# 获取全部区域的代理数量
|
||
try:
|
||
total_proxies = proxy_manager.get_proxy_count("ALL")
|
||
except Exception as e:
|
||
total_proxies = "获取失败"
|
||
print(f"获取全部代理数时出错: {e}")
|
||
|
||
# 显示总代表数量
|
||
proxy_stats = f"总数: {total_proxies} 个代理\n\n"
|
||
|
||
# 获取并显示每个国家的代理数量
|
||
country_data = []
|
||
for country_code, country_name in countries.items():
|
||
try:
|
||
# 获取该国家的代理数量
|
||
country_proxy_count = proxy_manager.get_proxy_count(country_code)
|
||
country_data.append(f"{country_name}({country_code}): {country_proxy_count} 个代理")
|
||
except Exception as e:
|
||
country_data.append(f"{country_name}({country_code}): 获取代理数失败")
|
||
print(f"获取 {country_name} ({country_code}) 代理数时出错: {e}")
|
||
|
||
# 更新界面上的代理统计标签
|
||
lbl_proxy_stats.config(text=proxy_stats)
|
||
# 分列显示国家的代理统计信息
|
||
max_rows = 5 # 每列最多显示多少行
|
||
columns = 4 # 显示几列
|
||
for col in range(columns):
|
||
for row in range(min(max_rows, len(country_data) - col * max_rows)):
|
||
label = tk.Label(root, text=country_data[col * max_rows + row], justify="left")
|
||
label.grid(row=row + 9, column=col, padx=5, pady=5, sticky="w")
|
||
|
||
# 为每列添加居中对齐,使用 columnspan 来确保列宽
|
||
for col in range(columns):
|
||
root.grid_columnconfigure(col, weight=1, uniform="equal")
|
||
|
||
# 调整列之间的间距,通过 columnconfigure 设置每列最小宽度(minsize)
|
||
for col in range(columns):
|
||
root.grid_columnconfigure(col, minsize=5)
|
||
except Exception as e:
|
||
print(f"更新代理统计信息时出错: {e}")
|
||
lbl_proxy_stats.config(text="获取代理统计信息失败")
|
||
|
||
def start_processing():
|
||
file_path = entry_file_path.get()
|
||
sheet_name = entry_sheet_name.get()
|
||
max_concurrency = entry_concurrency.get()
|
||
max_retries = entry_retries.get()
|
||
|
||
|
||
|
||
# 检查文件路径和 Sheet 名称是否填写完整
|
||
if not file_path or not sheet_name or not max_concurrency or not max_retries:
|
||
messagebox.showwarning("警告", "请填写完整的文件路径、表格名称、并发数和重试次数!")
|
||
return
|
||
|
||
# 检查文件路径是否存在
|
||
if not os.path.exists(file_path):
|
||
if not file_path.endswith(".xlsx"):
|
||
messagebox.showerror("错误", "文件的格式必须是.xlsx")
|
||
else:
|
||
messagebox.showerror("错误", "没有找到该路径的文件,请检查文件是否存在!")
|
||
return
|
||
|
||
# 检查文件是否正在被打开
|
||
try:
|
||
with open(file_path, 'r+b') as file:
|
||
pass # 成功打开说明未被占用
|
||
except PermissionError:
|
||
messagebox.showerror("错误", "文件正在被占用,请先保存并关闭 Excel 程序!")
|
||
return
|
||
|
||
# 验证 Sheet 名称是否存在
|
||
try:
|
||
wb = load_workbook(file_path, read_only=True)
|
||
if sheet_name not in wb.sheetnames:
|
||
messagebox.showerror("错误", f"“{sheet_name}”表格名字不存在,请打开 Excel 文件检查!")
|
||
return
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"无法打开文件:{e}")
|
||
return
|
||
|
||
# 验证并发数
|
||
try:
|
||
max_concurrency = int(max_concurrency)
|
||
if max_concurrency <= 0:
|
||
raise ValueError("并发数必须是大于0的整数!")
|
||
except ValueError as e:
|
||
messagebox.showerror("错误", f"无效的并发数:{e}")
|
||
return
|
||
|
||
# 验证重试次数
|
||
try:
|
||
max_retries = int(max_retries)
|
||
if max_retries < 0 or max_retries > 3:
|
||
raise ValueError("重试次数必须是整数且在0到3之间!")
|
||
except ValueError as e:
|
||
messagebox.showerror("错误", f"无效的重试次数:{e}")
|
||
return
|
||
|
||
db_manager.import_from_excel(file_path, clear_old=True)
|
||
proxy_manager.import_proxies_with_classifier("Ip.txt", classifier=classifier_smartproxy)
|
||
|
||
# 初始化 Excel 数据和进度条状态
|
||
all_rows = read_data_from_db() # 读取 Excel 数据
|
||
total_tasks = len(all_rows)
|
||
completed_count = 0
|
||
failed_count = 0
|
||
|
||
# 设置进度条和状态显示
|
||
progress_bar['maximum'] = total_tasks
|
||
progress_bar['value'] = completed_count # 初始化为0
|
||
lbl_progress_status.config(
|
||
text=f"完成:{completed_count}/{total_tasks},失败:{failed_count}"
|
||
)
|
||
|
||
stop_flag.clear() # 清除停止标志
|
||
exec_thread = threading.Thread(target=parallel_execution_with_db,
|
||
args=(file_path, max_concurrency, max_retries))
|
||
exec_thread.daemon = True # 设置为守护线程
|
||
exec_thread.start()
|
||
|
||
def stop_processing():
|
||
stop_flag.set() # 设置标志位为 True,通知停止
|
||
messagebox.showinfo("提示", "等待当前任务完成,程序即将停止!")
|
||
|
||
def on_closing():
|
||
stop_flag.set() # 设置标志位为 True,通知停止
|
||
if exec_thread and exec_thread.is_alive():
|
||
exec_thread.join(timeout=5) # 等待线程结束
|
||
root.destroy() # 销毁主窗口
|
||
sys.exit(0) # 强制退出程序
|
||
|
||
def parallel_execution_with_db(file_path, max_concurrency, max_retries):
|
||
progress_queue = Queue()
|
||
all_rows = read_data_from_db() # 第一次读取数据
|
||
print(f"数据库中的数据:{all_rows}")
|
||
|
||
total_rows = len(all_rows)
|
||
completed_count = 0
|
||
failed_count = 0
|
||
progress_bar['maximum'] = total_rows
|
||
|
||
retry_count = 0
|
||
|
||
with Pool(processes=max_concurrency, initializer=init_worker()) as pool:
|
||
while all_rows:
|
||
if stop_flag.is_set():
|
||
print("停止信号已接收,中止任务!")
|
||
break
|
||
|
||
results = []
|
||
|
||
for i, row in enumerate(all_rows):
|
||
if stop_flag.is_set():
|
||
print("停止信号已接收,中止任务!")
|
||
break
|
||
|
||
# 获取邮箱和区域信息
|
||
account_email = row[0]
|
||
region = row[9] # 假设第6列存储的是账号的区域信息
|
||
|
||
if not region:
|
||
region = "ALL" # 如果没有区域信息,则使用默认区域
|
||
|
||
# 获取一个随机代理并传递给当前账号
|
||
proxy = proxy_manager.get_random_proxy_by_region(region=region, remove_after_fetch=True)
|
||
|
||
if proxy: # 如果分配成功
|
||
proxy_host, proxy_port, proxy_user, proxy_pass = proxy['host'], proxy['port'], proxy['user'], \
|
||
proxy['password']
|
||
print(f"为账号 {account_email} 分配代理:{proxy_host}:{proxy_port}:{proxy_user}:{proxy_pass}")
|
||
|
||
db_manager.update_record(account_email,
|
||
proxy=f"{proxy_host}:{proxy_port}:{proxy_user}:{proxy_pass}")
|
||
row = row[:5] + (proxy_host, proxy_port, proxy_user, proxy_pass) + row[9:] # 将代理信息加入到数据行中
|
||
else:
|
||
print(f"为账号 {account_email} 分配代理失败,跳过此账号。")
|
||
|
||
with write_lock:
|
||
try:
|
||
print("更新剩余可用IP")
|
||
proxy_manager.export_proxies("剩下的可用IP.txt")
|
||
except Exception as e:
|
||
print(f"导出代理时发生错误: {e}")
|
||
|
||
update_proxy_stats()
|
||
|
||
# 传递锁给子进程,并将当前账号数据传递给 main 函数
|
||
result = pool.apply_async(main, args=(row), callback=lambda result: progress_queue.put(result))
|
||
results.append(result)
|
||
|
||
|
||
for result in results:
|
||
try:
|
||
retry_row = result.get()
|
||
print(f"main函数返回的值: {retry_row}")
|
||
|
||
if not retry_row: # 如果返回False,表示任务失败
|
||
failed_count += 1
|
||
elif isinstance(retry_row, tuple): # 返回的是元组,表示需要重试
|
||
failed_count += 1 # 增加失败计数
|
||
else: # 返回的是单个 account,表示成功
|
||
completed_count += 1
|
||
# 更新进度条和显示的状态
|
||
progress_bar['value'] = completed_count
|
||
lbl_progress_status.config(text=f"完成:{completed_count}/{total_rows},失败:{failed_count}")
|
||
time.sleep(2) # 模拟耗时操作
|
||
|
||
except Exception as e:
|
||
print(f"任务执行时发生错误: {e}")
|
||
failed_count += 1
|
||
lbl_progress_status.config(text=f"完成:{completed_count}/{total_rows},失败:{failed_count}")
|
||
messagebox.showwarning("任务执行错误", f"任务执行时发生错误: {e}")
|
||
|
||
# 如果停止信号被触发,则中止
|
||
if stop_flag.is_set():
|
||
print("停止信号已接收,中止任务!")
|
||
break
|
||
|
||
# 重试逻辑,重新从数据库读取失败的数据
|
||
if retry_count < max_retries:
|
||
retry_count += 1
|
||
all_rows = read_data_from_db() # 重新从数据库读取失败的记录
|
||
else:
|
||
print("达到最大重试次数,停止重试。")
|
||
break
|
||
|
||
else:
|
||
print("所有任务已完成。")
|
||
messagebox.showinfo('运行结束', '所有任务已经完成')
|
||
|
||
def read_data_from_db() -> List[Tuple]:
|
||
"""
|
||
从数据库读取数据,同时保持与旧的接口和逻辑一致。
|
||
根据账号区域自动分配代理,并导入到数据库。
|
||
(去除代理分配和数据库更新代理信息的部分)
|
||
"""
|
||
# 初始化数据库管理器
|
||
skip_keywords = {"已更改", "接码", "被盗"}
|
||
data = []
|
||
|
||
# 从数据库中查询所有数据
|
||
accounts = db_manager.export_data()
|
||
|
||
global total_tasks, completed_tasks, failed_tasks # 全局变量以更新状态
|
||
|
||
# 计算任务数量
|
||
completed_tasks = sum(account.change_status in skip_keywords for account in accounts)
|
||
failed_tasks = len(accounts) - completed_tasks - sum(account.change_status is None for account in accounts)
|
||
total_tasks = len(accounts) - completed_tasks
|
||
|
||
for i, account in enumerate(accounts):
|
||
if not account.email:
|
||
break
|
||
if account.change_status not in skip_keywords:
|
||
# 不再分配代理,只是记录账号信息
|
||
data.append((
|
||
account.email, account.original_password, account.original_aux_email,
|
||
account.new_password, account.new_aux_email, "", "", "", "",account.region
|
||
))
|
||
|
||
return data
|
||
|
||
root = tk.Tk()
|
||
root.title("程序控制界面")
|
||
|
||
tk.Label(root, text="Excel 文件路径:").grid(row=0, column=0, padx=10, pady=10)
|
||
entry_file_path = tk.Entry(root, width=50)
|
||
entry_file_path.grid(row=0, column=1, padx=10, pady=10)
|
||
btn_select_file = tk.Button(root, text="选择文件", command=handle_file_select)
|
||
btn_select_file.grid(row=0, column=2, padx=10, pady=10)
|
||
|
||
tk.Label(root, text="Sheet 名称:").grid(row=1, column=0, padx=10, pady=10)
|
||
entry_sheet_name = tk.Entry(root, width=50)
|
||
entry_sheet_name.grid(row=1, column=1, padx=10, pady=10)
|
||
entry_sheet_name.insert(0, "修改邮箱密码和辅助邮箱")
|
||
|
||
tk.Label(root, text="并发数:").grid(row=2, column=0, padx=10, pady=10)
|
||
entry_concurrency = tk.Entry(root, width=50)
|
||
entry_concurrency.grid(row=2, column=1, padx=10, pady=10)
|
||
entry_concurrency.insert(0, str(multiprocessing.cpu_count()))
|
||
|
||
tk.Label(root, text="重试次数:").grid(row=3, column=0, padx=10, pady=10)
|
||
entry_retries = tk.Entry(root, width=50)
|
||
entry_retries.grid(row=3, column=1, padx=10, pady=10)
|
||
entry_retries.insert(0, "3")
|
||
|
||
# 进度条
|
||
progress_bar = ttk.Progressbar(root, orient="horizontal", length=400, mode="determinate")
|
||
progress_bar.grid(row=4, column=0, columnspan=2, padx=10, pady=10)
|
||
|
||
# 进度状态显示
|
||
lbl_progress_status = tk.Label(root, text="完成:0/0,失败:0")
|
||
lbl_progress_status.grid(row=5, column=0, columnspan=2, padx=10, pady=10)
|
||
|
||
btn_start = tk.Button(root, text="开始处理", command=start_processing)
|
||
btn_start.grid(row=6, column=0, columnspan=2, padx=20, pady=20, sticky="ew")
|
||
|
||
btn_stop = tk.Button(root, text="停止处理", command=stop_processing)
|
||
btn_stop.grid(row=7, column=0, columnspan=2, padx=20, pady=20, sticky="ew")
|
||
|
||
# 添加显示代理统计信息的标签
|
||
tk.Label(root, text="代理统计信息:").grid(row=8, column=0, padx=10, pady=10)
|
||
lbl_proxy_stats = tk.Label(root, text="代理统计信息未加载")
|
||
lbl_proxy_stats.grid(row=8, column=1, padx=5, pady=10, columnspan=2)
|
||
|
||
root.protocol("WM_DELETE_WINDOW", on_closing) # 绑定窗口关闭事件
|
||
root.mainloop()
|
||
|
||
|
||
|
||
# 示例运行方式
|
||
if __name__ == "__main__":
|
||
logs_dir = "logs"
|
||
|
||
# 确保 logs 文件夹存在,如果不存在则创建
|
||
if not os.path.exists(logs_dir):
|
||
os.makedirs(logs_dir)
|
||
|
||
# 清空 logs 文件夹中的所有文件
|
||
for filename in os.listdir("logs"):
|
||
file_path = os.path.join("logs", filename)
|
||
if os.path.isfile(file_path):
|
||
os.remove(file_path)
|
||
|
||
freeze_support()
|
||
run_gui()
|