[code]import tkinter as tk
from tkinter import ttk, scrolledtext,messagebox
import os
import requests
from bs4 import BeautifulSoup
import re
from urllib.parse import urlparse
import json
class NovelReader:
def __init__(self, rt):
self.root = rt
self.root.title("小说阅读器")
# 获取屏幕宽度和高度
screen_width = rt.winfo_screenwidth()
screen_height = rt.winfo_screenheight()
self.width = 1440
self.height = 900
# 计算窗口左上角的位置,使得窗口居中
position_top = int(screen_height / 2 - self.height / 2)
position_right = int(screen_width / 2 - self.width / 2)
# 设置窗口位置和大小
self.root.geometry(f'{self.width}x{self.height}+{position_right}+{position_top}')
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
self.url_prefix_p = []
self.soup = None
= {}
self.url_prefix_show_txt = []
# 创建表格
self.tree_scroll = ttk.Scrollbar(self.root, orient=tk.VERTICAL)
self.tree_dictionary = ttk.Treeview(root, columns=('Key', 'Value'), show='headings', height=10,
yscrollcommand=self.tree_scroll.set)
self.create_text()
self.create_treeview()
self.load_data()
self.create_widgets()
self.create_context_menu()
# 绑定全局左键点击事件以取消菜单
self.root.bind("<Button-1>", self.hide_context_menu)
()
self.refresh_path()
self.root.protocol("WM_DELETE_WINDOW", self.quit_ip)
# 绑定窗口的关闭事件
def net_path_dictionary(self):
self.tree_scroll.configure(command=self.tree_dictionary.yview)
self.tree_dictionary.heading('Key', text='路径')
self.tree_dictionary.heading('Value', text='域名')
self.tree_dictionary.column("Key", width=250)
self.tree_dictionary.column("Value", width=353)
self.tree_dictionary.place(x=810, y=360)
self.tree_scroll.place(x=1416, y=360, height=222, anchor='nw')
def refresh_path(self):
# 清空表格
for row in self.tree_dictionary.get_children():
self.tree_dictionary.delete(row)
# 填充数据
for key, value in ():
self.tree_dictionary.insert('', "end", values=(key, value))
def add_path(self):
self.add_path_button.configure(state="disabled")
# 弹出窗口添加新条目
self.add_window2 = tk.Toplevel(self.root)
self.add_window2.title("添加路径")
self.add_window2.geometry("520x200+290+180")
# 创建Entry组件
self.path_entry1 = tk.Entry(self.add_window2, width=60)
self.domain_name_entry2 = tk.Entry(self.add_window2, width=60)
path_label1 = tk.Label(self.add_window2, text="路径:")
path_label1.grid(row=0, column=0, padx=5, pady=5)
domain_name_label2 = tk.Label(self.add_window2, text="域名:")
domain_name_label2.grid(row=1, column=0, padx=5, pady=5)
self.path_entry1.grid(row=0, column=1, padx=5, pady=5)
self.domain_name_entry2.grid(row=1, column=1, padx=5, pady=5)
# 为右键菜单绑定事件(这里以entry1为例,其他entry类似)
self.path_entry1.bind("<Button-3>", self.show_context_menu)
self.domain_name_entry2.bind("<Button-3>", self.show_context_menu)
def add_entry():
key = self.path_entry1.get()
value = self.domain_name_entry2.get()
if key and value:
[key] = value
self.tree_dictionary.insert('', 'end', values=(key, value))
self.add_path_button.configure(state="normal")
self.add_window2.destroy()
def cancel():
self.add_path_button.configure(state="normal")
self.add_window2.destroy()
# 绑定窗口关闭事件
self.add_window2.protocol("WM_DELETE_WINDOW", cancel)
ttk.Button(self.add_window2, text="确定", command=add_entry, cursor='hand2').place(x=300,y=150)
ttk.Button(self.add_window2, text="取消", command=cancel, cursor='hand2').place(x=400,y=150)
def save_path(self):
# 保存数据到文件
with open('net_dress.json', 'w', encoding='utf-8') as f:
json.dump(, f, ensure_ascii=False, indent=4)
tk.messagebox.showinfo("Info", "数据已保存.")
def deleted_path(self):
# 删除选中的条目
selected_item = self.tree_dictionary.selection()
if not selected_item:
return
key = self.tree_dictionary.item(selected_item, 'values')[0]
del [key]
self.refresh_path()
def add_url_p(self):
self.add_url_button.configure(state="disabled")
# 弹出窗口添加新条目
self.add_window3 = tk.Toplevel(self.root)
self.add_window3.title("添加域名")
self.add_window3.geometry("520x200+290+180")
# 创建Entry组件
url_entry1 = tk.Entry(self.add_window3, width=60)
url_entry1.grid(row=0, column=1, padx=5, pady=5)
url_entry1.bind("<Button-3>", self.show_context_menu)
def add_entry():
url = url_entry1.get()
if url:
self.url_prefix_p.append(url)
self.listbox_p.insert(tk.END, url)
url_entry1.delete(0, tk.END)
self.add_window3.destroy()
self.add_url_button.configure(state="normal")
else:
messagebox.showwarning("提示", "请输入有效的 URL!")
def cancel():
self.add_url_button.configure(state="normal")
self.add_window3.destroy()
# 绑定窗口关闭事件
self.add_window3.protocol("WM_DELETE_WINDOW", cancel)
ttk.Button(self.add_window3, text="确定", command=add_entry, cursor='hand2').place(x=300,y=150)
ttk.Button(self.add_window3, text="取消", command=cancel, cursor='hand2').place(x=400,y=150)
def save_url_p(self):
# 保存数据到文件
with open('url_prefix_p.json', 'w', encoding='utf-8') as f:
json.dump(self.url_prefix_p, f, ensure_ascii=False, indent=4)
tk.messagebox.showinfo("Info", "数据已保存.")
def delete_url_p(self):
selected_index = self.listbox_p.curselection()
if selected_index:
self.url_prefix_p.clear()
self.listbox_p.delete(selected_index[0])
for idx in range(self.listbox_p.size()):
self.url_prefix_p.append(self.listbox_p.get(idx))
else:
messagebox.showwarning("提示", "请选择要删除的 URL!")
def add_url_show_txt(self):
self.add_url_button.configure(state="disabled")
# 弹出窗口添加新条目
self.add_window4 = tk.Toplevel(self.root)
self.add_window4.title("添加域名")
self.add_window4.geometry("520x200+290+180")
# 创建Entry组件
url_entry1 = tk.Entry(self.add_window4, width=60)
url_entry1.grid(row=0, column=1, padx=5, pady=5)
url_entry1.bind("<Button-3>", self.show_context_menu)
def add_entry():
url = url_entry1.get()
if url:
self.url_prefix_show_txt.append(url)
self.listbox_show_txt.insert(tk.END, url)
url_entry1.delete(0, tk.END)
self.add_window4.destroy()
self.add_url_button.configure(state="normal")
else:
messagebox.showwarning("提示", "请输入有效的 URL!")
def cancel():
self.add_url_button.configure(state="normal")
self.add_window4.destroy()
# 绑定窗口关闭事件
self.add_window4.protocol("WM_DELETE_WINDOW", cancel)
ttk.Button(self.add_window4, text="确定", command=add_entry, cursor='hand2').place(x=300,y=150)
ttk.Button(self.add_window4, text="取消", command=cancel, cursor='hand2').place(x=400,y=150)
def save_url_show_txt(self):
# 保存数据到文件
with open('url_prefix_show_txt.json', 'w', encoding='utf-8') as f:
json.dump(self.url_prefix_show_txt, f, ensure_ascii=False, indent=4)
tk.messagebox.showinfo("Info", "数据已保存.")
def delete_url_show_txt(self):
selected_index = self.listbox_show_txt.curselection()
if selected_index:
self.url_prefix_show_txt.clear()
self.listbox_show_txt.delete(selected_index[0])
for idx in range(self.listbox_show_txt.size()):
self.url_prefix_show_txt.append(self.listbox_p.get(idx))
else:
messagebox.showwarning("提示", "请选择要删除的 URL!")
def add_bookmark(self):
self.add_button.configure(state="disabled")
# 弹出窗口添加新条目
self.add_window = tk.Toplevel(self.root)
self.add_window.title("添加书签")
self.add_window.geometry("520x200+290+180")
# 创建Entry组件
self.title_entry1 = tk.Entry(self.add_window, width=60)
self.chapter_entry2 = tk.Entry(self.add_window, width=60)
self.dress_entry3 = tk.Entry(self.add_window, width=60)
title_label1 = tk.Label(self.add_window, text="小说名称:")
title_label1.grid(row=0, column=0, padx=5, pady=5)
chapter_label2 = tk.Label(self.add_window, text="章节:")
chapter_label2.grid(row=1, column=0, padx=5, pady=5)
dress_label3 = tk.Label(self.add_window, text="URL:")
dress_label3.grid(row=2, column=0, padx=5, pady=5)
self.title_entry1.grid(row=0, column=1, padx=5, pady=5)
self.chapter_entry2.grid(row=1, column=1, padx=5, pady=5)
self.dress_entry3.grid(row=2, column=1, padx=5, pady=5)
# 为右键菜单绑定事件(这里以entry1为例,其他entry类似)
self.title_entry1.bind("<Button-3>", self.show_context_menu)
self.chapter_entry2.bind("<Button-3>", self.show_context_menu)
self.dress_entry3.bind("<Button-3>", self.show_context_menu)
def cancel():
self.add_button.configure(state="normal")
self.add_window.destroy()
# 绑定窗口关闭事件
self.add_window.protocol("WM_DELETE_WINDOW", cancel)
ttk.Button(self.add_window, text="确定", command=self.add_reading, cursor='hand2').place(x=300,y=150)
ttk.Button(self.add_window, text="取消", command=cancel, cursor='hand2').place(x=400,y=150)
def create_widgets(self):
# 创建Entry组件
# 书签
self.add_button = ttk.Button(self.root, text="添加", command=self.add_bookmark, cursor='hand2')
save_button = ttk.Button(self.root, text="保存", command=lambda:self.save_reading(sv=1), cursor='hand2')
delete_button = ttk.Button(self.root, text="删除", command=self.delete, cursor='hand2')
open_button = ttk.Button(self.root, text="打开", command=self.open_reading, cursor='hand2')
self.add_button.place(x=920, y=320)
save_button.place(x=1000, y=320)
delete_button.place(x=1080, y=320)
open_button.place(x=1160, y=320)
self.previous_chapter_button = ttk.Button(self.root, text="上一章", command=self.previous_chapter, cursor='hand2')
self.next_chapter_button = ttk.Button(self.root, text="下一章", command=self.next_chapter, cursor='hand2')
self.bookmark_button = ttk.Button(self.root, text="添加书签", command=self.bookmark,cursor='hand2')
self.previous_chapter_button.place(x=200,y=792)
self.next_chapter_button.place(x=400,y=792)
self.bookmark_button.place(x=600,y=792)
# 路径
self.add_path_button = ttk.Button(self.root, text="添加", command=self.add_path, cursor='hand2')
save_path_button = ttk.Button(self.root, text="保存", command=self.save_path, cursor='hand2')
delete_path_button = ttk.Button(self.root, text="删除", command=self.deleted_path, cursor='hand2')
self.add_path_button.place(x=920, y=600)
save_path_button.place(x=1000, y=600)
delete_path_button.place(x=1080, y=600)
# url
# 列表框显示 URL_p
self.listbox_p = tk.Listbox(self.root, width=40, height=6)
scrollbar_p = tk.Scrollbar(self.root, orient="vertical", command=self.listbox_p.yview)
self.listbox_p.config(yscrollcommand=scrollbar_p.set)
# 布局
self.listbox_p.place(x=810,y=660)
scrollbar_p.place(x=1092,y=660, height=125)
# 填充列表框
for url in self.url_prefix_p:
self.listbox_p.insert(tk.END, url)
# 列表框显示 URL_show_txt
self.listbox_show_txt = tk.Listbox(self.root, width=40, height=6)
scrollbar_show_txt = tk.Scrollbar(self.root, orient="vertical", command=self.listbox_show_txt.yview)
self.listbox_show_txt.config(yscrollcommand=scrollbar_p.set)
# 布局
self.listbox_show_txt.place(x=1109,y=660)
scrollbar_show_txt.place(x=1380,y=660, height=125)
# 填充列表框
for url in self.url_prefix_show_txt:
self.listbox_show_txt.insert(tk.END, url)
self.add_url_button = ttk.Button(self.root, text="添加", command=lambda: self.on_button_click("add"), cursor='hand2')
save_url_button = ttk.Button(self.root, text="保存", command=lambda: self.on_button_click("save"), cursor='hand2')
delete_url_button = ttk.Button(self.root, text="删除", command=lambda: self.on_button_click("delete"), cursor='hand2')
self.add_url_button.place(x=920, y=800)
save_url_button.place(x=1000, y=800)
delete_url_button.place(x=1080, y=800)
ttk.Label(self.root, text="章节标签为p的域名:").place(x=810,y=639)
ttk.Label(self.root, text="章节标签为show_txt的域名:").place(x=1109,y=639)
def on_button_click(self, action):
focused_widget = self.root.focus_get()
print(focused_widget)
if self.listbox_p.curselection():
if action == "add":
self.add_url_p()
elif action == "save":
self.save_url_p()
elif action == "delete":
self.delete_url_p()
elif self.listbox_show_txt.curselection():
if action == "add":
self.add_url_show_txt()
elif action == "save":
self.save_url_show_txt()
elif action == "delete":
self.delete_url_show_txt()
else:
messagebox.showwarning("提示", "请先选中一个列表框!")
def create_context_menu(self):
# 创建右键菜单
self.context_menu = tk.Menu(self.root, tearoff=0)
self.context_menu.add_command(label="剪切", command=self.cut_text)
self.context_menu.add_command(label="复制", command=self.copy_text)
self.context_menu.add_command(label="粘贴", command=self.paste_text)
self.context_menu.add_command(label="全选", command=self.select_all_text)
# 添加“取消菜单”选项
self.context_menu.add_separator()
self.context_menu.add_command(label="取消菜单", command=lambda: self.context_menu.unpost())
def show_context_menu(self, event):
self.context_menu.post(event.x_root, event.y_root)
def cut_text(self):
try:
self.root.focus_get().event_generate("<<Cut>>")
except Exception as e:
print(f"剪切错误: {e}")
def copy_text(self):
try:
self.root.focus_get().event_generate("<<Copy>>")
except Exception as e:
print(f"复制错误: {e}")
def paste_text(self):
try:
self.root.focus_get().event_generate("<<Paste>>")
except Exception as e:
print(f"粘贴错误: {e}")
def select_all_text(self):
try:
self.root.focus_get().select_range(0, tk.END)
except Exception as e:
print(f"全选错误: {e}")
def hide_context_menu(self, event):
# 如果当前有菜单显示,并且点击的不是菜单项,则取消它
if hasattr(self, 'context_menu') and self.context_menu.winfo_exists():
# 检查点击是否发生在菜单上(可选,根据需求决定是否保留)
# 如果不保留此检查,则任何左键点击都会取消菜单
self.context_menu.unpost()
def load_data(self):
if os.path.isfile("reading.txt"):
with open('reading.txt', 'r', encoding='utf-8', newline='') as fread:
for line in fread:
original_string = line.strip()
parts = original_string.split(',')
first_part = parts[0].strip("()'")
second_part = parts[1].strip("'()' ")
third_part = parts[2].strip("'()' ")
self.gride.insert('', 'end', values=(first_part, second_part, third_part))
if os.path.isfile("net_dress.json"):
with open('net_dress.json', 'r', encoding='utf-8') as f:
= json.load(f)
if os.path.isfile("url_prefix_p.json"):
with open('url_prefix_p.json', 'r', encoding='utf-8') as f:
self.url_prefix_p = json.load(f)
else:
self.url_prefix_p = [
"https://www., "https://www.,
"https://www., "https://www.,
"https://350.ooo/", "https://www., "https://www.
]
if os.path.isfile("url_prefix_show_txt.json"):
with open('url_prefix_show_txt.json', 'r', encoding='utf-8') as f:
self.url_prefix_show_txt = json.load(f)
else:
self.url_prefix_show_txt = [
"https://www.skjvvx.cc/", "https://www., "https://www.balshuzhai.cc/"
]
def create_text(self):
# 创建多行文本
self.text = scrolledtext.ScrolledText(
self.root, width=60, height=25, font=("Arial", 18), fg="white", bg="black",wrap=tk.WORD)
self.text.place(x=10, y=74)
# text.config(state=tk.DISABLED)
self.label1 = tk.Label(self.root, text='
')
self.label1.place(x=60, y=10, anchor='nw')
self.label2 = tk.Label(self.root, text='
', width=53, font=("Arial", 20), fg="red", relief='groove')
#, bg="black"
self.label2.place(x=12, y=40, anchor='nw')
self.label_dress = tk.Label(self.root, text='地址:', width=60, anchor='nw')
self.label_dress.place(x=700, y=10, anchor='nw')
def create_treeview(self):
# 创建Treeview组件和Scrollbar
self.fscroll = ttk.Scrollbar(self.root, orient=tk.VERTICAL)
self.gride = ttk.Treeview(self.root, columns=('c1', 'c2', 'c3'), show='headings', height=12, yscrollcommand=self.fscroll.set)
self.gride.place(x=810, y=40, anchor='nw')
self.fscroll.configure(command=self.gride.yview)
self.fscroll.place(x=1420, y=40, height=320, anchor='nw')
# 设置Treeview列宽和标题
self.gride.column("c1", width=180)
self.gride.column("c2", width=190)
self.gride.column("c3", width=230)
self.gride.heading("c1", text="文章名称")
self.gride.heading("c2", text="书签")
self.gride.heading("c3", text="URL")
self.gride.bind("<Double-1>", self.open_reading)
def add_reading(self):
r1 = self.title_entry1.get().rstrip()
r2 = self.chapter_entry2.get().rstrip()
r3 = self.dress_entry3.get().rstrip()
if r1 and r2 and len(r1) > 1 and len(r2) > 1:
self.gride.insert('', 'end', values=(r1, r2, r3))
self.title_entry1.delete(0, tk.END)
self.chapter_entry2.delete(0, tk.END)
self.dress_entry3.delete(0, tk.END)
self.add_window.destroy()
self.add_button.configure(state="normal")
else:
messagebox.showwarning("警告", "请输入内容!")
def save_reading(self,sv):
with open('reading.txt', 'w', encoding='utf-8', newline='') as f_read:
item_ids = self.gride.get_children()
for item_id in item_ids:
values = self.gride.item(item_id, "values")
s = ','.join(map(str, values))
# 将列表转换为逗号分隔的字符串
f_read.write(s + "\r\n")
if sv == 1:
messagebox.showinfo("提示", "数据已保存!")
def delete(self):
for selected_item in self.gride.selection():
self.gride.delete(selected_item)
def open_reading(self, event=None):
selected_items = self.gride.selection()
if selected_items:
values = self.gride.item(selected_items[0], "values")
if len(values) >= 3:
# 确保有足够的列
url = values[2].strip()
# 直接获取第三列的值
# self.get_chapter(url)
self.get_chapter(values[2].strip())