diff --git a/.github/workflows/destversion.yml b/.github/workflows/destversion.yml index 4697395..453f33f 100644 --- a/.github/workflows/destversion.yml +++ b/.github/workflows/destversion.yml @@ -4,15 +4,20 @@ on: schedule: - cron: '0 7 * * *' workflow_dispatch: + inputs: + force_release: + description: 'Force create release (ignore version checks)' + type: boolean + default: false jobs: save_new_wechat: runs-on: macOS-latest steps: - uses: actions/checkout@v2 - # - name: Test Github Action Server Time - # run: echo `date` + - name: Check new version and push env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: bash -x ./scripts/destVersionForMac.sh ${{ github.event.inputs.download_link }} + FORCE_RELEASE: ${{ github.event.inputs.force_release }} + run: python3 ./scripts/destVersionForMac.py diff --git a/.github/workflows/notify.yml b/.github/workflows/notify.yml deleted file mode 100644 index 663bc79..0000000 --- a/.github/workflows/notify.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Wechat Release Notify - -on: - release: - types: [published] - - -jobs: - notify_to_tg: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Check new version and push - env: - GHTOKEN: ${{ secrets.GHTOKEN }} - BOTTOKEN: ${{ secrets.BOTTOKEN }} - CHATIDS: ${{ secrets.CHATIDS }} - run: bash -x ./scripts/notify.sh diff --git a/.gitignore b/.gitignore index 8ab55c2..655b485 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +temp/ + ################################################################################ # This below was automatically created for macOS system ################################################################################ diff --git a/README.md b/README.md index c7aeb08..69f38f6 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,21 @@ -# Wechat-Versions-For-macOS -收集 Mac 微信版本并保存 +# Wechat For Macos Version Archive +收集官网 Mac 微信版本并保存至release。 -## 目录结构 -```shell -├── README.md # 自述文件 -├── WeChatSetup # 微信安装包临时目录 -│ └── temp # 临时目录 -└── scripts # 脚本目录 - ├── destVersioForMac.sh # 获取安装包及取得版本号与 hash 值的脚本 - └── notify.sh # 新release 时调用通知的脚本 -``` +## 更新日志 +* 2026.2.12:使用python重写脚本,移除冗余文件和代码。在`4.0.5`版本后,之前获取精确版本号的规则失效,目前已修复;未来如果继续出现无法获取精确版本号则会自动采用大版本+build的形式(如`v4.0.0+build.12345`)。 -## 说明 +* 2024.10.1:通过转换dmg为img后解压,获取精确的微信版本号(例如:3.8.9.xx);在此之前,后两位小版本号无法获取,所以通过添加更新日期后缀来区分大版本中的小版本,需在下载前自行判断。 -* 2024.10.1 更新:通过转换dmg为img后解压,获取精确的微信版本号(例如:3.8.9.xx);在此之前,后两位小版本号无法获取,所以通过添加更新日期后缀来区分大版本中的小版本,需在下载前自行判断。 - -项目使用 Github Action 自动下载微信**官网最新版本安装包**计算 Hash 值并推送至仓库 Release,目前暂无保留App Store安装包的想法。 +项目使用 Github Action 每天自动检测微信**官网新版本更新**,计算 Hash/MD5 值并推送至仓库 Release。 +项目仅抓取官网的Mac安装包,并不包含App Store中的版本。 各版本更新日志可参见官网 [changelog](https://weixin.qq.com/cgi-bin/readtemplate?lang=zh_CN&t=weixin_faq_list&head=true) +相关项目: +- [微信 Windows 64 位 3.0 版本存档](https://github.com/tom-snow/wechat-windows-versions) +- [微信 Windows 32 位 3.0 版本存档](https://github.com/tom-snow/wechat-windows-versions-x86) +- [微信 Mac 3.0 & 4.0 版本存档](https://github.com/zsbai/wechat-versions) + + *如有问题/侵权,请直接提交 issue 告知。* diff --git a/scripts/destVersionForMac.py b/scripts/destVersionForMac.py new file mode 100644 index 0000000..d67770e --- /dev/null +++ b/scripts/destVersionForMac.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python3 + +import datetime +import hashlib +import html.parser +import os +import plistlib +import re +import shutil +import subprocess +import sys +import time +import urllib.request +from pathlib import Path + +WEBSITE_URL = "https://mac.weixin.qq.com/?t=mac&lang=zh_CN" +BASE_DIR = Path.cwd() / "WeChatMac" +TEMP_DIR = BASE_DIR / "temp" + +class DownloadLinkParser(html.parser.HTMLParser): + """ + 从微信 Mac 官方网站的 HTML 中解析下载链接。 + """ + + def __init__(self) -> None: + super().__init__() + self.link = "" + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + if tag != "a" or self.link: + return + attrs_dict = {key: value or "" for key, value in attrs} + classes = attrs_dict.get("class", "").split() + if "download-button" in classes: + self.link = attrs_dict.get("href", "").strip() + +def run( + cmd: list[str], check: bool = True, capture: bool = True +) -> subprocess.CompletedProcess: + """ + 运行子进程命令的辅助函数,默认捕获输出并检查返回码。 + + Args: + cmd (list[str]): 要执行的命令列表。 + check (bool, optional): 是否检查命令返回码。默认为 True。 + capture (bool, optional): 是否捕获命令输出。默认为 True。 + + Returns: + subprocess.CompletedProcess: 子进程执行结果。 + """ + return subprocess.run( + cmd, + check=check, + text=True, + stdout=subprocess.PIPE if capture else None, + stderr=subprocess.STDOUT if capture else None, + ) + +def log(message: str) -> None: + """ + 打印日志信息并立即刷新输出缓冲区。 + + Args: + message (str): 要打印的日志信息。 + """ + print(message, flush=True) + +def fetch_download_link() -> str: + """ + 从微信 Mac 官方网站获取最新的下载链接 + + Raises: + RuntimeError: 如果在网站上未找到下载链接 + + Returns: + str: 下载链接 URL + """ + # Fetch HTML and extract the first download link on the page. + with urllib.request.urlopen(WEBSITE_URL, timeout=30) as response: + html = response.read().decode("utf-8", errors="replace") + parser = DownloadLinkParser() + parser.feed(html) + if not parser.link: + raise RuntimeError("Download link not found on website.") + return parser.link + +def fetch_head_metadata(url: str) -> dict[str, str]: + """ + 使用 HEAD 请求从直接文件链接读取元数据。 + Args: + url (str): 文件的直接下载链接 + Returns: + dict[str, str]: 从 HEAD 响应中提取的元数据字典,键为小写字符串,值为对应的响应头值 + """ + + # Use HEAD request to read metadata from the direct file link. + request = urllib.request.Request(url, method="HEAD") + with urllib.request.urlopen(request, timeout=30) as response: + return {key.lower(): value.strip() for key, value in response.headers.items()} + +def download_with_retry(url: str, dest: Path) -> None: + """ + 下载软件包 + + Args: + url (str): 文件的直接下载链接 + dest (Path): 目标文件路径 + """ + # Keep temp files under current working directory for debugging. + dest.parent.mkdir(parents=True, exist_ok=True) + attempts = 2 + last_error: Exception | None = None + for attempt in range(1, attempts + 1): + try: + run( + [ + "wget", + "--quiet", + "--tries", + "5", + "--waitretry", + "5", + "--retry-connrefused", + "--timeout", + "30", + url, + "-O", + str(dest), + ] + ) + return + except Exception as exc: + last_error = exc + if attempt < attempts: + log(f"Download failed (attempt {attempt}). Waiting before retry...") + time.sleep(10) + if last_error: + raise last_error + +def mount_dmg(dmg_path: Path) -> str: + """ + 挂载目标 dmg 镜像到本地 + + Args: + dmg_path (Path): dmg 镜像文件路径 + + Raises: + RuntimeError: 如果挂载失败或未找到挂载点 + + Returns: + str: 挂载点路径 + """ + # Mount DMG and capture the mount point under /Volumes. + result = run(["hdiutil", "attach", str(dmg_path), "-nobrowse"]) + matches = re.findall(r"(/Volumes/[^\n]+)", result.stdout) + if not matches: + raise RuntimeError("Failed to mount DMG.") + return matches[-1].strip() + +def detach_dmg(mount_dir: str) -> None: + """ + 解除挂载对应目录 + + Args: + mount_dir (str): 挂载点路径 + """ + run(["hdiutil", "detach", mount_dir], check=False) + +def get_tag_from_plist(mount_dir: str) -> str: + """ + 解析 Info.plist 构建 Tag 标签 + + 如果 WeChatBundleVersion 存在则直接使用,否则使用 CFBundleShortVersionString 和 CFBundleVersion 组合的形式。 + + Args: + mount_dir (str): 挂载路径 + + Raises: + RuntimeError: 如果 Info.plist 文件未找到 + RuntimeError: 如果 CFBundleShortVersionString 未找到 + RuntimeError: 如果 CFBundleVersion 未找到 + + Returns: + str: 构建标签 + """ + # Info.plist lives inside the mounted WeChat.app bundle. + info_plist = Path(mount_dir) / "WeChat.app" / "Contents" / "Info.plist" + if not info_plist.exists(): + raise RuntimeError("Info.plist not found in mounted volume.") + with info_plist.open("rb") as handle: + data = plistlib.load(handle) + short_version = str(data.get("CFBundleShortVersionString", "")).strip() + build = str(data.get("CFBundleVersion", "")).strip() + version = str(data.get("WeChatBundleVersion", "")).strip() + + if not short_version: + raise RuntimeError("CFBundleShortVersionString not found.") + if not build: + raise RuntimeError("CFBundleVersion not found.") + if version: + tag = version + else: + tag = f"{short_version}+build.{build}" + return tag + +def compute_sha256(file_path: Path) -> str: + """ + 计算文件的 sha256 + + Args: + file_path (Path): 文件路径 + + Returns: + str: 文件的 sha256 值 + """ + digest = hashlib.sha256() + with file_path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + +def parse_release_body(body: str) -> dict[str, str]: + """ + 从 GitHub release 的 body 文本中解析出键值对信息,返回一个字典。 + + Args: + body (str): GitHub release 的 body 文本,预期包含多行 "Key: Value" 格式的内容。 + + Returns: + dict[str, str]: 从 body 中解析出的键值对字典. + """ + info: dict[str, str] = {} + for line in body.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + info[key.lstrip("- ").strip()] = value.strip() + return info + +def get_latest_release_info() -> dict[str, str]: + """ + 获取最新 GitHub release 的信息,返回一个包含键值对的字典。 + + Returns: + dict[str, str]: 包含最新 release 信息的字典 + """ + result = run( + ["gh", "release", "view", "--json", "body", "--jq", ".body"], check=False + ) + if result.returncode != 0 or not result.stdout: + return {} + return parse_release_body(result.stdout) + +def tag_exists(tag: str) -> bool: + """ + 检查指定的 Git 标签是否已经存在于远程仓库中。 + + Args: + tag (str): Git 标签名称 + + Returns: + bool: 指定的 Git 标签是否存在 + """ + result = run(["gh", "release", "view", tag, "--json", "tagName"], check=False) + return result.returncode == 0 + +def build_release_notes( + tag: str, + download_link: str, + remote_md5: str, + sha256_sum: str, + remote_size: str, + remote_last_modified: str, +) -> str: + """ + 构建 Release 发布信息 + + Args: + tag (str): 构建版本号 + download_link (str): 下载链接 + remote_md5 (str): 远端md5的值 + sha256_sum (str): sha256的值 + remote_size (str): 文件大小 + remote_last_modified (str): 最后修改时间 + + Returns: + str: 最终的发布信息 + """ + lines = [ + "WeChat for Mac automatic release", + "", + "Download and integrity details are below.", + "", + "Release details", + f"- DestVersion: {tag}", + "", + "Source and checksums", + f"- DownloadFrom: {download_link}", + f"- Md5: {remote_md5}", + f"- Sha256: {sha256_sum}", + ] + if remote_size: + lines.append(f"- ContentLength: {remote_size}") + if remote_last_modified: + lines.append(f"- LastModified: {remote_last_modified}") + return "\n".join(lines) + "\n" + +def write_sha_file( + sha_file: Path, + tag: str, + download_link: str, + sha256_sum: str, + remote_md5: str, + remote_size: str, + remote_last_modified: str, +) -> None: + """ + 写入 SHA 文件 + + Args: + sha_file (Path): SHA 文件路径 + tag (str): 构建标签 + download_link (str): 下载链接 + sha256_sum (str): sha256的值 + remote_md5 (str): 远端md5的值 + remote_size (str): 文件大小 + remote_last_modified (str): 最后修改时间 + """ + timestamp = datetime.datetime.now(datetime.timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) + lines = [ + f"DestVersion: {tag}", + f"Md5: {remote_md5}", + f"Sha256: {sha256_sum}", + ] + if remote_size: + lines.append(f"ContentLength: {remote_size}") + if remote_last_modified: + lines.append(f"LastModified: {remote_last_modified}") + lines.extend( + [ + f"UpdateTime: {timestamp} (UTC)", + f"DownloadFrom: {download_link}", + ] + ) + sha_file.write_text("\n".join(lines) + "\n", encoding="utf-8") + +def main() -> int: + BASE_DIR.mkdir(parents=True, exist_ok=True) + TEMP_DIR.mkdir(parents=True, exist_ok=True) + + mount_dir = "" + try: + force_release = os.environ.get("FORCE_RELEASE", "").strip().lower() in { + "1", + "true", + "yes", + "on", + } + log(f"Force release: {'true' if force_release else 'false'}") + + # Step 1: resolve download link from website. + log("Resolving download link from website...") + download_link = fetch_download_link() + log(f"Download link: {download_link}") + + # Step 2: read metadata from HEAD response. + log("Fetching HEAD metadata...") + headers = fetch_head_metadata(download_link) + remote_md5 = headers.get("x-cos-meta-md5", "") + remote_size = headers.get("content-length", "") + remote_last_modified = headers.get("last-modified", "") + log( + "HEAD metadata: " + f"md5={remote_md5 or 'n/a'}, " + f"size={remote_size or 'n/a'}, " + f"last_modified={remote_last_modified or 'n/a'}" + ) + + # Step 3: compare with latest release by MD5 to avoid downloads. + log("Fetching latest GitHub release info...") + latest_info = get_latest_release_info() + latest_md5 = latest_info.get("Md5", "") + latest_sha256 = latest_info.get("Sha256", "") + log( + "Latest release: " + f"md5={latest_md5 or 'n/a'}, " + f"sha256={latest_sha256 or 'n/a'}" + ) + + if remote_md5 and latest_md5 and remote_md5 == latest_md5: + if force_release: + log("MD5 matches latest release, but force release is enabled.") + else: + log("No new version detected by MD5. Skipping download.") + return 0 + + # Step 4: download DMG with retry. + log("Downloading DMG...") + dmg_path = TEMP_DIR / "WeChatMac.dmg" + download_with_retry(download_link, dmg_path) + log(f"Downloaded DMG to {dmg_path}") + + # Step 5: mount DMG and read plist values. + log("Mounting DMG and reading Info.plist...") + mount_dir = mount_dmg(dmg_path) + tag = get_tag_from_plist(mount_dir) + detach_dmg(mount_dir) + mount_dir = "" + log(f"Detected tag: {tag}") + + # Step 6: prepare release assets in workspace. + log("Preparing release assets...") + version_dir = BASE_DIR / tag + version_dir.mkdir(parents=True, exist_ok=True) + final_dmg = version_dir / f"WeChatMac-{tag}.dmg" + shutil.copy2(dmg_path, final_dmg) + + sha256_sum = compute_sha256(final_dmg) + log(f"Computed SHA256: {sha256_sum}") + sha_file = version_dir / f"WeChatMac-{tag}.dmg.sha256" + write_sha_file( + sha_file, + tag, + download_link, + sha256_sum, + remote_md5, + remote_size, + remote_last_modified, + ) + + if not latest_md5 and latest_sha256 and sha256_sum == latest_sha256: + if force_release: + log("SHA256 matches latest release, but force release is enabled.") + else: + log("No new version detected by SHA256. Skipping release.") + return 0 + if not latest_md5: + log("Latest release has no MD5, used SHA256 fallback check.") + + if tag_exists(tag): + suffix = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d") + tag = f"{tag}_{suffix}" + log(f"Release tag: {tag}") + + title = f"Wechat For Mac {tag}" + notes_content = build_release_notes( + tag, + download_link, + remote_md5, + sha256_sum, + remote_size, + remote_last_modified, + ) + notes_file = TEMP_DIR / "release_notes.txt" + notes_file.write_text(notes_content, encoding="utf-8") + log(f"Release notes written to {notes_file}") + + # Step 7: publish release with assets and notes. + log("Creating GitHub release...") + run( + [ + "gh", + "release", + "create", + tag, + str(final_dmg), + str(sha_file), + "-F", + str(notes_file), + "-t", + title, + ] + ) + log("GitHub release created.") + + return 0 + finally: + # Always detach and cleanup to keep workspace tidy. + if mount_dir: + detach_dmg(mount_dir) + shutil.rmtree(BASE_DIR, ignore_errors=True) + log("Cleanup completed.") + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as exc: + print(f"Error: {exc}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/destVersionForMac.sh b/scripts/destVersionForMac.sh deleted file mode 100644 index b40a163..0000000 --- a/scripts/destVersionForMac.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env bash - -set -eo pipefail - -# ==================================================== -# 配置变量 -# ==================================================== -TEMP_PATH="WeChatMac/temp" -WEBSITE_URL="https://mac.weixin.qq.com/?t=mac&lang=zh_CN" -DOWNLOAD_LINK="" - -# ==================================================== -# 函数定义 -# ==================================================== - -# 打印分隔线 -print_separator() { - printf '%*s\n' 60 | tr ' ' '#' -} - -# 彩色输出函数 -echo_color() { - local color="$1" - shift - local message="$*" - case "$color" in - yellow) - echo -e "\033[1;33m$message\033[0m" - ;; - red) - echo -e "\033[1;31m$message\033[0m" >&2 - ;; - green) - echo -e "\033[1;32m$message\033[0m" - ;; - *) - echo "$message" - ;; - esac -} - -# 安装依赖项 -install_depends() { - print_separator - echo_color "yellow" "Installing dependencies: wget, curl, git, gh, shasum, pup" - print_separator - - brew install wget curl git gh pup -} - -# 下载 WeChat DMG -download_wechat() { - DOWNLOAD_LINK=$(curl -s "$WEBSITE_URL" | pup 'a.download-button:nth-of-type(1) attr{href}') - - print_separator - echo_color "yellow" "Downloading the newest WeChatMac..." - print_separator - - mkdir -p "$TEMP_PATH" - - wget -q "$DOWNLOAD_LINK" -O "${TEMP_PATH}/WeChatMac.dmg" - if [ "$?" -ne 0 ]; then - echo_color "red" "Download Failed, please check your network!" - clean_data 1 - fi -} - -# 从 Info.plist 提取版本信息 -get_version() { - print_separator - echo_color "yellow" "Extracting version from DMG (macOS)..." - print_separator - - # 挂载 dmg - MOUNT_DIR=$(hdiutil attach "${TEMP_PATH}/WeChatMac.dmg" -nobrowse | sed -n 's/^.*\(\/Volumes\/.*\)$/\1/p' | tail -n1) - - if [ -z "$MOUNT_DIR" ]; then - echo_color "red" "Failed to mount DMG!" - clean_data 1 - fi - - # 定位 Info.plist - # INFO_PLIST=$(find "${MOUNT_DIR}" -type f -name "Info.plist" | head -n 1) - INFO_PLIST="${MOUNT_DIR}/WeChat.app/Contents/Info.plist" - - if [ ! -f "$INFO_PLIST" ]; then - echo_color "red" "Info.plist not found in mounted volume!" - hdiutil detach "$MOUNT_DIR" - clean_data 1 - fi - - # 使用 grep 和 sed 提取版本号 - VERSION=$(grep -A1 'CFBundleShortVersionString' "$INFO_PLIST" | grep '' | sed -E 's/.*([^<]+)<\/string>.*/\1/') - - # 使用 grep 和 sed 提取构建版本号 - BUILD_VERSION=$(grep -A1 'CFBundleVersion' "$INFO_PLIST" | grep '' | sed -E 's/.*([^<]+)<\/string>.*/\1/') - - # 卸载 dmg - hdiutil detach "$MOUNT_DIR" - - if [ -z "$VERSION" ]; then - echo_color "red" "Version information not found in Info.plist!" - clean_data 1 - fi - - echo "Version: $VERSION" -} - - -# 计算 SHA256 -compute_sha256() { - local file_path="$1" - shasum -a 256 "$file_path" | awk '{print $1}' -} - -# 准备提交(复制 DMG 并创建 .sha256 文件) -prepare_commit() { - print_separator - echo_color "yellow" "Preparing to commit new version..." - print_separator - - VERSION_DIR="WeChatMac/$VERSION" - mkdir -p "$VERSION_DIR" - - cp "${TEMP_PATH}/WeChatMac.dmg" "$VERSION_DIR/WeChatMac-$VERSION.dmg" - - NOW_SUM256=$(compute_sha256 "$VERSION_DIR/WeChatMac-$VERSION.dmg") - - cat > "$VERSION_DIR/WeChatMac-$VERSION.dmg.sha256" <&2 echo -e "\033[1;31mMissing Github Token(GHTOKEN)! Please get a BotToken from 'Github Settings->Developer settings->Personal access tokens' and set it in Repo Secrect\033[0m" - exit 1 -fi - -if [ -z $BOTTOKEN ]; then - >&2 echo -e "\033[1;31mMissing Bot Token(BOTTOKEN)! Please get a BotToken from @Botfather on Telegram and set it in Repo Secrect\033[0m" - exit 2 -fi - -if [ -z $CHATIDS ]; then - >&2 echo -e "\033[1;31mMissing ChatIds(CHATIDS)! Please get ChatId from @GroupIDbot on Telegram Chats(Muti chatids split with comma ',') and set it in Repo Environment Values\033[0m" - exit 2 -fi - -function login_gh() { - printf "#%.0s" {1..60} - echo - echo -e "## \033[1;33mLogin to github to use github-cli...\033[0m" - printf "#%.0s" {1..60} - echo - - echo $GHTOKEN > WeChatSetup/temp/GHTOKEN - gh auth login --with-token < WeChatSetup/temp/GHTOKEN - if [ "$?" -ne 0 ]; then - >&2 echo -e "\033[1;31mLogin Failed, please check your network or token!\033[0m" - clean_data 1 - fi - rm -rfv WeChatSetup/temp/GHTOKEN -} - -### https://kodango.com/sed-and-awk-notes-part-5 -## start=${1:-""} means as follows in general -## if ($1) then -## start=$1 -## else -## start="" -## end -function join_lines() { - local delim=${1:-,} - sed 'H;$!d;${x;s/^\n//;s/\n/\'$delim'/g}' -} - -function clean_data() { - printf "#%.0s" {1..60} - echo - echo -e "## \033[1;33mClean runtime and exit...\033[0m" - printf "#%.0s" {1..60} - echo - - rm -rfv WeChatSetup/* - exit $1 -} - -function main() { - temp_path="WeChatSetup/temp" - mkdir -p ${temp_path} - - login_gh - - gh release view --json body --jq ".body" > ${temp_path}/release.info - - release_info=`awk '!/^$|Sha256/ { $1="*"$1"*";sub("UpdateTime", "CheckTime"); if ( match($2, /https?:\/\/([\w\.\/:])*/) ) $2="[Url]("$2")"; print $0 }' ${temp_path}/release.info | join_lines '%0A' | sed 's/ /%20/g'` - dest_version=`awk '/DestVersion/ { print $2 }' ${temp_path}/release.info` - release_info="$release_info%0A%0A*NotifyFrom:*%20[Github](https://github.com/tom-snow/wechat-windows-versions/releases/tag/v$dest_version)" - - echo $CHATIDS | sed 's/,/\n/g' > ${temp_path}/chat_ids - # while IFS="" read -r chatid || [ -n "$chatid" ] - while IFS="" read -r chatid - do - api_link="https://api.telegram.org/bot$BOTTOKEN/sendMessage?chat_id=$chatid&text=*New%20WeChat%20Windows%20Version!!*%0A%0A$release_info&parse_mode=Markdown&disable_web_page_preview=true" - curl -s -o /dev/null $api_link - done < ${temp_path}/chat_ids - - gh auth logout --hostname github.com | echo "y" - clean_data 0 -} - -main \ No newline at end of file