Переглянути джерело

add new autoupdate script

Dew-OF-Aurora 2 місяців тому
батько
коміт
cea80df842
3 змінених файлів з 719 додано та 56 видалено
  1. 1 1
      custom/default.custom.yaml
  2. 51 55
      custom/squirrel.custom.yaml
  3. 667 0
      rime-wanxiang-update-macos.sh

+ 1 - 1
custom/default.custom.yaml

@@ -2,5 +2,5 @@ patch:
   "switcher/hotkeys/+":
     - F3
   "switcher/fold_options/": true
-  "switcher/abbreviate_options/": false
+  "switcher/abbreviate_options/": true
   "switcher/option_list_separator/": " | "

+ 51 - 55
custom/squirrel.custom.yaml

@@ -13,69 +13,65 @@ patch:
       ascii_mode: true
     st:
       ascii_mode: true
+
   style:
     color_scheme: mac_light
-    color_scheme_dark: mac_dark
-
-    text_orientation: horizontal # horizontal | vertical
+    color_scheme_dark: mac_light
     candidate_list_layout: linear
     inline_preedit: true
-    corner_radius: 10
-    hilited_corner_radius: 0
+    corner_radius: 8
     border_height: 0
     border_width: 0
-    line_spacing: 5
+    line_spacing: 10
     spacing: 10
-    font_face: "Lucida Grande"
-    font_point: 21
-    label_font_point: 18
-    comment_font_point: 18
+    font_face: "PingFang SC"
+    font_point: 18
 
   preset_color_schemes:
+    wechat_light:
+      name: "Wechat Light"
+      back_color: 0xFFFFFF
+      border_color: 0xFFFFFF
+      text_color: 0x000000
+      hilited_text_color: 0xFFFFFF
+      hilited_back_color: 0x007AFF
+      hilited_candidate_text_color: 0xFFFFFF
+      hilited_candidate_back_color: 0xD75A00
+      candidate_text_color: 0x000000
+      comment_text_color: 0x888888
+
+    wechat_dark:
+      name: "Wechat Dark"
+      back_color: 0x2d2d2d
+      border_color: 0x2d2d2d
+      text_color: 0xFFFFFF
+      hilited_text_color: 0xFFFFFF
+      hilited_back_color: 0x0A84FF
+      hilited_candidate_text_color: 0xFFFFFF
+      hilited_candidate_back_color: 0x0A84FF
+      candidate_text_color: 0xFFFFFF
+      comment_text_color: 0xAAAAAA
+
     mac_light:
       name: Mac浅色
-      horizontal: true # true横排,false竖排
-      candidate_format: "%c %@ " # 用 1/6 em 空格 U+2005 来控制编号 %c 和候选词 %@ 前后的空间
-      corner_radius: 5 # 窗口圆角
-      hilited_corner_radius: 5 # 高亮圆角
-      line_spacing: 10 # 行间距(适用于竖排)
-      border_height: 4 # 窗口上下高度,大于圆角半径才生效
-      border_width: 4 # 窗口左右宽度,大于圆角半径才生效
-      font_face: "PingFangSC" # 候选词字体
-      font_point: 21 # 候选字大小
-      label_font_point: 13 # 候选编号大小
-      text_color: 0x424242 # 拼音行文字颜色
-      back_color: 0xFFFFFF # 候选条背景色
-      border_color: 0xFFFFFF # 边框色
-      label_color: 0x999999 # 预选栏编号颜色
-      candidate_text_color: 0x3c3c3c # 预选项文字颜色
-      comment_text_color: 0x999999 # 拼音等提示文字颜色
-      hilited_text_color: 0x999999 # 高亮拼音 (需要开启内嵌编码)
-      hilited_back_color: 0xD75A00 # 第一候选项背景背景色
-      hilited_candidate_text_color: 0xFFFFFF # 第一候选项文字颜色
-      hilited_candidate_label_color: 0xFFFFFF # 第一候选项编号颜色
-      hilited_comment_text_color: 0x999999 # 注解文字高亮
-
-    mac_dark:
-      name: Mac深色
-      horizontal: true # true横排,false竖排
-      candidate_format: "%c %@ " # 用 1/6 em 空格 U+2005 来控制编号 %c 和候选词 %@ 前后的空间
-      corner_radius: 5 # 窗口圆角
-      hilited_corner_radius: 5 # 高亮圆角
-      line_spacing: 10 # 行间距(适用于竖排)
-      border_height: 4 # 窗口上下高度,大于圆角半径才生效
-      border_width: 4 # 窗口左右宽度,大于圆角半径才生效
-      font_face: "PingFangSC" # 候选词字体
-      font_point: 21 # 候选字大小
-      label_font_point: 13 # 候选编号大小
-      text_color: 0x424242 # 拼音行文字颜色
-      back_color: 0x252a2e # 候选条背景色
-      border_color: 0x050505 # 边框色
-      label_color: 0x999999 # 预选栏编号颜色
-      candidate_text_color: 0xe9e9ea # 预选项文字颜色
-      comment_text_color: 0x999999 # 拼音等提示文字颜色
-      hilited_text_color: 0x999999 # 高亮拼音 (需要开启内嵌编码)
-      hilited_back_color: 0xD75A00 # 第一候选项背景背景色
-      hilited_candidate_text_color: 0xFFFFFF # 第一候选项文字颜色
-      hilited_candidate_label_color: 0xFFFFFF # 第一候选项编号颜色
-      hilited_comment_text_color: 0x999999 # 注解文字高亮
+      horizontal: true
+      candidate_format: "%c %@ "
+      corner_radius: 5
+      hilited_corner_radius: 5
+      line_spacing: 10
+      border_height: 4
+      border_width: 4
+      font_face: "PingFangSC"
+      font_point: 21
+      label_font_point: 13
+      text_color: 0x424242
+      back_color: 0xFFFFFF
+      border_color: 0xFFFFFF
+      label_color: 0x999999
+      candidate_text_color: 0x3c3c3c
+      comment_text_color: 0x999999
+      hilited_text_color: 0x999999
+      hilited_back_color: 0xD75A00
+      hilited_candidate_text_color: 0xFFFFFF
+      hilited_candidate_label_color: 0xFFFFFF
+      hilited_comment_text_color: 0x999999

