gui.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. """
  4. @Contact : liuyuqi.gov@msn.cn
  5. @Time : 2024/12/19
  6. @License : Copyright © 2017-2022 liuyuqi. All Rights Reserved.
  7. @Desc : GUI界面入口
  8. """
  9. import tkinter as tk
  10. from tkinter import ttk, scrolledtext, filedialog, messagebox
  11. import threading
  12. import sys
  13. import os
  14. from collections import OrderedDict
  15. from searchdomain import SearchDomain, GenerateDomain, GenerateEnDomain
  16. from searchdomain.utils.frozen_dir import get_app_path
  17. class SearchDomainGUI:
  18. def __init__(self, root):
  19. self.root = root
  20. self.root.title("域名批量检索工具")
  21. self.root.geometry("800x700")
  22. self.root.resizable(True, True)
  23. # 设置应用路径
  24. self.app_path = get_app_path()
  25. # 创建主界面
  26. self.create_widgets()
  27. # 运行状态
  28. self.is_running = False
  29. def create_widgets(self):
  30. # 创建Notebook(标签页)
  31. notebook = ttk.Notebook(self.root)
  32. notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
  33. # 生成域名标签页
  34. self.generate_frame = ttk.Frame(notebook)
  35. notebook.add(self.generate_frame, text="生成域名")
  36. self.create_generate_tab()
  37. # 搜索域名标签页
  38. self.search_frame = ttk.Frame(notebook)
  39. notebook.add(self.search_frame, text="搜索域名")
  40. self.create_search_tab()
  41. # 日志输出区域(共用)
  42. log_frame = ttk.LabelFrame(self.root, text="运行日志")
  43. log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
  44. self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD)
  45. self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
  46. # 状态栏
  47. self.status_var = tk.StringVar(value="就绪")
  48. status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN)
  49. status_bar.pack(side=tk.BOTTOM, fill=tk.X)
  50. def create_generate_tab(self):
  51. # 语言选择
  52. lang_frame = ttk.LabelFrame(self.generate_frame, text="语言设置")
  53. lang_frame.pack(fill=tk.X, padx=10, pady=5)
  54. self.lang_var = tk.StringVar(value="en")
  55. ttk.Radiobutton(lang_frame, text="中文", variable=self.lang_var, value="zh").pack(side=tk.LEFT, padx=10, pady=5)
  56. ttk.Radiobutton(lang_frame, text="英文", variable=self.lang_var, value="en").pack(side=tk.LEFT, padx=10, pady=5)
  57. # 关键词输入
  58. keyword_frame = ttk.LabelFrame(self.generate_frame, text="关键词(用逗号分隔)")
  59. keyword_frame.pack(fill=tk.X, padx=10, pady=5)
  60. self.keyword_entry = ttk.Entry(keyword_frame, width=50)
  61. self.keyword_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)
  62. self.keyword_entry.insert(0, "gpt,go,ai")
  63. # 域名后缀
  64. domain_frame = ttk.LabelFrame(self.generate_frame, text="域名后缀(用逗号分隔)")
  65. domain_frame.pack(fill=tk.X, padx=10, pady=5)
  66. self.domain_entry = ttk.Entry(domain_frame, width=50)
  67. self.domain_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)
  68. self.domain_entry.insert(0, "com,cn,io")
  69. # 位置选择
  70. position_frame = ttk.LabelFrame(self.generate_frame, text="关键词位置")
  71. position_frame.pack(fill=tk.X, padx=10, pady=5)
  72. self.position_var = tk.StringVar(value="prefix")
  73. ttk.Radiobutton(position_frame, text="前缀", variable=self.position_var, value="prefix").pack(side=tk.LEFT, padx=10, pady=5)
  74. ttk.Radiobutton(position_frame, text="后缀", variable=self.position_var, value="suffix").pack(side=tk.LEFT, padx=10, pady=5)
  75. # 输出文件
  76. output_frame = ttk.LabelFrame(self.generate_frame, text="输出文件")
  77. output_frame.pack(fill=tk.X, padx=10, pady=5)
  78. self.generate_output_entry = ttk.Entry(output_frame, width=40)
  79. self.generate_output_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)
  80. self.generate_output_entry.insert(0, "domain.txt")
  81. ttk.Button(output_frame, text="浏览", command=self.browse_generate_output).pack(side=tk.LEFT, padx=5, pady=5)
  82. # 生成按钮
  83. button_frame = ttk.Frame(self.generate_frame)
  84. button_frame.pack(fill=tk.X, padx=10, pady=10)
  85. self.generate_button = ttk.Button(button_frame, text="开始生成", command=self.start_generate)
  86. self.generate_button.pack(side=tk.LEFT, padx=5)
  87. ttk.Button(button_frame, text="清空日志", command=self.clear_log).pack(side=tk.LEFT, padx=5)
  88. def create_search_tab(self):
  89. # 输入文件
  90. input_frame = ttk.LabelFrame(self.search_frame, text="输入文件(域名列表)")
  91. input_frame.pack(fill=tk.X, padx=10, pady=5)
  92. self.input_entry = ttk.Entry(input_frame, width=40)
  93. self.input_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)
  94. self.input_entry.insert(0, "domain.txt")
  95. ttk.Button(input_frame, text="浏览", command=self.browse_input_file).pack(side=tk.LEFT, padx=5, pady=5)
  96. # 输出文件
  97. output_frame = ttk.LabelFrame(self.search_frame, text="输出文件(结果)")
  98. output_frame.pack(fill=tk.X, padx=10, pady=5)
  99. self.search_output_entry = ttk.Entry(output_frame, width=40)
  100. self.search_output_entry.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)
  101. self.search_output_entry.insert(0, "result.txt")
  102. ttk.Button(output_frame, text="浏览", command=self.browse_search_output).pack(side=tk.LEFT, padx=5, pady=5)
  103. # 导出选项
  104. export_frame = ttk.LabelFrame(self.search_frame, text="导出选项")
  105. export_frame.pack(fill=tk.X, padx=10, pady=5)
  106. self.export_all_var = tk.BooleanVar(value=False)
  107. ttk.Checkbutton(export_frame, text="导出所有域名(包括不可用的)", variable=self.export_all_var).pack(side=tk.LEFT, padx=10, pady=5)
  108. # 搜索按钮
  109. button_frame = ttk.Frame(self.search_frame)
  110. button_frame.pack(fill=tk.X, padx=10, pady=10)
  111. self.search_button = ttk.Button(button_frame, text="开始搜索", command=self.start_search)
  112. self.search_button.pack(side=tk.LEFT, padx=5)
  113. self.cancel_search_button = ttk.Button(button_frame, text="终止", command=self.cancel_search, state=tk.DISABLED)
  114. self.cancel_search_button.pack(side=tk.LEFT, padx=5)
  115. ttk.Button(button_frame, text="清空日志", command=self.clear_log).pack(side=tk.LEFT, padx=5)
  116. def browse_input_file(self):
  117. filename = filedialog.askopenfilename(
  118. title="选择输入文件",
  119. filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
  120. )
  121. if filename:
  122. self.input_entry.delete(0, tk.END)
  123. self.input_entry.insert(0, filename)
  124. def browse_search_output(self):
  125. filename = filedialog.asksaveasfilename(
  126. title="选择输出文件",
  127. defaultextension=".txt",
  128. filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
  129. )
  130. if filename:
  131. self.search_output_entry.delete(0, tk.END)
  132. self.search_output_entry.insert(0, filename)
  133. def browse_generate_output(self):
  134. filename = filedialog.asksaveasfilename(
  135. title="选择输出文件",
  136. defaultextension=".txt",
  137. filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
  138. )
  139. if filename:
  140. self.generate_output_entry.delete(0, tk.END)
  141. self.generate_output_entry.insert(0, filename)
  142. def log(self, message):
  143. """添加日志"""
  144. self.log_text.insert(tk.END, message + "\n")
  145. self.log_text.see(tk.END)
  146. self.root.update_idletasks()
  147. def clear_log(self):
  148. """清空日志"""
  149. self.log_text.delete(1.0, tk.END)
  150. def start_generate(self):
  151. """开始生成域名"""
  152. if self.is_running:
  153. messagebox.showwarning("警告", "任务正在运行中,请等待完成")
  154. return
  155. # 获取参数
  156. lang = self.lang_var.get()
  157. keyword = self.keyword_entry.get().strip()
  158. domain = self.domain_entry.get().strip()
  159. position = self.position_var.get()
  160. output = self.generate_output_entry.get().strip()
  161. if not keyword:
  162. messagebox.showerror("错误", "请输入关键词")
  163. return
  164. if not domain:
  165. messagebox.showerror("错误", "请输入域名后缀")
  166. return
  167. if not output:
  168. messagebox.showerror("错误", "请输入输出文件名")
  169. return
  170. # 准备参数
  171. params = OrderedDict({
  172. "lang": lang,
  173. "keyword": keyword,
  174. "domain": domain,
  175. "position": position,
  176. "app_path": self.app_path
  177. })
  178. # 更新输出文件路径为绝对路径
  179. if not os.path.isabs(output):
  180. output = os.path.join(self.app_path, output)
  181. else:
  182. params["app_path"] = os.path.dirname(output)
  183. # 在后台线程中运行
  184. self.is_running = True
  185. self.generate_button.config(state=tk.DISABLED)
  186. self.status_var.set("正在生成域名...")
  187. thread = threading.Thread(target=self._generate_thread, args=(params, lang))
  188. thread.daemon = True
  189. thread.start()
  190. def _generate_thread(self, params, lang):
  191. """生成域名的后台线程"""
  192. try:
  193. self.log(f"开始生成域名...")
  194. self.log(f"语言: {lang}, 关键词: {params['keyword']}, 域名后缀: {params['domain']}, 位置: {params['position']}")
  195. if lang == "en":
  196. generateDomain = GenerateEnDomain(params=params)
  197. else:
  198. generateDomain = GenerateDomain(params=params)
  199. generateDomain.run()
  200. output_file = params.get("output", "domain.txt")
  201. self.log("域名生成完成!")
  202. self.log(f"结果已保存到: {output_file}")
  203. messagebox.showinfo("成功", f"域名生成完成!\n结果已保存到: {output_file}")
  204. except Exception as e:
  205. error_msg = f"生成域名时出错: {str(e)}"
  206. self.log(error_msg)
  207. messagebox.showerror("错误", error_msg)
  208. finally:
  209. self.is_running = False
  210. self.root.after(0, lambda: self.generate_button.config(state=tk.NORMAL))
  211. self.root.after(0, lambda: self.status_var.set("就绪"))
  212. def start_search(self):
  213. """开始搜索域名"""
  214. if self.is_running:
  215. messagebox.showwarning("警告", "任务正在运行中,请等待完成")
  216. return
  217. # 获取参数
  218. input_file = self.input_entry.get().strip()
  219. output_file = self.search_output_entry.get().strip()
  220. export_all = self.export_all_var.get()
  221. if not input_file:
  222. messagebox.showerror("错误", "请输入输入文件名")
  223. return
  224. if not output_file:
  225. messagebox.showerror("错误", "请输入输出文件名")
  226. return
  227. # 检查输入文件是否存在
  228. if not os.path.isabs(input_file):
  229. input_file = os.path.join(self.app_path, input_file)
  230. if not os.path.exists(input_file):
  231. messagebox.showerror("错误", f"输入文件不存在: {input_file}")
  232. return
  233. # 准备输出文件路径
  234. if not os.path.isabs(output_file):
  235. output_file = os.path.join(self.app_path, output_file)
  236. # 确保输出目录存在
  237. output_dir = os.path.dirname(output_file)
  238. if output_dir and not os.path.exists(output_dir):
  239. try:
  240. os.makedirs(output_dir, exist_ok=True)
  241. except Exception as e:
  242. messagebox.showerror("错误", f"无法创建输出目录: {output_dir}\n{str(e)}")
  243. return
  244. # 准备参数
  245. # app_path 应该设置为输出文件的目录,因为 saveRes 使用 app_path + output
  246. params = OrderedDict({
  247. "input": input_file, # 使用完整路径
  248. "output": os.path.basename(output_file), # 只使用文件名
  249. "app_path": os.path.dirname(output_file) # 输出文件的目录
  250. })
  251. # 在后台线程中运行
  252. self.is_running = True
  253. self.search_button.config(state=tk.DISABLED)
  254. self.cancel_search_button.config(state=tk.NORMAL)
  255. self.status_var.set("正在搜索域名...")
  256. thread = threading.Thread(target=self._search_thread, args=(params, export_all))
  257. thread.daemon = True
  258. thread.start()
  259. def cancel_search(self):
  260. """取消搜索任务"""
  261. if self.search_domain_instance and self.is_running:
  262. self.search_domain_instance.cancel()
  263. self.log("正在终止搜索任务...")
  264. self.status_var.set("正在终止...")
  265. def _search_thread(self, params, export_all):
  266. """搜索域名的后台线程"""
  267. try:
  268. self.log(f"开始搜索域名...")
  269. self.log(f"输入文件: {params['input']}")
  270. self.log(f"输出目录: {params['app_path']}")
  271. self.log(f"输出文件: {params['output']}")
  272. self.log(f"导出所有: {export_all}")
  273. # 传入日志回调函数,将搜索进度显示在运行日志中
  274. self.search_domain_instance = SearchDomain(params=params, debug=True, export_all=export_all, log_callback=self.log)
  275. self.search_domain_instance.run()
  276. # 检查是否被取消
  277. if self.search_domain_instance._cancelled:
  278. self.log("搜索任务已取消")
  279. self.root.after(0, lambda: messagebox.showinfo("提示", "搜索任务已取消"))
  280. else:
  281. self.log("域名搜索完成!")
  282. output_path = os.path.join(params["app_path"], params["output"])
  283. self.log(f"结果已保存到: {output_path}")
  284. # 验证文件是否真的存在
  285. if os.path.exists(output_path):
  286. file_size = os.path.getsize(output_path)
  287. self.log(f"文件大小: {file_size} 字节")
  288. self.root.after(0, lambda: messagebox.showinfo("成功", f"域名搜索完成!\n结果已保存到: {output_path}\n文件大小: {file_size} 字节"))
  289. else:
  290. error_msg = f"警告:输出文件不存在: {output_path}"
  291. self.log(error_msg)
  292. self.root.after(0, lambda: messagebox.showwarning("警告", error_msg))
  293. except Exception as e:
  294. error_msg = f"搜索域名时出错: {str(e)}"
  295. self.log(error_msg)
  296. import traceback
  297. self.log(traceback.format_exc())
  298. self.root.after(0, lambda: messagebox.showerror("错误", error_msg))
  299. finally:
  300. self.is_running = False
  301. self.search_domain_instance = None
  302. self.root.after(0, lambda: self.search_button.config(state=tk.NORMAL))
  303. self.root.after(0, lambda: self.cancel_search_button.config(state=tk.DISABLED))
  304. self.root.after(0, lambda: self.status_var.set("就绪"))
  305. def main():
  306. root = tk.Tk()
  307. app = SearchDomainGUI(root)
  308. root.mainloop()
  309. if __name__ == "__main__":
  310. main()