Skip to content

Commit 1c32066

Browse files
authored
大幅精简
1 parent 0f7a336 commit 1c32066

1 file changed

Lines changed: 86 additions & 153 deletions

File tree

bili_ticket_monitor.py

Lines changed: 86 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -11,206 +11,139 @@
1111
from tabulate import tabulate
1212
from wcwidth import wcswidth
1313

14-
# 全局禁用SSL警告
1514
disable_warnings(InsecureRequestWarning)
16-
requests.packages.urllib3.disable_warnings() # pylint: disable=no-member
17-
15+
requests.packages.urllib3.disable_warnings()
1816
init(autoreset=True)
1917

20-
21-
class Config: # pylint: disable=too-few-public-methods
22-
"""项目配置类,包含API相关参数和请求头设置
23-
24-
Attributes:
25-
TICKET_ID: 票务项目ID
26-
REFRESH_INTERVAL: 刷新间隔(秒)
27-
TIMEOUT: 请求超时时间
28-
MAX_RETRIES: 最大重试次数
29-
API_BASE_URL: 基础API地址
30-
HEADERS: 请求头配置
31-
"""
32-
TICKET_ID = "请替换这里"
33-
REFRESH_INTERVAL = 1
34-
TIMEOUT = 50
35-
MAX_RETRIES = 3
36-
API_BASE_URL = "https://show.bilibili.com/api/ticket/project/getV2"
18+
class Config:
19+
TICKET_ID = "请替换这里" # 票务ID
20+
REFRESH_INTERVAL = 1 # 刷新间隔
21+
TIMEOUT = 50 # 请求超时时间
22+
API_URL = f"https://show.bilibili.com/api/ticket/project/getV2?version=134&id={TICKET_ID}"
3723
HEADERS = {
3824
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 "
39-
"(KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36"
25+
"(KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36"
4026
}
4127