+ 667 - 0
rime-wanxiang-update-macos.sh

@@ -0,0 +1,667 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+#### 配置 Rime 输入法引擎 ####
+# 支持鼠须管或小企鹅
+# 例如 "fcitx5"
+# 例如 "squirrel"
+ENGINE="squirrel"
+
+######### 配置结束 #########
+
+# 全局变量
+CNB_API="https://cnb.cool/amzxyz/rime-wanxiang/-/releases"
+SCHEMA_API="https://api.github.com/repos/amzxyz/rime_wanxiang/releases"
+GRAM_API="https://api.github.com/repos/amzxyz/RIME-LMDG/releases"
+TOOLS_API="https://api.github.com/repos/rimeinn/rime-wanxiang-update-tools/releases"
+FUZHU_LIST=("base" "flypy" "hanxin" "moqi" "tiger" "wubi" "zrm")
+TEMP_DIR=$(mktemp -d /tmp/wanxiang-update-XXXXXX)
+UPDATE_TOOLS_VERSION="v6.1.5"
+
+# 日志与错误处理
+log() {
+  local red="\033[0;31m" green="\033[0;32m" yellow="\033[0;33m" nc="\033[0m"
+  local level="$1" color="$nc"
+  case "$level" in
+  INFO) color="$green" ;;
+  WARN) color="$yellow" ;;
+  ERROR) color="$red" ;;
+  esac
+  shift
+  printf "${color}[%s] %s${nc}\n" "$level" "$*"
+}
+
+# 获取当前脚本名称
+script_name=$(basename $0)
+script_dir=$(pwd)
+
+engine_check() {
+# 输入法引擎检测
+if [ -z "$ENGINE" ]; then
+  log ERROR "当前未配置输入法引擎"
+  log WARN "如果使用Fcitx5(小企鹅)输入法,请复制以下语句并按回车执行,结束后请重新运行脚本:"
+  echo "sed -i '' 's/ENGINE=\"\"/ENGINE=\"fcitx5\"/g' ${script_dir}/${script_name}"
+  log WARN  "如果使用Squirrel(鼠须管)输入法,请复制以下语句并按回车执行,结束后请重新运行脚本:"
+  echo "sed -i '' 's/ENGINE=\"\"/ENGINE=\"squirrel\"/g' ${script_dir}/${script_name}"
+  exit
+elif [ "$ENGINE" == "fcitx5" ]; then
+  log INFO "当前使用Fcitx5(小企鹅)输入法"
+  read -rp "按回车继续,M 键更改: " if_modify
+  if [ "$if_modify" == "M" ]; then
+  log WARN "请复制以下语句并按回车执行,结束后请重新运行脚本:"
+  echo "sed -i '' 's/ENGINE=\"fcitx5\"/ENGINE=\"squirrel\"/g' ${script_dir}/${script_name}"
+  exit
+  fi
+elif [ "$ENGINE" == "squirrel" ]; then
+  log INFO "当前使用squirrel(鼠须管)输入法"
+  read -rp "按回车继续,M 键更改: " if_modify
+  if [ "$if_modify" == "M" ]; then
+  log WARN "请复制以下语句并按回车执行,结束后请重新运行脚本:"
+  echo "sed -i '' 's/ENGINE=\"squirrel\"/ENGINE=\"fcitx5\"/g' ${script_dir}/${script_name}"
+  exit
+  fi
+fi
+}
+
+error_exit() {
+  log ERROR "$*"
+  cleanup
+  exit 1
+}
+cleanup() {
+  if [[ -d "$TEMP_DIR" ]]; then
+    rm -rf "$TEMP_DIR" || log WARN "清理缓存文件失败"
+  fi
+}
+deps_check() {
+  for _cmd in curl jq unzip; do
+    command -v "$_cmd" >/dev/null || error_exit "缺少必要依赖:$_cmd"
+  done
+}
+fuzhu_check() {
+  local fuzhu_check="$1"
+  for _fuzhu in "${FUZHU_LIST[@]}"; do
+    if [[ "$fuzhu_check" == "$_fuzhu" ]]; then
+      return 0
+    fi
+  done
+  return 1
+}
+script_check() {
+  local mirror="$1"
+  if [[ "$UPDATE_TOOLS_VERSION" =~ ^"DEFAULT" ]]; then
+    log WARN "您似乎正在使用源文件!"
+    log WARN "请从 Release 页面下载正式版!"
+    error_exit "终止操作"
+  fi
+  log INFO "工具当前版本 $UPDATE_TOOLS_VERSION"
+  if [[ "$mirror" == "github" ]]; then
+    # 检查 GitHub 连接状态
+    log INFO "正在检查 GitHub 连接状态"
+    if ! curl -sL --connect-timeout 5 "https://api.github.com" >/dev/null; then
+      error_exit "您似乎无法连接到 GitHub API, 请检查您的网络"
+    elif ! curl -sL --connect-timeout 5 "https://github.com" >/dev/null; then
+      error_exit "您似乎无法连接到 GitHub, 请检查您的网络"
+    fi
+    log INFO "正在检查本工具是否存在更新"
+    local local_version remote_version
+    local_version="$UPDATE_TOOLS_VERSION"
+    remote_version=$(
+      curl -sL --connect-timeout 10 $TOOLS_API |
+        jq -r '.[].tag_name' | grep -vE "rc" | sort -rV | head -n 1
+    )
+    if [[ "$remote_version" > "$local_version" ]]; then
+      log WARN "检测到工具最新版本为: $remote_version, 建议更新后继续"
+      log WARN "https://github.com/rimeinn/rime-wanxiang-update-tools/releases/download/$remote_version/rime-wanxiang-update-macos.sh"
+    else
+      log INFO "工具已是最新版本"
+    fi
+  elif [[ "$mirror" == "cnb" ]]; then
+    log WARN "由于您正在使用镜像,无法检查本工具是否存在更新"
+  fi
+}
+
+get_info() {
+  local mirror="$1" version="$2" name="$3" info
+  if [[ "$mirror" == "github" ]]; then
+    info=$(
+      jq -r --arg version "$version" --arg name "$name" '.[] |
+      select( .tag_name == $version ) | .assets.[] |
+      select( .name | test( $name ) )' "$TEMP_DIR/github_$name.json"
+    )
+    echo "$info"
+  elif [[ "$mirror" == "cnb" ]]; then
+    info=$(
+      jq -r --arg version "refs/tags/$version" --arg name "$name" '.releases.[] |
+      select( .tag_ref == $version ) | .assets[] |
+      select( .name | test( $name ) )' "$TEMP_DIR/cnb_$name.json"
+    )
+    echo "$info"
+  fi
+}
+
+# 排除文件检查
+# 函数:检查文件是否存在,如果不存在则创建并写入指定内容
+create_exclude_file() {
+  local file="${DEPLOY_DIR}/custom/user_exclude_file.txt"
+
+  if [[ -z "$file" ]]; then
+    error_exit "错误:必须指定排除文件路径"
+  fi
+
+  if [[ -f "$file" ]]; then
+    echo "排除文件已存在:$file"
+  else
+    echo "排除文件不存在,正在创建:$file"
+    # 确保目录存在
+    mkdir -p "$(dirname "$file")"
+    # 创建并写入内容
+    cat > "$file" <<EOF
+# 排除文件本身(请勿删除)
+custom/user_exclude_file.txt
+# 用户数据库
+lua/sequence.userdb
+lua/sequence.txt
+lua/input_stats.lua
+zc.userdb
+# 同步
+installation.yaml
+user.yaml
+# custom文件
+default.custom.yaml
+wanxiang_pro.custom.yaml
+wanxiang_reverse.custom.yaml
+wanxiang_mixedcode.custom.yaml
+# ##############以上内容请在了解万象方案机制后自行更改,否则请不要更改##############
+EOF
+  fi
+}
+apply() {
+  local source_dir="$1"
+  local base_dir="${2:-$source_dir}"  # 基准目录,默认为第一个参数,$2和$source_dir哪个不为空就取哪个
+
+  # 计算目标路径
+  local relative_path="${source_dir#$base_dir}" # 从source_dir删除开头的base_dir部分
+  local target_path="$DEPLOY_DIR/${relative_path:+$relative_path/}" # relative_path不为空则使用relative_path,否则使用空值
+
+  # 确保目标目录存在
+  mkdir -p "$target_path"
+
+  # 处理当前目录的文件和子目录
+  for item in "$source_dir"/*; do
+    if [[ -f "$item" ]]; then
+	  # 复制解压出来的文件到目标路径
+      cp -f "$item" "$target_path"
+    elif [[ -d "$item" ]]; then
+      # 处理子目录
+      apply "$item" "$base_dir"
+    fi
+  done
+}
+update_schema() {
+  local mirror="$1" fuzhu="$2" gram="$3"
+  # 缓存 API 响应
+  if [[ "$mirror" == "github" ]]; then
+    if [[ ! -f "$TEMP_DIR/github_$fuzhu.json" ]]; then
+      if ! curl -sL -H "Accept: application/vnd.github.v3+json" \
+        --connect-timeout 10 "$SCHEMA_API" >"$TEMP_DIR/github_$fuzhu.json"; then
+        error_exit "连接到 GitHub API 失败,您可能需要检查网络"
+      fi
+    fi
+  elif [[ "$mirror" == "cnb" ]]; then
+    if [[ ! -f "$TEMP_DIR/cnb_$fuzhu.json" ]]; then
+      if ! curl -sL -H "accept: application/vnd.cnb.web+json" \
+        --connect-timeout 10 "$CNB_API" >"$TEMP_DIR/cnb_$fuzhu.json"; then
+        error_exit "连接到 CNB 失败,您可能需要检查网络"
+      fi
+    fi
+  fi
+  # 获取本地版本号
+  local local_version remote_version
+  if [[ -f "$DEPLOY_DIR/lua/wanxiang.lua" ]]; then
+    local_version=$(grep "wanxiang.version" "$DEPLOY_DIR/lua/wanxiang.lua" | awk -F '"' '{print $2}')
+    [[ "$local_version" == v* ]] || local_version="v$local_version"
+  else
+    local_version="v0"
+  fi
+  # 获取远程版本号
+  if [[ "$mirror" == "github" ]]; then
+    remote_version=$(
+      jq -r '.[].tag_name' "$TEMP_DIR/github_$fuzhu.json" |
+        grep -vE "dict-nightly" | sort -rV | head -n 1
+    )
+  elif [[ "$mirror" == "cnb" ]]; then
+    remote_version=$(
+      jq -r '.releases.[].tag_ref' \
+        "$TEMP_DIR/cnb_$fuzhu.json" | grep -vE "model" | sort -rV | head -n 1
+    )
+    remote_version="${remote_version#"refs/tags/"}"
+  fi
+  [[ "$remote_version" == v* ]] || remote_version="v$remote_version"
+  if [[ "$remote_version" > "$local_version" ]]; then
+    log INFO "远程方案文件版本号为 $remote_version, 以下内容为更新日志"
+    local changelog
+    if [[ "$mirror" == "github" ]]; then
+      changelog=$(
+        jq -r --arg version "$remote_version" '.[] |
+        select( .tag_name == $version ) | .body' "$TEMP_DIR/github_$fuzhu.json"
+      )
+    elif [[ "$mirror" == "cnb" ]]; then
+      changelog=$(
+        jq -r --arg version "refs/tags/$remote_version" '.releases.[] |
+        select( .tag_ref == $version ) | .body' "$TEMP_DIR/cnb_$fuzhu.json"
+      )
+    fi
+    echo -e "$changelog" | sed -n '/## 📝 更新日志/,/## 🚀 下载引导/p' | sed '$d'
+    sleep 3
+    log INFO "开始更新方案文件,正在下载文件"
+    local schemaurl schemaname local_size remote_size
+    if [[ "$mirror" == "github" ]]; then
+      schemaurl=$(get_info "$mirror" "$remote_version" "$fuzhu" | jq -r '.browser_download_url')
+    elif [[ "$mirror" == "cnb" ]]; then
+      schemaurl=$(get_info "$mirror" "$remote_version" "$fuzhu" | jq -r '.path')
+      schemaurl="https://cnb.cool$schemaurl"
+    fi
+    schemaname=$(get_info "$mirror" "$remote_version" "$fuzhu" | jq -r '.name')
+    curl -L --connect-timeout 10 -o "$TEMP_DIR/$schemaname" "$schemaurl"
+    log INFO "正在验证文件完整性"
+    local_size=$(stat -f %z "$TEMP_DIR/$schemaname")
+    if [[ "$mirror" == "github" ]]; then
+      remote_size=$(get_info "$mirror" "$remote_version" "$fuzhu" | jq -r '.size')
+    elif [[ "$mirror" == "cnb" ]]; then
+      remote_size=$(get_info "$mirror" "$remote_version" "$fuzhu" | jq -r '.size_in_byte')
+    fi
+    if [[ "$local_size" != "$remote_size" ]]; then
+      log ERROR "期望文件大小: $remote_size, 实际文件大小: $local_size"
+      error_exit "方案文件下载出错,请重试!"
+    fi
+    log INFO "验证成功,开始更新方案文件"
+    unzip -q "$TEMP_DIR/$schemaname" -d "$TEMP_DIR/${schemaname%.zip}"
+    for _file in "简纯+.trime.yaml" "custom_phrase.txt" "squirrel.yaml" "weasel.yaml"; do
+      if [[ -f "$TEMP_DIR/${schemaname%.zip}/$_file" ]]; then
+        rm -r "$TEMP_DIR/${schemaname%.zip}/${_file:?}"
+      fi
+    done
+    local exclude_file
+    while IFS= read -r _line; do
+      if [[ "$_line" != \#* ]]; then
+        exclude_file="$_line"
+        # if [[ ! -e "$DEPLOY_DIR/$exclude_file" ]]; then
+        #   log WARN "项目 $DEPLOY_DIR/$exclude_file 不存在,跳过备份!"
+        # else
+        #   cp -rf "$DEPLOY_DIR/$exclude_file" "$TEMP_DIR/${schemaname%.zip}/$exclude_file"
+        # fi
+        if [[ -e "$TEMP_DIR/$exclude_file" ]]; then
+          log WARN "项目 $TEMP_DIR/$exclude_file 为排除文件不更新"
+          rm -rf "$TEMP_DIR/$exclude_file"
+        fi
+      fi
+    done <"$DEPLOY_DIR/custom/user_exclude_file.txt"
+    # 单独处理语法模型
+    # [[ "$gram" == "true" ]] || cp -rf "$DEPLOY_DIR/wanxiang-lts-zh-hans.gram" \
+    #   "$TEMP_DIR/${schemaname%.zip}/wanxiang-lts-zh-hans.gram"
+    # rm -rf "${DEPLOY_DIR:?}"
+    # cp -rf "$TEMP_DIR/${schemaname%.zip}" "$DEPLOY_DIR"
+    # 应用更新
+    apply "$TEMP_DIR/${schemaname%.zip}"
+    log INFO "方案文件更新成功"
+    return 0
+  else
+    log INFO "远程方案文件版本号为 $remote_version"
+    log INFO "本地方案文件版本号为 $local_version, 您目前无需更新它"
+    return 1
+  fi
+}
+update_dict() {
+  local mirror="$1" fuzhu="$2"
+  # 缓存 API 响应
+  if [[ "$mirror" == "github" ]]; then
+    if [[ ! -f "$TEMP_DIR/github_$fuzhu.json" ]]; then
+      if ! curl -sL -H "Accept: application/vnd.github.v3+json" \
+        --connect-timeout 10 "$SCHEMA_API" >"$TEMP_DIR/github_$fuzhu.json"; then
+        error_exit "连接到 GitHub API 失败,您可能需要检查网络"
+      fi
+    fi
+  elif [[ "$mirror" == "cnb" ]]; then
+    if [[ ! -f "$TEMP_DIR/cnb_$fuzhu.json" ]]; then
+      if ! curl -sL -H "accept: application/vnd.cnb.web+json" \
+        --connect-timeout 10 "$CNB_API" >"$TEMP_DIR/cnb_$fuzhu.json"; then
+        error_exit "连接到 CNB 失败,您可能需要检查网络"
+      fi
+    fi
+  fi
+  local local_date remote_date
+  if [[ -f "$DEPLOY_DIR/dicts/chengyu.txt" ]]; then
+    local_date=$(stat -f %c "$DEPLOY_DIR/dicts/chengyu.txt")
+  else
+    local_date=0
+  fi
+  if [[ "$mirror" == "github" ]]; then
+    remote_date=$(get_info "$mirror" "dict-nightly" "$fuzhu" | jq -r '.updated_at')
+  elif [[ "$mirror" == "cnb" ]]; then
+    remote_date=$(get_info "$mirror" "v1.0.0" "$fuzhu" | jq -r '.updated_at')
+  fi
+  remote_date=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$remote_date" +%s)
+  if [[ $remote_date -gt $local_date ]]; then
+    log INFO "正在下载最新词典文件"
+    local dicturl dictname local_size remote_size
+    if [[ "$mirror" == "github" ]]; then
+      dicturl=$(get_info "$mirror" "dict-nightly" "$fuzhu" | jq -r '.browser_download_url')
+      dictname=$(get_info "$mirror" "dict-nightly" "$fuzhu" | jq -r '.name')
+    elif [[ "$mirror" == "cnb" ]]; then
+      dicturl=$(get_info "$mirror" "v1.0.0" "$fuzhu" | jq -r '.path')
+      dicturl="https://cnb.cool$dicturl"
+      dictname=$(get_info "$mirror" "v1.0.0" "$fuzhu" | jq -r '.name')
+    fi
+    curl -L --connect-timeout 10 -o "$TEMP_DIR/$dictname" "$dicturl"
+    log INFO "正在验证文件完整性"
+    local_size=$(stat -f %z "$TEMP_DIR/$dictname")
+    if [[ "$mirror" == "github" ]]; then
+      remote_size=$(get_info "$mirror" "dict-nightly" "$fuzhu" | jq -r '.size')
+    elif [[ "$mirror" == "cnb" ]]; then
+      remote_size=$(get_info "$mirror" "v1.0.0" "$fuzhu" | jq -r '.size_in_byte')
+    fi
+    if [[ "$local_size" != "$remote_size" ]]; then
+      log ERROR "期望文件大小: $remote_size, 实际文件大小: $local_size"
+      error_exit "词典文件下载出错,请重试!"
+    fi
+    log INFO "验证成功,开始更新词典文件"
+    unzip -q "$TEMP_DIR/$dictname" -d "$TEMP_DIR"
+    dictname="${dictname:2}" && dictname="${dictname%.zip}"
+    cp -rf "$TEMP_DIR/$dictname"/* "$DEPLOY_DIR/dicts"
+    log INFO "词典文件更新成功"
+    return 0
+  else
+    remote_date=$(date -r "$remote_date" +"%Y-%m-%d %H:%M:%S")
+    log INFO "远程词典文件最后更新于 $remote_date"
+    local_date=$(date -r "$local_date" +"%Y-%m-%d %H:%M:%S")
+    log INFO "本地词典文件最后更新于 $local_date, 您目前无需更新它"
+    return 1
+  fi
+}
+update_gram() {
+  local mirror="$1"
+  # 缓存 API 响应
+  if [[ "$mirror" == "github" ]]; then
+    if [[ ! -f "$TEMP_DIR/github_gram.json" ]]; then
+      if ! curl -sL -H "Accept: application/vnd.github.v3+json" \
+        --connect-timeout 10 "$GRAM_API" >"$TEMP_DIR/github_gram.json"; then
+        error_exit "连接到 GitHub API 失败,您可能需要检查网络"
+      fi
+    fi
+  elif [[ "$mirror" == "cnb" ]]; then
+    if [[ ! -f "$TEMP_DIR/cnb_gram.json" ]]; then
+        headers=$(curl -sL -D - -o /dev/null -H "accept: application/vnd.cnb.web+json" "$CNB_API")
+        X_CNB_TOTAL=$(echo "$headers" | awk -F': ' '/[Xx]-[Cc]nb-[Tt]otal:/ {gsub(/ /,"",$2); print $2}')
+        X_CNB_PAGE_SIZE=$(echo "$headers" | awk -F': ' '/[Xx]-[Cc]nb-[Pp]age-[Ss]ize:/ {gsub(/ /,"",$2); print $2}')
+        # 防止为空
+        X_CNB_TOTAL=${X_CNB_TOTAL:-0}
+        X_CNB_PAGE_SIZE=${X_CNB_PAGE_SIZE:-1}
+        # 确保是数字
+        X_CNB_TOTAL=$(echo "$X_CNB_TOTAL" | tr -d -c 0-9)
+        X_CNB_PAGE_SIZE=$(echo "$X_CNB_PAGE_SIZE" | tr -d -c 0-9)
+        # 获取最后一页
+        last_page=$(( (X_CNB_TOTAL + X_CNB_PAGE_SIZE - 1) / X_CNB_PAGE_SIZE ))
+
+        if ! curl -G -sL -H "accept: application/vnd.cnb.web+json" \
+            --data-urlencode "page=${last_page}" \
+            --connect-timeout 10 "$CNB_API" >"$TEMP_DIR/cnb_gram.json"; then
+            error_exit "连接到 CNB 失败,您可能需要检查网络"
+        fi
+    fi
+  fi
+  local local_date remote_date gramname="wanxiang-lts-zh-hans.gram"
+  if [[ -f "$DEPLOY_DIR/$gramname" ]]; then
+    local_date=$(stat -f %c "$DEPLOY_DIR/$gramname")
+  else
+    local_date=0
+  fi
+  if [[ "$mirror" == "github" ]]; then
+    remote_date=$(get_info "$mirror" "LTS" "gram" | jq -r '.updated_at')
+  elif [[ "$mirror" == "cnb" ]]; then
+    remote_date=$(get_info "$mirror" "model" "gram" | jq -r '.updated_at')
+  fi
+  remote_date=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$remote_date" +%s)
+  if [[ $remote_date -gt $local_date ]]; then
+    log INFO "正在下载最新语法模型"
+    local gramurl local_size remote_size
+    if [[ "$mirror" == "github" ]]; then
+      gramurl=$(get_info "$mirror" "LTS" "gram" | jq -r '.browser_download_url')
+    elif [[ "$mirror" == "cnb" ]]; then
+      gramurl=$(get_info "$mirror" "model" "gram" | jq -r '.path')
+      gramurl="https://cnb.cool$gramurl"
+    fi
+    curl -L --connect-timeout 10 -o "$TEMP_DIR/$gramname" "$gramurl"
+    log INFO "正在验证文件完整性"
+    local_size=$(stat -f %z "$TEMP_DIR/$gramname")
+    if [[ "$mirror" == "github" ]]; then
+      remote_size=$(get_info "$mirror" "LTS" "gram" | jq -r '.size')
+    elif [[ "$mirror" == "cnb" ]]; then
+      remote_size=$(get_info "$mirror" "model" "gram" | jq -r '.size_in_byte')
+    fi
+    if [[ "$local_size" != "$remote_size" ]]; then
+      log ERROR "期望文件大小: $remote_size, 实际文件大小: $local_size"
+      error_exit "语法模型下载出错,请重试!"
+    fi
+    log INFO "验证成功,开始更新语法模型"
+    cp -rf "$TEMP_DIR/$gramname" "${DEPLOY_DIR}/$gramname"
+    log INFO "语法模型更新成功"
+    return 0
+  else
+    remote_date=$(date -r "$remote_date" +"%Y-%m-%d %H:%M:%S")
+    log INFO "远程语法模型最后更新于 $remote_date"
+    local_date=$(date -r "$local_date" +"%Y-%m-%d %H:%M:%S")
+    log INFO "本地语法模型最后更新于 $local_date, 您目前无需更新它"
+    return 1
+  fi
+}
+# 部属函数
+deploy() {
+  local deploy_executable="$1"
+  shift # 移除第一个参数,后续所有参数都是要传给可执行文件的
+  if [ -x "$deploy_executable" ]; then
+  echo "正在触发重新部署配置"
+    if output_and_error=$("$deploy_executable" "$@" 2>&1); then
+      [[ -n "$output_and_error" ]] && echo "输出: $output_and_error"
+      echo "重新部署成功"
+    else
+      echo "重新部署失败"
+      [[ -n "$output_and_error" ]] && echo "错误信息: $output_and_error"
+    fi
+  else
+    echo "找不到可执行文件: $deploy_executable"
+    echo "请手动部署"
+  fi
+}
+show_help() {
+  cat <<EOF
+Usage: $0 [OPTIONS]
+
+选项:
+  --mirror [github|cnb]        选择下载源 (默认: github)
+  --engine [fcitx5|squirrel]   设置输入法引擎 (必需,也可在脚本中设置对应变量)
+  --schema [base|pro]          更新方案类型
+  --fuzhu SCHEMA               更新辅助码表 (base|flypy|hanxin|moqi|tiger|wubi|zrm)
+  --dict                       更新词典
+  --gram                       更新语法模型
+  --help                       显示此帮助信息
+
+示例:
+  $0 --engine squirrel --schema base --fuzhu base --dict
+  $0 --mirror cnb --engine squirrel --schema pro --fuzhu flypy --gram
+
+注意:
+  必须至少指定一个更新项目: --schema, --dict 或 --gram
+  使用 --schema 或 --dict 时必须同时使用 --fuzhu
+EOF
+}
+main() {
+  # 脚本退出清理临时目录
+  trap cleanup EXIT
+  # 欢迎语
+  log INFO "欢迎使用万象方案更新助手"
+  # 检查是否为root用户
+  if [[ "$EUID" -eq 0 ]]; then
+    error_exit "请不要使用 root 身份运行该脚本!"
+  fi
+  # 检查必要的依赖
+  deps_check
+  # 处理用户输入
+  local mirror="" schema="" fuzhu="" dict="false" gram="false"
+  # 解析命令行参数
+  while [[ "$#" -gt 0 ]]; do
+    case $1 in
+    --mirror)
+      if [[ -n "$mirror" ]]; then
+        error_exit "选项 mirror 需要参数!"
+      else
+        shift
+      fi
+      if [[ "$1" != "cnb" ]]; then
+        error_exit "选项 mirror 的参数目前只能为 cnb"
+      else
+        mirror="$1"
+      fi
+      ;;
+    --engine)
+      if [[ -n "$ENGINE" ]]; then
+        error_exit "选项 engine 已指定!"
+      fi
+      shift
+      if [[ -z "$1" || "$1" == --* ]]; then
+        error_exit "选项 engine 需要参数!"
+      fi
+      if [[ "$1" != "fcitx5" && "$1" != "squirrel" ]]; then
+        error_exit "选项 engine 的参数只能为 fcitx5 或 squirrel"
+      fi
+      ENGINE="$1"
+      ;;
+    --schema)
+      if [[ -n "$schema" ]]; then
+        error_exit "选项 schema 需要参数!"
+      else
+        shift
+      fi
+      if [[ "$1" != "base" && "$1" != "pro" ]]; then
+        error_exit "选项 schema 的参数只能为 base 或 pro"
+      else
+        schema="$1"
+      fi
+      ;;
+    --fuzhu)
+      if [[ -n "$fuzhu" ]]; then
+        error_exit "选项 fuzhu 需要参数!"
+      else
+        shift
+      fi
+      if fuzhu_check "$1"; then
+        fuzhu="$1"
+      else
+        error_exit "选项 fuzhu 的参数只能为 ${FUZHU_LIST[*]} 其中之一"
+      fi
+      ;;
+    --dict)
+      dict="true"
+      ;;
+    --gram)
+      gram="true"
+      ;;
+    --help)
+      show_help
+      exit 0
+      ;;
+    *)
+      log WARN "未知参数: $1"
+      log WARN "使用 --help 查看帮助信息"
+      error_exit "参数输入错误: $1"
+      ;;
+    esac
+    shift
+  done
+
+  engine_check
+  # 获取输入法配置路径
+  if [ "$ENGINE" = "fcitx5" ]; then
+    DEPLOY_DIR="$HOME/.local/share/fcitx5/rime"
+  else
+    DEPLOY_DIR="$HOME/Library/Rime"
+  fi
+
+  # 判断是否设置了部署目录
+  if [[ -n "$DEPLOY_DIR" ]]; then
+    if [[ ! -d "$DEPLOY_DIR" ]]; then
+      log WARN "部署目录 $DEPLOY_DIR 不存在,您要创建它吗?"
+      read -rp "请输入 YES 或 NO (区分大小写) " _check
+      if [[ "$_check" == "YES" ]]; then
+        log WARN "您真的要创建该目录吗?您确定您的设置正确吗?"
+        read -rp "请输入 YES 或 NO (区分大小写) " _check_again
+        [[ "$_check_again" == "YES" ]] || error_exit "用户终止操作"
+        mkdir -p "$DEPLOY_DIR"
+      else
+        error_exit "用户终止操作"
+      fi
+    fi
+  else
+    error_exit "请设置部署目录!"
+  fi
+  # 排除项目列表文件是否存在
+  if [[ -f "$DEPLOY_DIR/user_exclude_file.txt" ]]; then
+    mv "$DEPLOY_DIR/user_exclude_file.txt" "$DEPLOY_DIR/custom/user_exclude_file.txt"
+    sed -i 's/user_exclude_file\.txt/custom\/user_exclude_file\.txt/g' \
+      "$DEPLOY_DIR/custom/user_exclude_file.txt"
+  fi
+  if [[ ! -f "$DEPLOY_DIR/custom/user_exclude_file.txt" ]]; then
+    log WARN "您没有设置排除项目列表!"
+    log WARN "将为您自动创建包含部分排除项目列表文件: $DEPLOY_DIR/custom/user_exclude_file.txt"
+    # 生成排除文件
+    create_exclude_file
+    log INFO "排除项目列表文件已创建"
+    log WARN "您还可以在该文件中写入您需要排除的项目,每行一个"
+    read -rp "按回车继续,M 键更改: " if_modify
+    if [ "$if_modify" == "M" ]; then
+    log WARN "请修改排除项目列表文件: $DEPLOY_DIR/custom/user_exclude_file.txt"
+    log WARN "保存后重新运行该脚本"
+    open "$DEPLOY_DIR/custom/user_exclude_file.txt"
+    exit
+    fi
+  fi
+  # 检查 schema 和 fuzhu 是否同时存在
+  if [[ -n "$schema" && -z "$fuzhu" ]]; then
+    error_exit "选项 schema 与选项 fuzhu 必须同时使用"
+  fi
+  # 检查 dict 和 fuzhu 是否同时存在
+  if [[ "$dict" == "true" && -z "$fuzhu" ]]; then
+    error_exit "选项 dict 与选项 fuzhu 必须同时使用"
+  fi
+  # 检查当 schema 为 base 时,fuzhu 是否也为 base
+  if [[ "$schema" == "base" && "$fuzhu" != "base" ]]; then
+    error_exit "当选项 schema 为 base 时,选项 fuzhu 必须为 base"
+  fi
+  [[ -n "$mirror" ]] || mirror="github"
+  # 脚本自检
+  script_check "$mirror"
+  # 开始更新
+  updated=false
+  [[ -z "$schema" ]] || {
+    update_schema "$mirror" "$fuzhu" "$gram" && updated=true
+  }
+  [[ "$dict" == "false" ]] || {
+    update_dict "$mirror" "$fuzhu" && updated=true
+  }
+  [[ "$gram" == "false" ]] || {
+    update_gram "$mirror" && updated=true
+  }
+  # 自动部署
+  if [ "$updated" = true ]; then
+    if [ "$ENGINE" = "squirrel" ]; then
+      DEPLOY_EXECUTABLE="/Library/Input Methods/Squirrel.app/Contents/MacOS/Squirrel"
+      deploy "$DEPLOY_EXECUTABLE" --reload
+    else
+      DEPLOY_EXECUTABLE="/Library/Input Methods/Fcitx5.app/Contents/bin/fcitx5-curl"
+      deploy "$DEPLOY_EXECUTABLE" /config/addon/rime/deploy -X POST -d '{}'
+    fi
+  fi
+}
+
+main "$@"