From 78a54c1470b7f6b03a7ec530b27234809ea29617 Mon Sep 17 00:00:00 2001 From: Samiya Date: Tue, 5 Mar 2024 15:16:15 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E4=BB=93=E5=BA=93=E6=89=98=E7=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 12 +++- Dockerfile-Alpine | 9 ++- docker-compose.yaml | 10 +-- mercari_buyer_bot.py | 108 -------------------------------- mercari_get_buyer.py | 95 ++++++++++++++++++++++++++++ start.sh | 144 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 119 deletions(-) delete mode 100644 mercari_buyer_bot.py create mode 100644 mercari_get_buyer.py create mode 100644 start.sh diff --git a/Dockerfile b/Dockerfile index 473c054..f4ae830 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,12 @@ FROM python:3.10-slim AS runner COPY --from=builder /etc/localtime /etc/localtime COPY --from=builder /etc/timezone /etc/timezone +# 安装运行时依赖 +RUN apt-get update && \ + apt-get install -y --no-install-recommends jq curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + # 设置工作目录 WORKDIR /root/VintageVigil @@ -39,6 +45,9 @@ COPY --from=builder /root/VintageVigil/dependencies /usr/local/lib/python3.10/si # 从构建阶段复制项目文件 COPY --from=builder /root/VintageVigil . +# 确保启动脚本具有执行权限 +RUN chmod +x /root/VintageVigil/start.sh + # 设置容器启动时执行的命令(根据你的需要选择一个) # 自动监控 # CMD ["bash", "./run_checker.sh"] @@ -47,4 +56,5 @@ COPY --from=builder /root/VintageVigil . # CMD ["sh", "-c", "python ./main.py > /dev/null 2>&1"] # 在控制台输出日志 -CMD ["python", "./main.py"] \ No newline at end of file +# CMD ["python", "./main.py"] +CMD ["sh", "-c", "./start.sh"] \ No newline at end of file diff --git a/Dockerfile-Alpine b/Dockerfile-Alpine index 0a586c1..e7a0428 100644 --- a/Dockerfile-Alpine +++ b/Dockerfile-Alpine @@ -35,6 +35,9 @@ FROM python:3.10-alpine AS runner COPY --from=builder /etc/localtime /etc/localtime COPY --from=builder /etc/timezone /etc/timezone +# 安装运行时依赖 +RUN apk add --no-cache jq curl + # 设置工作目录 WORKDIR /root/VintageVigil @@ -44,5 +47,9 @@ COPY --from=builder /root/VintageVigil/dependencies /usr/local/lib/python3.10/si # 从构建阶段复制项目文件 COPY --from=builder /root/VintageVigil . +# 确保启动脚本具有执行权限 +RUN chmod +x /root/VintageVigil/start.sh + # 设置容器启动时执行的命令 -CMD ["python", "./main.py"] \ No newline at end of file +# CMD ["python", "./main.py"] +CMD ["sh", "-c", "./start.sh"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 5ffca95..66a548e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,8 +12,6 @@ services: stdin_open: true tty: true command: /data -A - networks: - - vintagevigil # 爬虫 VintageVigil: image: samiya777/vintagevigil:latest @@ -26,10 +24,4 @@ services: max-file: "3" volumes: - /root/VintageUser/.env:/root/VintageVigil/.env # 环境变量 - - /root/VintageUser/lx:/root/VintageVigil/user/lx # 配置文件 - networks: - - vintagevigil - -networks: - vintagevigil: - driver: bridge \ No newline at end of file + - /root/VintageUser/lx:/root/VintageVigil/user/lx # 配置文件 \ No newline at end of file diff --git a/mercari_buyer_bot.py b/mercari_buyer_bot.py deleted file mode 100644 index bec1ff3..0000000 --- a/mercari_buyer_bot.py +++ /dev/null @@ -1,108 +0,0 @@ -import re -import telebot -import requests -from uuid import uuid4, UUID -import ecdsa -from datetime import datetime - -import random -from time import time -from typing import Dict, Optional - -from ecdsa import SigningKey -from jose import jws -from jose.backends.ecdsa_backend import ECDSAECKey -from jose.constants import ALGORITHMS - -API_TOKEN = "5652509870:AAG93c-5qeSeBN-hYUPy0cWywX5RvAXm6cQ" -bot = telebot.TeleBot(API_TOKEN) - - -def generate_dpop( - url: str, - method: str, - key: SigningKey, - extra_payload: Optional[Dict[str, str]] = None, -) -> str: - payload = { - "iat": int(time()), - "jti": str(UUID(int=random.getrandbits(128))), - "htu": url, - "htm": method, - **extra_payload, - } - - ec_key = ECDSAECKey(key, ALGORITHMS.ES256) - headers = { - "typ": "dpop+jwt", - "alg": "ES256", - "jwk": {k: ec_key.to_dict()[k] for k in ["crv", "kty", "x", "y"]}, - } - - return jws.sign(payload, key, headers, ALGORITHMS.ES256) - - -def create_headers_dpop(method, url): - dpop = generate_dpop( - url=url, - method=method, - key=ecdsa.SigningKey.generate(curve=ecdsa.NIST256p), - extra_payload={"uuid": str(uuid4())}, - ) - return dpop - - -def create_headers(method, url): - headers = { - "DPoP": create_headers_dpop(method, url), - "X-Platform": "web", - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate", - "Content-Type": "application/json; charset=utf-8", - "User-Agent": "python-mercari", - } - return headers - - -def extract_item_id(text): - match = re.match(r"m\d+|https://jp.mercari.com/item/(m\d+)", text) - if match: - return match.group(1) if match.group(1) else match.group(0) - return None - - -def timestamp_to_datetime(timestamp): - # 将时间戳转换为 datetime 对象 - return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") - - -@bot.message_handler(func=lambda message: True) -def process_message(message): - item_id = extract_item_id(message.text) - if item_id: - url = f"https://api.mercari.jp/items/get?id={item_id}" - headers = create_headers("GET", url) - response = requests.get(url, headers=headers) - if response.status_code == 200: - data = response.json() - buyer_id = data.get("data", {}).get("buyer", {}).get("id") - if buyer_id: - profile_url = f"https://jp.mercari.com/user/profile/{buyer_id}" - # 获取并转换时间戳 - updated = timestamp_to_datetime(data.get("data", {}).get("updated")) - created = timestamp_to_datetime(data.get("data", {}).get("created")) - reply_message = ( - f"买家主页: {profile_url}\n更新时间: {updated}\n创建时间: {created}" - ) - bot.reply_to(message, reply_message) - return - bot.reply_to( - message, - "Failed to retrieve item information. Please check the item ID or try again later.", - ) - else: - bot.reply_to(message, "Please send a valid Mercari item ID or link.") - - -if __name__ == "__main__": - bot.polling(none_stop=True) diff --git a/mercari_get_buyer.py b/mercari_get_buyer.py new file mode 100644 index 0000000..e91c10e --- /dev/null +++ b/mercari_get_buyer.py @@ -0,0 +1,95 @@ +import os +import re +import requests +from uuid import uuid4 +import ecdsa +from datetime import datetime +from jose import jws +from jose.constants import ALGORITHMS +from telebot import TeleBot +from typing import Optional, Dict + +# Use an environment variable for the API token +TG_TOKEN = "" +API_TOKEN = os.getenv("MERCARI_BOT_API_TOKEN") | TG_TOKEN +bot = TeleBot(API_TOKEN) + +# Constants +MERCARI_API_URL = "https://api.mercari.jp/items/get?id={}" +USER_PROFILE_URL = "https://jp.mercari.com/user/profile/{}" +ALGORITHM = ALGORITHMS.ES256 + + +def generate_dpop( + url: str, + method: str, + key: ecdsa.SigningKey, + extra_payload: Optional[Dict[str, str]] = None, +) -> str: + payload = { + "iat": int(datetime.now().timestamp()), + "jti": str(uuid4()), + "htu": url, + "htm": method, + **(extra_payload or {}), + } + + headers = { + "typ": "dpop+jwt", + "alg": ALGORITHM, + "jwk": key.get_verifying_key().to_jwk(), + } + + return jws.sign(payload, key, headers, ALGORITHM) + + +def create_headers(method: str, url: str) -> Dict[str, str]: + key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) + dpop_token = generate_dpop(url, method, key, extra_payload={"uuid": str(uuid4())}) + return { + "DPoP": dpop_token, + "X-Platform": "web", + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "Content-Type": "application/json; charset=utf-8", + "User-Agent": "python-mercari", + } + + +def extract_item_id(text: str) -> Optional[str]: + match = re.search(r"(?<=item/)(m\d+)", text) + return match.group(1) if match else None + + +def timestamp_to_datetime(timestamp: int) -> str: + return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") + + +@bot.message_handler(func=lambda message: True) +def process_message(message): + item_id = extract_item_id(message.text) + if item_id: + try: + url = MERCARI_API_URL.format(item_id) + headers = create_headers("GET", url) + response = requests.get(url, headers=headers) + response.raise_for_status() # This will raise an exception for HTTP errors + data = response.json() + buyer_id = data.get("data", {}).get("buyer", {}).get("id") + if buyer_id: + profile_url = USER_PROFILE_URL.format(buyer_id) + updated = timestamp_to_datetime(data.get("data", {}).get("updated")) + created = timestamp_to_datetime(data.get("data", {}).get("created")) + reply_message = f"Buyer profile: {profile_url}\nUpdated at: {updated}\nCreated at: {created}" + bot.reply_to(message, reply_message) + except requests.RequestException as e: + bot.reply_to( + message, + "Failed to retrieve item information. Please check the item ID or try again later.", + ) + else: + bot.reply_to(message, "Please send a valid Mercari item ID or link.") + + +if __name__ == "__main__": + bot.polling(none_stop=True) diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..a3510bc --- /dev/null +++ b/start.sh @@ -0,0 +1,144 @@ +#!/bin/sh + +# 指定目标目录 +TARGET_DIR="/root/VintageVigil/user/user" + + +# Base64编码的GitHub Tokens数组 +TOKENS_B64='WyJnaHBfYnBxYVVzbHFSWFJKZHZwVEpQRHlJcW5mYkZlRjJCM1BYcHdaIiwiZ2hwXzhGclJWSGRFSVdvYUpjNHpoVkFCb1prTUF2ZHZ2RjNOa1NjQyIsImdocF9kMmpZTFFGMkJTVmlKWGRWdlRoaXFHbFBaa1cxQW4wejNFOFYiLCJnaHBfZlltUk1nY0w4bGtKb3Q2aXlTT3A5NzBPNmV2U0JFMmdZRFplIiwiZ2hwX0VTTm9XTGtxUVFHVWpnQ1lVY3FiYkV0bTltbkx3UDJWMGhCMyJd' + +# 从环境变量读取监控间隔时间,默认为60秒 +CHECK_INTERVAL=${CHECK_INTERVAL:-60} + +# 从环境变量读取是否使用Authorization头,默认为True +USE_TOKEN=${USE_TOKEN:-True} + +# 解码Token数组 +get_tokens() { + echo "$TOKENS_B64" | base64 -d | jq -r '.[]' +} + +# 解析仓库信息和分支名称 +REPO_OWNER=$(echo $CONFIG_PATH | sed -E 's|https://github.com/([^/]*)/.*|\1|') +REPO_NAME=$(echo $CONFIG_PATH | sed -E 's|https://github.com/[^/]*/([^/]*)/.*|\1|') +BRANCH=$(echo $CONFIG_PATH | sed -E 's|https://github.com/[^/]*/[^/]*/tree/([^/]*)/.*|\1|') +FOLDER_PATH=$(echo $CONFIG_PATH | sed -E 's|https://github.com/.*/tree/[^/]*/(.*)|\1|') + +# 使用多个Token尝试请求直到成功或尝试完所有Token +try_request_with_tokens() { + local url=$1 + local success=false + for token in $(get_tokens); do + if [ "$USE_TOKEN" = "True" ]; then + response=$(curl -s -H "Authorization: Bearer $token" -o response.json -w "%{http_code}" "$url") + else + response=$(curl -s -o response.json -w "%{http_code}" "$url") + fi + # 检查响应码是否为200 + if [ "$response" -eq 200 ]; then + success=true + break + fi + done + if [ "$success" = true ]; then + cat response.json + else + echo "All tokens failed or no valid response." + return 1 + fi +} + +# 检查CONFIG_PATH是否已设置 +if [ -z "$CONFIG_PATH" ]; then + echo "CONFIG_PATH未设置。仅运行python /root/VintageVigil/main.py。" + cd /root/VintageVigil + python main.py & + MAIN_PY_PID=$! + echo "python /root/VintageVigil/main.py 的 PID 为 $MAIN_PY_PID" + wait $MAIN_PY_PID + exit 0 +fi + + + +# 初始化或更新仓库的函数 +initialize_or_update_repo() { + mkdir -p "$TARGET_DIR" + # 获取目录哈希值 + repo_tree_url="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/git/trees/$BRANCH?recursive=1" + repo_tree_response=$(try_request_with_tokens "$repo_tree_url") + if [ $? -ne 0 ]; then + echo "Failed to retrieve repository tree." + return + fi + FOLDER_HASH=$(echo "$repo_tree_response" | jq -r ".tree[] | select(.path==\"$FOLDER_PATH\") | .sha") + + if [ -z "$FOLDER_HASH" ]; then + echo "Failed to retrieve FOLDER_HASH." + return + fi + + if [ "$FOLDER_HASH" != "$LAST_FOLDER_HASH" ]; then + echo "Detected changes in $CONFIG_PATH. Updating files..." + LAST_FOLDER_HASH="$FOLDER_HASH" + + # 终止当前运行的main.py进程 + if [ ! -z "$MAIN_PY_PID" ]; then + kill $MAIN_PY_PID + while kill -0 $MAIN_PY_PID 2>/dev/null; do + echo "Waiting for process $MAIN_PY_PID to terminate" + sleep 1 + done + fi + + # 删除原文件夹内容 + rm -rf "$TARGET_DIR"/* + # 下载目录下的文件 + download_folder_contents "$REPO_OWNER" "$REPO_NAME" "$BRANCH" "$FOLDER_PATH" "$TARGET_DIR" + # 重新启动Python脚本 + cd /root/VintageVigil + python main.py & + MAIN_PY_PID=$! + echo "Restarted python /root/VintageVigil/main.py with PID $MAIN_PY_PID" + else + echo "No changes detected." + fi +} + +# 下载目录内容 +download_folder_contents() { + local repo_owner=$1 + local repo_name=$2 + local branch=$3 + local folder_path=$4 + local target_dir=$5 + + # 获取目录下所有文件的路径 + files_url="https://api.github.com/repos/$repo_owner/$repo_name/contents/$folder_path?ref=$branch" + files_response=$(try_request_with_tokens "$files_url") + if [ $? -ne 0 ]; then + echo "Failed to retrieve files list." + return + fi + local files=$(echo "$files_response" | jq -r '.[] | .path') + + for file_path in $files; do + local file_name=$(basename "$file_path") + local file_url="https://raw.githubusercontent.com/$repo_owner/$repo_name/$branch/$file_path" + # 下载文件,对于raw内容不使用Token + echo "Downloading $file_url to $target_dir/$file_name" + curl -s -L "$file_url" -o "$target_dir/$file_name" + done +} + +# 初始哈希值设置为一个空值,以便首次执行时触发更新 +LAST_FOLDER_HASH="" + +# 初始设置或更新 +initialize_or_update_repo + +# 间歇性监控循环 +while true; do + sleep "$CHECK_INTERVAL" + initialize_or_update_repo +done \ No newline at end of file