42-
43-
class StatusColor: # pylint: disable=too-few-public-methods
44-
"""票务状态颜色映射配置"""
45-
MAPPING = {
46-
"已售罄": Fore.RED,
47-
"已停售": Fore.RED,
48-
"不可售": Fore.RED,
49-
"未开售": Fore.RED,
50-
"暂时售罄": Fore.YELLOW,
51-
"预售中": Fore.GREEN,
28+
class StatusColor:
29+
COLOR_MAP = {
30+
"已售罄": Fore.RED, "已停售": Fore.RED, "不可售": Fore.RED,
31+
"未开售": Fore.RED, "暂时售罄": Fore.YELLOW, "预售中": Fore.GREEN
5232
}
5333
DEFAULT = Fore.WHITE
5434

55-
56-
def clear_screen() -> None:
57-
"""清空控制台屏幕"""
35+
def clear_screen():
5836
print("\033c", end="")
5937

60-
61-
def build_api_url() -> str:
62-
"""构建API请求地址"""
63-
return f"{Config.API_BASE_URL}?version=134&id={Config.TICKET_ID}"
64-
65-
66-
def process_response_data(json_data: dict) -> Tuple[Optional[str], Optional[List[List[str]]]]:
67-
"""处理API响应数据
68-
69-
Args:
70-
json_data: 原始JSON响应数据
71-
72-
Returns:
73-
Tuple: (项目名称, 票务信息列表)
74-
"""
38+
def process_data(json_data: dict) -> Tuple[Optional[str], Optional[List[List[str]]]]:
7539
data = json_data.get('data', {})
7640
if not data:
7741
return None, None
7842

79-
project_name = data.get('name', '')
80-
tickets = []
81-
for screen in data.get('screen_list', []):
82-
for ticket in screen.get('ticket_list', []):
83-
tickets.append([
84-
f"{ticket.get('screen_name', '')} {ticket.get('desc', '')}",
85-
ticket.get('sale_flag', {}).get('display_name', '')
86-
])
87-
return project_name, tickets if tickets else None
88-
43+
name = data.get('name', '')
44+
tickets = [
45+
[f"{t.get('screen_name', '')} {t.get('desc', '')}",
46+
t.get('sale_flag', {}).get('display_name', '')]
47+
for screen in data.get('screen_list', [])
48+
for t in screen.get('ticket_list', [])
49+
]
50+
return name, tickets or None
8951

90-
def display_table(name: str, tickets: List[List[str]], pause_event: threading.Event = None) -> None:
91-
"""显示票务状态表格
92-
93-
Args:
94-
name: 项目名称
95-
tickets: 票务数据
96-
pause_event: 线程暂停事件
97-
"""
52+
def show_table(name: str, tickets: List[List[str]], pause_event: threading.Event = None):
9853
if pause_event:
9954
pause_event.set()
10055

101-
max_desc = max(wcswidth(row[0]) for row in tickets)
102-
max_status = max(len(row[1]) for row in tickets)
56+
col1 = max(wcswidth(row[0]) for row in tickets)
57+
col2 = max(len(row[1]) for row in tickets)
10358

104-
table_data = [
105-
[row[0].ljust(max_desc),
106-
f"{StatusColor.MAPPING.get(row[1], StatusColor.DEFAULT)}{row[1]}{Style.RESET_ALL}"]
59+
print(f"\n{Style.BRIGHT}{name}")
60+
print(f"{Fore.CYAN}{'票种'.ljust(col1)}{'状态'.rjust(col2)}")
61+
print('-' * (col1 + col2 + 8))
62+
63+
formatted = [
64+
[row[0].ljust(col1),
65+
f"{StatusColor.COLOR_MAP.get(row[1], StatusColor.DEFAULT)}{row[1]}{Style.RESET_ALL}"]
10766
for row in tickets
10867
]
109-
110-
print(f"\n{Style.BRIGHT}{name}")
111-
print(f"{Fore.CYAN}{'票种'.ljust(max_desc)}{'状态'.rjust(max_status)}")
112-
print('-' * (max_desc + max_status + 8))
113-
print(tabulate(table_data, tablefmt='plain'))
68+
print(tabulate(formatted, tablefmt='plain'))
11469

11570
if pause_event:
11671
pause_event.clear()
11772

118-
119-
class MonitorController:
120-
"""票务监控控制器"""
73+
class Monitor:
12174
def __init__(self):
122-
self.stop_event = threading.Event()
123-
self.pause_event = threading.Event()
124-
self.last_state = None
125-
self.last_fetch_success = True
126-
127-
def start(self) -> None:
128-
"""启动监控线程"""
129-
threads = [
130-
threading.Thread(target=self.time_display, daemon=True),
131-
threading.Thread(target=self.monitor, daemon=True)
132-
]
133-
for t in threads:
134-
t.start()
135-
time.sleep(0.5)
75+
self.stop = threading.Event()
76+
self.pause = threading.Event()
77+
self.last_data = None
78+
self.healthy = True
79+
80+
def start(self):
81+
time_thread = threading.Thread(target=self.show_time, daemon=True)
82+
monitor_thread = threading.Thread(target=self.run_monitor, daemon=True)
83+
84+
time_thread.start()
85+
monitor_thread.start()
86+
13687
try:
137-
while any(t.is_alive() for t in threads):
88+
while time_thread.is_alive() or monitor_thread.is_alive():
13889
time.sleep(1)
13990
except KeyboardInterrupt:
140-
self.stop_event.set()
141-
142-
def time_display(self) -> None:
143-
"""实时时间显示线程"""
144-
last_time = ""
145-
while not self.stop_event.is_set():
146-
if not self.pause_event.is_set():
147-
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
148-
if current_time != last_time:
149-
print(f"{Fore.GREEN}当前时间: {current_time}", end='\r')
150-
last_time = current_time
91+
self.stop.set()
92+
93+
def show_time(self):
94+
current = ""
95+
while not self.stop.is_set():
96+
if not self.pause.is_set():
97+
new_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
98+
if new_time != current:
99+
print(f"{Fore.GREEN}当前时间: {new_time}", end='\r')
100+
current = new_time
151101
time.sleep(0.1)
152102

153-
def monitor(self) -> None:
154-
"""票务监控主线程"""
155-
with requests.Session() as session:
156-
session.verify = False # 禁用证书验证
157-
adapter = requests.adapters.HTTPAdapter(max_retries=3)
158-
session.mount('https://', adapter)
103+
def run_monitor(self):
104+
with requests.Session() as s:
105+
s.verify = False
106+
s.mount('https://', requests.adapters.HTTPAdapter(max_retries=3))
159107

160-
while not self.stop_event.is_set():
108+
while not self.stop.is_set():
161109
try:
162-
response = session.get(
163-
build_api_url(),
164-
headers=Config.HEADERS,
165-
timeout=Config.TIMEOUT
166-
)
167-
response.raise_for_status()
168-
name, tickets = process_response_data(response.json())
169-
110+
resp = s.get(Config.API_URL, headers=Config.HEADERS, timeout=Config.TIMEOUT)
111+
resp.raise_for_status()
112+
113+
name, tickets = process_data(resp.json())
170114
if not tickets:
171115
continue
172116

173-
if not self.last_fetch_success:
174-
self._display_full_interface(name, tickets)
175-
elif tickets != self.last_state:
176-
display_table(name, tickets, self.pause_event)
177-
178-
self.last_state = tickets
179-
self.last_fetch_success = True
180-
181-
except requests.exceptions.HTTPError as err:
182-
# 412风控处理
183-
if err.response.status_code == 412:
184-
self._handle_error("可能遭到风控,请立即停止操作!", is_critical=True)
185-
else:
186-
self._handle_error(f"HTTP错误: {err}", is_critical=False)
187-
self.last_fetch_success = False
188-
except requests.exceptions.RequestException as err:
189-
self._handle_error(f"请求错误: {err}", is_critical=False)
190-
self.last_fetch_success = False
117+
if not self.healthy:
118+
self.full_display(name, tickets)
119+
elif tickets != self.last_data:
120+
show_table(name, tickets, self.pause)
191121

122+
self.last_data = tickets
123+
self.healthy = True
124+
125+
except requests.exceptions.HTTPError as e:
126+
self.handle_error("HTTP错误" if e.response.status_code != 412 else "触发风控!立即停止!", e.response.status_code == 412)
127+
except requests.exceptions.RequestException as e:
128+
self.handle_error(f"请求异常: {e}", False)
129+
192130
time.sleep(Config.REFRESH_INTERVAL)
193131

194-
def _display_full_interface(self, name: str, tickets: List[List[str]]) -> None:
195-
"""显示完整界面"""
132+
def full_display(self, name, tickets):
196133
clear_screen()
197134
print(f"{Fore.YELLOW}监控ID: {Config.TICKET_ID} | 刷新间隔: {Config.REFRESH_INTERVAL}s")
198135
print("=" * 40)
199-
display_table(name, tickets, self.pause_event)
200-
201-
def _handle_error(self, message: str, is_critical: bool) -> None:
202-
"""处理错误信息"""
203-
print(Fore.RED + f"\n{message}")
204-
if is_critical:
205-
self.stop_event.set()
136+
show_table(name, tickets, self.pause)
206137

138+
def handle_error(self, msg, critical):
139+
print(Fore.RED + f"\n{msg}")
140+
self.healthy = False
141+
if critical:
142+
self.stop.set()
207143

208144
if __name__ == "__main__":
209145
clear_screen()
210146
print(f"{Fore.YELLOW}监控ID: {Config.TICKET_ID} | 刷新间隔: {Config.REFRESH_INTERVAL}s")
211147
print("=" * 40)
212-
controller = MonitorController()
213-
try:
214-
controller.start()
215-
finally:
216-
input("\n按回车键退出程序...\n")
148+
Monitor().start()
149+
input("\n按回车键退出程序...\n")

0 commit comments

Comments
 (0)