# utils.py # 此檔案包含各種工具函數 # 包括字型下載、IP 分類、繪圖輔助等 import os import urllib.request import matplotlib.pyplot as plt import matplotlib.font_manager as fm import pandas as pd import numpy as np import seaborn as sns import io import base64 import datetime import warnings from config import FONT_PATH # --- 解決 Matplotlib 中文顯示問題 --- def setup_chinese_font(): """下載並設定中文字型""" if not os.path.exists(FONT_PATH): try: print("📥 正在下載中文字型...") urllib.request.urlretrieve('https://github.com/StellarCN/scp_zh/raw/master/fonts/SimHei.ttf', FONT_PATH) except Exception as e: print(f"⚠️ 字型下載失敗: {e}") if os.path.exists(FONT_PATH): fm.fontManager.addfont(FONT_PATH) plt.rc('font', family='SimHei') plt.rcParams['axes.unicode_minus'] = False # Seaborn 近期會對舊的 pandas option 發出 FutureWarning(純警告、非功能錯誤) # 重整前端會觸發繪圖端點,因此你會在 terminal 看到重複訊息。 def suppress_warnings(): """抑制不必要的警告""" warnings.filterwarnings( "ignore", category=FutureWarning, message="use_inf_as_na option is deprecated*", ) # --- IP 分類函數 --- def get_ip_category(ip_str): """根據 IP 地址分類""" ip = str(ip_str).split(" ")[0] if ip.startswith("140.124."): return 0.0 elif ip.startswith("61.228."): return 1.0 elif ip.startswith("210.61."): return 2.0 elif ip.startswith("45.33."): return 3.0 else: return 4.0 def get_geo_level(ip_str): """根據 IP 地址獲取地理等級""" ip = str(ip_str).split(" ")[0] if ip == "140.124.71.55": return 0 elif ip == "140.124.18.22": return 1 elif ip.startswith("61.228."): return 2 elif ip.startswith("210.61."): return 3 elif ip.startswith("45.33."): return 4 else: return 5 def update_system_weights(ow, tw, iw, dw, gw, fw): """更新系統權重""" from config import SYSTEM_WEIGHTS SYSTEM_WEIGHTS.update({ "ow": float(ow), "tw": float(tw), "iw": float(iw), "dw": float(dw), "gw": float(gw), "fw": float(fw) }) return f"✅ 系統權重已更新 - 總體: {ow}%, 時間: {tw}%, IP: {iw}%, 裝置: {dw}%, 地理: {gw}%, 頻率: {fw}%" def _extract_gr_update_value(update_obj): """ Gradio 的 gr.update(...) 可能回傳 dict 形式或物件形式。 這裡統一抓出其中的 `value`,否則回傳 None。 """ if update_obj is None: return None if isinstance(update_obj, dict): return update_obj.get("value") # Fallback: try attribute access try: return getattr(update_obj, "value", None) except Exception: return None def get_current_time(): """獲取當前時間(台灣時區)""" return datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))).strftime("%Y-%m-%d %H:%M:%S") def validate_time_format(time_str): """驗證時間格式""" try: pd.to_datetime(str(time_str)) return True except Exception: return False