diff --git a/LTS.py b/LTS.py index e076cbd..bf01acb 100644 --- a/LTS.py +++ b/LTS.py @@ -13,9 +13,9 @@ """ # TouchFish 协议文档 -本协议文档版本:v2.5.0 +本协议文档版本:v2.6.0 -本协议分为三个部分:`Gate`,`Chat`,`Server`。 +本协议分为三个部分:`Gate`,`Chat`,`Misc`。 协议均使用 NDJSON(JSON 格式,相邻两个 JSON 以换行符分隔)格式进行发送。 @@ -23,6 +23,8 @@ # 协议更新日志 +- Protocol v2.6.0 (TouchFish v4.9.0) + - 恢复 v2.3.0 的字段更改 - Protocol v2.5.0 (TouchFish v4.8.0) - 撤销 v2.3.0 的字段更改 - Protocol v2.4.0 (TouchFish v4.7.0) @@ -215,29 +217,29 @@ --- -# 3 Server +# 3 Misc 这个部分是关于程序运行情况的协议内容。 ## 3.1 Start -`{ type: "SERVER.START", time: time, stamp: number, server_version: string, config: JSON }` +`{ type: "MISC.START", time: time, stamp: number, version: string, config: JSON }` 程序将启动时的启动参数写入日志。 -- `type`: `"SERVER.START"` +- `type`: `"MISC.START"` - `time`: 同上。 - `stamp`: 用于指定文件保存路径,取启动时的 UNIX 时间戳乘 `10 ** 6` 后向下取整。 -- `server_version`: 字符串,表示服务端程序版本。(下同) +- `version`: 字符串,表示服务端程序版本。(下同) - `config`: JSON 对象,表示启动参数。(具体格式详见代码,下同) ## 3.2 Data -`{ type: "SERVER.DATA", server_version: string, uid: number, config: JSON, users: [JSON, ...], chat_history: [JSON, ...] }` +`{ type: "MISC.DATA", server_version: string, uid: number, config: JSON, users: [JSON, ...], chat_history: [JSON, ...] }` 用于向新连接的客户端提供完整上下文。 -- `type`: `"SERVER.DATA"` +- `type`: `"MISC.DATA"` - `server_version`: 同上。 - `uid`: 表示服务端分配给该用户的用户 ID。 - `config`: 同上。 @@ -253,83 +255,83 @@ ## 3.3 Command -`{ type: "SERVER.COMMAND", time: time, command: string }` +`{ type: "MISC.COMMAND", time: time, command: string }` 程序将用户输入的指令写入日志。 -- `type`: `"SERVER.COMMAND"` +- `type`: `"MISC.COMMAND"` - `time`: 同上。 - `command`: 输入的指令。 ## 3.4 Client Stop -`{ type: "SERVER.CLIENT_STOP", time: time }` +`{ type: "MISC.CLIENT_STOP", time: time }` 客户端正常关闭时将事件写入日志。 -- `type`: `"MISC.SERVER.CLIENT_STOP"` +- `type`: `"MISC.MISC.CLIENT_STOP"` - `time`: 同上。 -## 3.5 Stop +## 3.5 Server Stop 服务端正常关闭时的协议。 ### 3.5.1 Announce -`{ type: "SERVER.STOP.ANNOUNCE" }` +`{ type: "MISC.SERVER_STOP.ANNOUNCE" }` 服务端正常关闭时,向全体客户端进行广播。 -- `type`: `"SERVER.STOP.ANNOUNCE"` +- `type`: `"MISC.SERVER_STOP.ANNOUNCE"` ### 3.5.2 Log -`{ type: "SERVER.STOP.LOG", time: time }` +`{ type: "MISC.SERVER_STOP.LOG", time: time }` 服务端正常关闭时将事件写入日志。 -- `type`: `"SERVER.STOP.LOG"` +- `type`: `"MISC.SERVER_STOP.LOG"` - `time`: 同上。 ## 3.6 Config ### 3.6.1 Post -`{ type: "SERVER.CONFIG.POST", key: string, value: any }` +`{ type: "MISC.CONFIG.POST", key: string, value: any }` 管理员向服务端发送配置修改请求。 -- `type`: `"SERVER.CONFIG.POST"` +- `type`: `"MISC.CONFIG.POST"` - `key`: 配置项名称。(下同) - `value`: 配置值。(下同) ### 3.6.2 Change -`{ type: "SERVER.CONFIG.CHANGE", key: string, value: any, operator: number }` +`{ type: "MISC.CONFIG.CHANGE", key: string, value: any, operator: number }` 服务端向客户端广播配置修改事件。 -- `type`: `"SERVER.CONFIG.CHANGE"` +- `type`: `"MISC.CONFIG.CHANGE"` - `key`: 同上。 - `value`: 同上。 - `operator`: 执行修改操作的用户 ID。 ### 3.6.3 Save -`{ type: "SERVER.CONFIG.SAVE", time: time }` +`{ type: "MISC.CONFIG.SAVE", time: time }` 服务端将聊天室房主导出配置的事件写入日志。 -- `type`: `"SERVER.CONFIG.SAVE"` +- `type`: `"MISC.CONFIG.SAVE"` - `time`: 同上。 ### 3.6.4 Log -`{ type: "SERVER.CONFIG.LOG", time: time, key: string, value: any, operator: number }` +`{ type: "MISC.CONFIG.LOG", time: time, key: string, value: any, operator: number }` 服务端将配置修改事件写入日志。 -- `type`: `"SERVER.CONFIG.LOG"` +- `type`: `"MISC.CONFIG.LOG"` - `time`: 同上。 - `key`: 同上。 - `value`: 同上。 @@ -360,7 +362,7 @@ import time # 程序版本 -VERSION = "v4.8.0" +VERSION = "v4.9.0" # 用于客户端解析协议 1.2 RESULTS = \ @@ -982,7 +984,7 @@ def process(message): prints("\033[0m\033[1;36m再见!\033[0m") exit_flag = True return - if message["type"] == "SERVER.CONFIG.CHANGE": # 服务端参数变更 (协议 3.6.2) + if message["type"] == "MISC.CONFIG.CHANGE": # 服务端参数变更 (协议 3.6.2) announce(message["operator"]) prints("配置项 {} 变更为:".format(message["key"]) + str(message["value"]), "cyan") if side == "Client": # 同上 @@ -993,7 +995,7 @@ def process(message): prints("该配置项相比修改前移除了:{}".format(str(deletions)), "cyan") config[message["key"].split(".")[0]][message["key"].split(".")[1]] = message["value"] return - if message["type"] == "SERVER.STOP.ANNOUNCE": # 服务端关闭 (协议 3.5.1) + if message["type"] == "MISC.SERVER_STOP.ANNOUNCE": # 服务端关闭 (协议 3.5.1) if side == "Client": # 同上 announce(0) prints("聊天室服务端已经关闭。", "cyan") @@ -1132,7 +1134,7 @@ def do_doorman(arg, verbose=True, by=-1): for i in range(len(users)): users_abstract.append({"username": users[i]["username"], "status": users[i]["status"]}) send_queue.put(json.dumps({"to": arg[1], "content": {"type": "GATE.REVIEW_RESULT", "accepted": True, "operator": {"username": users[by]["username"], "uid": by}}})) # 协议 1.3 - send_queue.put(json.dumps({"to": arg[1], "content": {"type": "SERVER.DATA", "server_version": VERSION, "uid": arg[1], "config": config, "users": users_abstract, "chat_history": history}})) # 协议 3.2 + send_queue.put(json.dumps({"to": arg[1], "content": {"type": "MISC.DATA", "server_version": VERSION, "uid": arg[1], "config": config, "users": users_abstract, "chat_history": history}})) # 协议 3.2 if side == "Client": upload({"type": "GATE.STATUS_CHANGE.REQUEST", "status": "Online", "uid": arg[1]}) # 协议 1.6.1 @@ -1313,12 +1315,12 @@ def do_config(arg, verbose=True, by=-1): first, second = arg[0].split(".") if side == "Server": config[first][second] = eval(arg[1]) - log_queue.put(json.dumps({"type": "SERVER.CONFIG.LOG", "time": time_str(), "key": first + "." + second, "value": eval(arg[1]), "operator": by})) # 协议 3.6.4 + log_queue.put(json.dumps({"type": "MISC.CONFIG.LOG", "time": time_str(), "key": first + "." + second, "value": eval(arg[1]), "operator": by})) # 协议 3.6.4 for i in range(len(users)): if users[i]["status"] in ["Online", "Admin", "Root"]: - send_queue.put(json.dumps({"to": i, "content": {"type": "SERVER.CONFIG.CHANGE", "key": first + "." + second, "value": eval(arg[1]), "operator": by}})) # 协议 3.6.2 + send_queue.put(json.dumps({"to": i, "content": {"type": "MISC.CONFIG.CHANGE", "key": first + "." + second, "value": eval(arg[1]), "operator": by}})) # 协议 3.6.2 if side == "Client": - upload({"type": "SERVER.CONFIG.POST", "key": first + "." + second, "value": eval(arg[1])}) # 协议 3.6.1 + upload({"type": "MISC.CONFIG.POST", "key": first + "." + second, "value": eval(arg[1])}) # 协议 3.6.1 except: printc(verbose, "指令格式不正确,请重试。") return @@ -1359,28 +1361,28 @@ def do_ban(arg, verbose=True, by=-1): if side == "Server": ips = [item for item in ips if item not in config["ban"]["ip"]] config["ban"]["ip"] += ips - log_queue.put(json.dumps({"type": "SERVER.CONFIG.LOG", "time": time_str(), "key": "ban.ip", "value": config["ban"]["ip"], "operator": by})) # 协议 3.6.4 + log_queue.put(json.dumps({"type": "MISC.CONFIG.LOG", "time": time_str(), "key": "ban.ip", "value": config["ban"]["ip"], "operator": by})) # 协议 3.6.4 for i in range(len(users)): if users[i]["status"] in ["Online", "Admin", "Root"]: - send_queue.put(json.dumps({"to": i, "content": {"type": "SERVER.CONFIG.CHANGE", "key": "ban.ip", "value": config["ban"]["ip"], "operator": by}})) # 协议 3.6.2 + send_queue.put(json.dumps({"to": i, "content": {"type": "MISC.CONFIG.CHANGE", "key": "ban.ip", "value": config["ban"]["ip"], "operator": by}})) # 协议 3.6.2 if side == "Client": ips = [item for item in ips if item not in config["ban"]["ip"]] new_value = config["ban"]["ip"] + ips - upload({"type": "SERVER.CONFIG.POST", "key": "ban.ip", "value": new_value}) # 协议 3.6.1 + upload({"type": "MISC.CONFIG.POST", "key": "ban.ip", "value": new_value}) # 协议 3.6.1 printc(verbose, "操作成功,共计封禁了 {} 个 IP 地址。".format(len(ips))) if arg[1] == "remove": if side == "Server": ips = [item for item in ips if item in config["ban"]["ip"]] config["ban"]["ip"] = [item for item in config["ban"]["ip"] if not item in ips] - log_queue.put(json.dumps({"type": "SERVER.CONFIG.LOG", "time": time_str(), "key": "ban.ip", "value": config["ban"]["ip"], "operator": by})) # 协议 3.6.4 + log_queue.put(json.dumps({"type": "MISC.CONFIG.LOG", "time": time_str(), "key": "ban.ip", "value": config["ban"]["ip"], "operator": by})) # 协议 3.6.4 for i in range(len(users)): if users[i]["status"] in ["Online", "Admin", "Root"]: - send_queue.put(json.dumps({"to": i, "content": {"type": "SERVER.CONFIG.CHANGE", "key": "ban.ip", "value": config["ban"]["ip"], "operator": by}})) # 协议 3.6.2 + send_queue.put(json.dumps({"to": i, "content": {"type": "MISC.CONFIG.CHANGE", "key": "ban.ip", "value": config["ban"]["ip"], "operator": by}})) # 协议 3.6.2 if side == "Client": ips = [item for item in ips if item in config["ban"]["ip"]] new_value = [item for item in config["ban"]["ip"] if not item in ips] - upload({"type": "SERVER.CONFIG.POST", "key": "ban.ip", "value": new_value}) # 协议 3.6.1 + upload({"type": "MISC.CONFIG.POST", "key": "ban.ip", "value": new_value}) # 协议 3.6.1 printc(verbose, "操作成功,共计解除封禁了 {} 个 IP 地址。".format(len(ips))) if arg[0] == "words": @@ -1402,14 +1404,14 @@ def do_ban(arg, verbose=True, by=-1): return if side == "Server": config["ban"]["words"].append(arg[2]) - log_queue.put(json.dumps({"type": "SERVER.CONFIG.LOG", "time": time_str(), "key": "ban.words", "value": config["ban"]["words"], "operator": by})) # 协议 3.6.4 + log_queue.put(json.dumps({"type": "MISC.CONFIG.LOG", "time": time_str(), "key": "ban.words", "value": config["ban"]["words"], "operator": by})) # 协议 3.6.4 for i in range(len(users)): if users[i]["status"] in ["Online", "Admin", "Root"]: - send_queue.put(json.dumps({"to": i, "content": {"type": "SERVER.CONFIG.CHANGE", "key": "ban.words", "value": config["ban"]["words"], "operator": by}})) # 协议 3.6.2 + send_queue.put(json.dumps({"to": i, "content": {"type": "MISC.CONFIG.CHANGE", "key": "ban.words", "value": config["ban"]["words"], "operator": by}})) # 协议 3.6.2 if side == "Client": new_value = config["ban"]["words"][:] new_value.append(arg[2]) - upload({"type": "SERVER.CONFIG.POST", "key": "ban.words", "value": new_value}) # 协议 3.6.1 + upload({"type": "MISC.CONFIG.POST", "key": "ban.words", "value": new_value}) # 协议 3.6.1 printc(verbose, "操作成功。") if arg[1] == "remove": @@ -1418,14 +1420,14 @@ def do_ban(arg, verbose=True, by=-1): return if side == "Server": config["ban"]["words"].remove(arg[2]) - log_queue.put(json.dumps({"type": "SERVER.CONFIG.LOG", "time": time_str(), "key": "ban.words", "value": config["ban"]["words"], "operator": by})) # 协议 3.6.4 + log_queue.put(json.dumps({"type": "MISC.CONFIG.LOG", "time": time_str(), "key": "ban.words", "value": config["ban"]["words"], "operator": by})) # 协议 3.6.4 for i in range(len(users)): if users[i]["status"] in ["Online", "Admin", "Root"]: - send_queue.put(json.dumps({"to": i, "content": {"type": "SERVER.CONFIG.CHANGE", "key": "ban.words", "value": config["ban"]["words"], "operator": by}})) # 协议 3.6.2 + send_queue.put(json.dumps({"to": i, "content": {"type": "MISC.CONFIG.CHANGE", "key": "ban.words", "value": config["ban"]["words"], "operator": by}})) # 协议 3.6.2 if side == "Client": new_value = config["ban"]["words"][:] new_value.remove(arg[2]) - upload({"type": "SERVER.CONFIG.POST", "key": "ban.words", "value": new_value}) # 协议 3.6.1 + upload({"type": "MISC.CONFIG.POST", "key": "ban.words", "value": new_value}) # 协议 3.6.1 printc(verbose, "操作成功。") def do_broadcast(arg, message=None, verbose=True, by=-1): @@ -1751,7 +1753,7 @@ def do_save(arg=None): with open("./config.json", "w", encoding="utf-8") as f: json.dump(config, f) print("参数已经成功保存到配置文件 config.json,下次启动时将自动加载配置项。") - log_queue.put(json.dumps({"type": "SERVER.CONFIG.SAVE", "time": time_str()})) # 协议 3.5 + log_queue.put(json.dumps({"type": "MISC.CONFIG.SAVE", "time": time_str()})) # 协议 3.5 except: print("无法将参数保存到配置文件 config.json,请稍后重试。") @@ -1770,13 +1772,13 @@ def do_exit(arg=None): # 防止干扰用户后续的终端使用 print("\033[0m\033[1;36m再见!\033[0m") if side == "Server": - log_queue.put(json.dumps({"type": "SERVER.STOP.LOG", "time": time_str()})) # 协议 3.2.2 + log_queue.put(json.dumps({"type": "MISC.SERVER_STOP.LOG", "time": time_str()})) # 协议 3.2.2 for i in range(len(users)): if users[i]["status"] in ["Pending", "Online", "Admin", "Root"]: - send_queue.put(json.dumps({"to": i, "content": {"type": "SERVER.STOP.ANNOUNCE"}})) # 协议 3.2.1 + send_queue.put(json.dumps({"to": i, "content": {"type": "MISC.SERVER_STOP.ANNOUNCE"}})) # 协议 3.2.1 server_socket.close() if side == "Client": - log_queue.put(json.dumps({"type": "SERVER.CLIENT_STOP", "time": time_str()})) # 协议 3.4 + log_queue.put(json.dumps({"type": "MISC.CLIENT_STOP", "time": time_str()})) # 协议 3.4 exit_flag = True my_socket.close() return @@ -1966,7 +1968,7 @@ def thread_gate(): users_abstract = [] for i in range(len(users)): users_abstract.append({"username": users[i]["username"], "status": users[i]["status"]}) - send_queue.put(json.dumps({"to": uid, "content": {"type": "SERVER.DATA", "server_version": VERSION, "uid": uid, "config": config, "users": users_abstract, "chat_history": history}})) # 协议 3.2 + send_queue.put(json.dumps({"to": uid, "content": {"type": "MISC.DATA", "server_version": VERSION, "uid": uid, "config": config, "users": users_abstract, "chat_history": history}})) # 协议 3.2 def thread_process(): global online_count @@ -2002,7 +2004,7 @@ def thread_process(): do_doorman("reject " + str(content["uid"]), False, sender) if content["status"] == "Online": do_doorman("accept " + str(content["uid"]), False, sender) - if content["type"] == "SERVER.CONFIG.POST": # 协议 3.6.1 + if content["type"] == "MISC.CONFIG.POST": # 协议 3.6.1 do_config("{} {}".format(content["key"], repr(content["value"])), False, sender) def thread_receive(): @@ -2165,7 +2167,7 @@ def thread_input(): print("\033[8;30m", end="", flush=True) blocked = False continue - log_queue.put(json.dumps({"type": "SERVER.COMMAND", "time": time_str(), "command": command})) # 协议 3.3 + log_queue.put(json.dumps({"type": "MISC.COMMAND", "time": time_str(), "command": command})) # 协议 3.3 # 将缩写形式替换为完整形式 for i in list(ABBREVIATION_TABLE.keys()): @@ -2477,7 +2479,7 @@ def main(): sys.exit(1) with open("./log.ndjson", "a", encoding="utf-8") as file: - file.write(json.dumps({"type": "SERVER.START", "time": time_str(), "stamp": stamp, "server_version": VERSION, "config": config}) + "\n") # 协议 3.1 + file.write(json.dumps({"type": "MISC.START", "time": time_str(), "stamp": stamp, "version": VERSION, "config": config}) + "\n") # 协议 3.1 side = "Server" prints("启动成功!", "green") @@ -2597,7 +2599,7 @@ def main(): my_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30) with open("./log.ndjson", "a", encoding="utf-8") as file: - file.write(json.dumps({"type": "SERVER.START", "time": time_str(), "stamp": stamp, "server_version": VERSION, "config": config}) + "\n") # 协议 3.1 + file.write(json.dumps({"type": "MISC.START", "time": time_str(), "stamp": stamp, "version": VERSION, "config": config}) + "\n") # 协议 3.1 # 核验协议 1.2,获取加入请求结果 try: @@ -2633,7 +2635,7 @@ def main(): if seconds_consumed == 11: prints("(也有可能是对方服务器端口被防火墙拦截,请联系服务器所有者确认,或检查本地网络及防火墙设置。)", "red") with open("./log.ndjson", "a", encoding="utf-8") as file: - file.write(json.dumps({"type": "SERVER.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 + file.write(json.dumps({"type": "MISC.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 input("\033[0m") sys.exit(1) @@ -2641,7 +2643,7 @@ def main(): print() prints("连接失败:{}".format(RESULTS[message["result"]]), "red") with open("./log.ndjson", "a", encoding="utf-8") as file: - file.write(json.dumps({"type": "SERVER.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 + file.write(json.dumps({"type": "MISC.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 input("\033[0m") sys.exit(1) @@ -2662,11 +2664,11 @@ def main(): if not message: raise # 特殊情况:聊天室服务端已经关闭 (协议 3.5.1) - if message["type"] == "SERVER.STOP.ANNOUNCE": + if message["type"] == "MISC.SERVER_STOP.ANNOUNCE": prints("聊天室服务端已经关闭。", "red") prints("连接失败。", "red") with open("./log.ndjson", "a", encoding="utf-8") as file: - file.write(json.dumps({"type": "SERVER.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 + file.write(json.dumps({"type": "MISC.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 input("\033[0m") sys.exit(1) # 一般情况:人工审核完成 (协议 1.3) @@ -2675,7 +2677,7 @@ def main(): prints("服务端管理员 {} (UID = {}) 拒绝了您的连接请求。".format(message["operator"]["username"], message["operator"]["uid"]), "red") prints("连接失败。", "red") with open("./log.ndjson", "a", encoding="utf-8") as file: - file.write(json.dumps({"type": "SERVER.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 + file.write(json.dumps({"type": "MISC.CLIENT_STOP", "time": time_str()}) + "\n") # 协议 3.4 input("\033[0m") sys.exit(1) if message["accepted"]: diff --git a/PKGBUILD b/PKGBUILD index 0831376..f5674f8 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: TouchFish Community pkgname=touchfish -pkgver=4.8.0 +pkgver=4.9.0 pkgrel=1 pkgdesc="FOSS multi-distribution LAN chatting tool" arch=('any') diff --git a/README.md b/README.md index c46eecc..edaf48e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ > [!WARNING] > **这是 TouchFish v4 版本,自 v4 开始不再向前兼容 v1 - v3。** > -> **根据规划,此版本(v4.7.0)是 TouchFish v4 的最后一个子版本。TouchFish v5 正在施工,[链接](https://github.com/2044-space-elevator/TFV5_server)。下一版本预计将进入 v5。** +> **根据规划,此版本(v4.9.0)是 TouchFish v4 的最后一个子版本。TouchFish v5 正在施工,[链接](https://github.com/2044-space-elevator/TFV5_server)。下一版本预计将进入 v5。** > [!IMPORTANT] > **重要通知:贡献者须知** @@ -43,9 +43,9 @@ > (这里的命令只针对 Linux 用户) > > 不建议下载预编译的二进制文件,建议通过源代码部署 -> `wget https://github.com/2044-space-elevator/TouchFish/releases/download/v4.7.0/LTS.py && python3 LTS.py` +> `wget https://github.com/2044-space-elevator/TouchFish/releases/download/v4.9.0/LTS.py && python3 LTS.py` > -> (也可以通过 @ILoveScratch2 提供的镜像站,但是截至 2026/2/23 只更新到了 v4.6 版本:`wget https://mirror.ilovescratch.us.ci/api/net/379814722/LTS.py && python3 LTS.py`) +> (也可以通过 @ILoveScratch2 提供的镜像站,但是截至 2026/7/13 只更新到了 v4.7.0 版本:`wget https://mirror.ilovescratch.us.ci/api/net/183301217/LTS.py && python3 LTS.py`) 1. **获取内网 IP 地址**: - **Windows**: 命令提示符执行 `ipconfig`,查找 "无线局域网适配器 WLAN" 下的 "IPv4 地址" @@ -129,7 +129,7 @@ TouchFish 拥有丰富的衍生版本生态系统,满足不同用户需求: -**这里的大部分发行版都暂不支持 v4 版本,如需使用请前往 [Release](https://github.com/2044-space-elevator/TouchFish/releases) 页面下载旧版本(v3及以前)** +**这里的大部分发行版都暂不支持 v4 版本,如需使用请前往 [Release](https://github.com/2044-space-elevator/TouchFish/releases) 页面下载旧版本(v3 及以前)** | 版本名称 | 简称 | 主要作者 | 链接 | 对 v1-3 支持 | 语言 | 平台支持 | 特色 | 对 v4 支持 |---------|------|----------|----|-----|------|----------|------|--| @@ -143,7 +143,7 @@ TouchFish 拥有丰富的衍生版本生态系统,满足不同用户需求: > 标 * 的版本可能需要自行编译、直接运行代码或缺少预编译包。 > -> 标 ^ 的版本可能与 LTS v4.6.0/v4.7.0 存在少量兼容性问题,此时建议使用 LTS v4.3.3。 +> 标 ^ 的版本可能与 LTS v4.9.0 存在少量兼容性问题,此时建议使用 LTS v4.8.0。 ---