### 更新日志 - 2024年6月15日

#### 新增功能
1. **配置文件路径调整**:
   - `bot_config.json`的读取路径移动到`/data/`目录,`bot_config_example.json`保留在原目录。
   - 日志文件存储路径移动到`/data/log/`目录。

2. **目录自动创建**:
   - 在程序启动时自动创建必要的目录(`/data`和`/data/log`),确保目录存在。

3. **配置文件不存在时自动复制**:
   - 当`bot_config.json`不存在时,自动将`bot_config_example.json`复制到指定位置,并写入日志,然后退出程序。

4. **API URL和代理配置读取**:
   - 将`api_url`和`proxies`配置移动到`bot_config.json`中读取,支持灵活配置反向代理。

5. **端口配置支持**:
   - 支持从配置文件中读取端口配置,默认端口为5000。注意:Docker环境中Gunicorn默认监听5000端口,建议使用端口映射指定端口。

6. **Docker支持**:
   - 编写Dockerfile以支持Docker环境运行。
   - 在Dockerfile中固定Gunicorn监听端口为5000,并确保Gunicorn绑定到`0.0.0.0`,从而对外部可访问。

#### 修复
1. **路径兼容性**:
   - 修复了Windows环境下目录和文件操作的兼容性问题,确保在不同平台下正常运行。

2. **日志和数据文件存储路径调整**:
   - 调整`received_data`和`sent_data`文件的存储路径到`/data/log`目录,统一管理日志文件。

#### 优化
1. **代码结构优化**:
   - 提升了代码的可读性和可维护性,简化了配置文件的读取和日志管理逻辑。
This commit is contained in:
yshtcn 2024-06-15 03:56:36 +08:00
parent 1fee83e19f
commit c82fb1871d
6 changed files with 174 additions and 46 deletions

26
Dockerfile Normal file
View File

@ -0,0 +1,26 @@
# Use an official Python runtime as a parent image
FROM python:3.12-slim
# Set the working directory in the container to /app
WORKDIR /app
# Copy only necessary files
COPY ServerChanPush2TelegramBot.py /app/
COPY wsgi.py /app/
COPY bot_config_example.json /app/
COPY requirements.txt /app/
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Ensure bot_config.json exists
RUN if [ ! -f /app/data/bot_config.json ]; then \
mkdir -p /app/data && \
cp /app/bot_config_example.json /app/data/bot_config.json; \
fi
# Expose the port
EXPOSE 5000
# Run wsgi server with gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "wsgi:app"]

View File

@ -6,12 +6,38 @@ import re
from urllib.parse import unquote from urllib.parse import unquote
from datetime import datetime from datetime import datetime
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
import os
import sys
import shutil
# 获取当前日期 # 获取当前日期
current_date = datetime.now().strftime("%Y-%m-%d") current_date = datetime.now().strftime("%Y-%m-%d")
# 获取当前工作目录
base_dir = os.path.dirname(os.path.abspath(__file__))
# 定义数据和日志目录
data_dir = os.path.join(base_dir, "data")
log_dir = os.path.join(data_dir, "log")
# 创建必要的目录
os.makedirs(data_dir, exist_ok=True)
os.makedirs(log_dir, exist_ok=True)
# 检查配置文件是否存在,如果不存在则复制示例配置文件并退出程序
config_path = os.path.join(data_dir, 'bot_config.json')
example_config_path = os.path.join(base_dir, 'bot_config_example.json')
if not os.path.exists(config_path):
if os.path.exists(example_config_path):
shutil.copyfile(example_config_path, config_path)
logging.error(f"Configuration file not found. {example_config_path} copied to {config_path}. Exiting.")
sys.exit(1)
else:
logging.error(f"Configuration file not found and example config file {example_config_path} does not exist. Exiting.")
sys.exit(1)
# 创建一个处理器,该处理器每天午夜都会创建一个新的日志文件 # 创建一个处理器,该处理器每天午夜都会创建一个新的日志文件
handler = TimedRotatingFileHandler(f"received_requests_StartAt{current_date}.log", when="midnight", interval=1, backupCount=10, encoding='utf-8') handler = TimedRotatingFileHandler(os.path.join(log_dir, f"received_requests_StartAt{current_date}.log"), when="midnight", interval=1, backupCount=10, encoding='utf-8')
handler.suffix = "To%Y-%m-%d.log" handler.suffix = "To%Y-%m-%d.log"
# 配置日志 # 配置日志
@ -27,16 +53,20 @@ app = Flask(__name__)
# 读取配置文件 # 读取配置文件
def load_config(): def load_config():
with open('bot_config.json', 'r', encoding='utf-8') as f: with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f) config = json.load(f)
for bot_config in config:
api_url_template = bot_config.get('api_url', '')
main_bot_id = bot_config.get('main_bot_id', '')
if '[AUTO_REPLACE_MAIN_BOT_ID]' in api_url_template:
bot_config['api_url'] = api_url_template.replace('[AUTO_REPLACE_MAIN_BOT_ID]', main_bot_id)
return config
# 保存接收到的请求数据
# 保存接收到的请求数据
def save_received_data(received_url, received_data): def save_received_data(received_url, received_data):
try: try:
current_date = datetime.now().strftime("%Y-%m-%d") current_date = datetime.now().strftime("%Y-%m-%d")
with open(f"received_data_{current_date}.json", "a", encoding='utf-8') as f: with open(os.path.join(log_dir, f"received_data_{current_date}.json"), "a", encoding='utf-8') as f:
json.dump({"received_url": received_url, "received_data": received_data}, f, ensure_ascii=False) json.dump({"received_url": received_url, "received_data": received_data}, f, ensure_ascii=False)
f.write("\n") f.write("\n")
except Exception as e: except Exception as e:
@ -46,7 +76,7 @@ def save_received_data(received_url, received_data):
def save_sent_data(api_url, payload): def save_sent_data(api_url, payload):
try: try:
current_date = datetime.now().strftime("%Y-%m-%d") current_date = datetime.now().strftime("%Y-%m-%d")
with open(f"sent_data_{current_date}.json", "a", encoding='utf-8') as f: with open(os.path.join(log_dir, f"sent_data_{current_date}.json"), "a", encoding='utf-8') as f:
json.dump({"sent_url": api_url, "sent_data": payload}, f, ensure_ascii=False) json.dump({"sent_url": api_url, "sent_data": payload}, f, ensure_ascii=False)
f.write("\n") f.write("\n")
except Exception as e: except Exception as e:
@ -61,11 +91,10 @@ def convert_str_gbk_to_utf8(text_str):
except: except:
return text_str # 如果转换失败,则返回原始字符串 return text_str # 如果转换失败,则返回原始字符串
# 读取待发送的消息 # 读取待发送的消息
def read_pending_messages(): def read_pending_messages():
try: try:
with open("pending_messages.json", "r") as f: with open(os.path.join(data_dir, "pending_messages.json"), "r") as f:
return json.load(f) return json.load(f)
except FileNotFoundError: except FileNotFoundError:
return [] return []
@ -74,7 +103,7 @@ def read_pending_messages():
# 写入待发送的消息 # 写入待发送的消息
def write_pending_messages(messages): def write_pending_messages(messages):
with open("pending_messages.json", "w") as f: with open(os.path.join(data_dir, "pending_messages.json"), "w") as f:
json.dump(messages, f, ensure_ascii=False) json.dump(messages, f, ensure_ascii=False)
# 发送 Telegram 消息 # 发送 Telegram 消息
@ -84,11 +113,15 @@ def send_telegram_message(bot_id, chat_id, title, desp=None, url=None):
# 用于标记是否找到匹配的关键词 # 用于标记是否找到匹配的关键词
found = False found = False
delimiter = None delimiter = None
api_url = None
proxies = None
# 遍历所有主 bot_id 的配置 # 遍历所有主 bot_id 的配置
for config in all_bots_config: for config in all_bots_config:
main_bot_id = config['main_bot_id'] main_bot_id = config['main_bot_id']
main_chat_id = config.get('main_chat_id', '') # 如果没有main_chat_id默认为空字符串 main_chat_id = config.get('main_chat_id', '') # 如果没有main_chat_id默认为空字符串
sub_bots = config['sub_bots'] sub_bots = config['sub_bots']
api_url = config.get('api_url', None)
proxies = config.get('proxies', None)
# 如果传入的 bot_id 和 chat_id 匹配某个主 bot_id 和主 chat_id # 如果传入的 bot_id 和 chat_id 匹配某个主 bot_id 和主 chat_id
if bot_id == main_bot_id and chat_id == main_chat_id: if bot_id == main_bot_id and chat_id == main_chat_id:
# 检查关键词,如果匹配则替换 bot_id 和 chat_id # 检查关键词,如果匹配则替换 bot_id 和 chat_id
@ -96,32 +129,23 @@ def send_telegram_message(bot_id, chat_id, title, desp=None, url=None):
for keyword in sub_bot['keywords']: for keyword in sub_bot['keywords']:
keyword_decode = keyword.decode('utf-8') if isinstance(keyword, bytes) else keyword keyword_decode = keyword.decode('utf-8') if isinstance(keyword, bytes) else keyword
title_decode = title.decode('utf-8') if isinstance(title, bytes) else title title_decode = title.decode('utf-8') if isinstance(title, bytes) else title
desp_decode = desp.decode('utf-8') if isinstance(desp, bytes) and desp is not None else desp if keyword_decode.lower() in title_decode.lower():
if (keyword_decode.lower() in title_decode.lower()) or (desp is not None and keyword_decode.lower() in desp_decode.lower()):
bot_id = sub_bot['bot_id'] bot_id = sub_bot['bot_id']
chat_id = sub_bot['chat_id'] # 替换 chat_id chat_id = sub_bot['chat_id'] # 替换 chat_id
delimiter = sub_bot.get('delimiter') # 获取隔断符配置 delimiter = sub_bot.get('delimiter') # 获取隔断符配置
found = True found = True
break break
if found: if found:
break break
# 一旦找到匹配的主 bot_id 和主 chat_id就跳出循环 # 一旦找到匹配的主 bot_id 和主 chat_id就跳出循环
if found: if found:
break break
api_url = api_url or f"https://api.telegram.org/bot{bot_id}/sendMessage"
api_url = f"https://api.telegram.org/bot{bot_id}/sendMessage"
proxies = {
'http': 'http://127.0.0.1:7890',
'https': 'http://127.0.0.1:7890',
}
text = title # 初始化 text 为 title text = title # 初始化 text 为 title
text += f"\n\n{(desp.split(delimiter)[0] if delimiter and desp else desp) if desp else ''}" text += f"\n\n{(desp.split(delimiter)[0] if delimiter and desp else desp) if desp else ''}"
text =text.rstrip() text = text.rstrip()
# 使用正则表达式来识别受影响的链接 # 使用正则表达式来识别受影响的链接
affected_urls = re.findall(r'(https|http|ftp)\\\\\\/\\\\\\/[\\w\\\\:\\\\/\\.\\-]+', text) affected_urls = re.findall(r'(https|http|ftp)\\\\\\/\\\\\\/[\\w\\\\:\\\\/\\.\\-]+', text)
@ -134,23 +158,22 @@ def send_telegram_message(bot_id, chat_id, title, desp=None, url=None):
if url: # 如果有 url添加到 text if url: # 如果有 url添加到 text
text += f"\n\n<a href=\"{url}\">详情:</a>" text += f"\n\n<a href=\"{url}\">详情:</a>"
text += f"{url}" # 直接添加 URLTelegram 会自动处理预览 text += f"{url}" # 直接添加 URLTelegram 会自动处理预览
text = unescape_url(text)
text=unescape_url(text)
payload = { payload = {
'chat_id': chat_id, 'chat_id': chat_id,
'text': text, 'text': text,
'parse_mode': 'HTML', 'parse_mode': 'HTML',
'disable_web_page_preview': False # 启用网页预览 'disable_web_page_preview': False # 启用网页预览
} }
try: try:
response = requests.post(api_url, data=payload, proxies=proxies, timeout=2) response = requests.post(api_url, data=payload, proxies=proxies, timeout=2)
logging.info(f"response: {response.text}") logging.info(f"response: {response.text}")
if response.status_code == 200 and response.json().get("ok"): if response.status_code == 200 and response.json().get("ok"):
# 保存发送的请求数据 # 保存发送的请求数据
converted_sent_data = convert_str_gbk_to_utf8(str(payload)) converted_sent_data = convert_str_gbk_to_utf8(str(payload))
save_sent_data(api_url,converted_sent_data) save_sent_data(api_url, converted_sent_data)
return True, response.json() return True, response.json()
else: else:
return False, response.json() return False, response.json()
@ -166,18 +189,11 @@ def index():
# 保存接收到的请求数据 # 保存接收到的请求数据
converted_received_data = convert_str_gbk_to_utf8(str(received_data)) converted_received_data = convert_str_gbk_to_utf8(str(received_data))
save_received_data(received_url,converted_received_data) save_received_data(received_url, converted_received_data)
logging.info(f"Received URL: {received_url}") logging.info(f"Received URL: {received_url}")
logging.info(f"Received POST Data: {received_data}") logging.info(f"Received POST Data: {received_data}")
#escaped_desp = received_data.get('desp', '') if received_data else ''
#unescaped_desp = json.loads(f'"{escaped_desp}"')
bot_id = request.args.get('bot_id') or (received_data.get('bot_id') if received_data else None) bot_id = request.args.get('bot_id') or (received_data.get('bot_id') if received_data else None)
chat_id = request.args.get('chat_id') or (received_data.get('chat_id') if received_data else None) chat_id = request.args.get('chat_id') or (received_data.get('chat_id') if received_data else None)
title = request.args.get('title') or (received_data.get('title') if received_data else None) title = request.args.get('title') or (received_data.get('title') if received_data else None)
@ -197,18 +213,16 @@ def index():
# 如果 error_list 不为空,返回错误信息和 400 状态码 # 如果 error_list 不为空,返回错误信息和 400 状态码
if error_list: if error_list:
TestStatus=request.args.get('TestStatus') or (received_data.get('TestStatus') if received_data else None) TestStatus = request.args.get('TestStatus') or (received_data.get('TestStatus') if received_data else None)
if TestStatus is None: if TestStatus is None:
return jsonify({"error": error_list}), 400 return jsonify({"error": error_list}), 400
else: else:
return jsonify({"ok": "the test passed"}), 200 return jsonify({"ok": "the test passed"}), 200
pending_messages = read_pending_messages() pending_messages = read_pending_messages()
success, response = send_telegram_message(bot_id, chat_id, title, desp, url) success, response = send_telegram_message(bot_id, chat_id, title, desp, url)
if success: if success:
new_pending_messages = [] new_pending_messages = []
for msg in pending_messages: for msg in pending_messages:
@ -223,7 +237,7 @@ def index():
desp = f"【This is a delayed message】" desp = f"【This is a delayed message】"
else: else:
desp = desp + f"\n\n【This is a delayed message】" desp = desp + f"\n\n【This is a delayed message】"
pending_messages.append({ pending_messages.append({
'bot_id': bot_id, 'bot_id': bot_id,
'chat_id': chat_id, 'chat_id': chat_id,
@ -235,4 +249,6 @@ def index():
return jsonify({"error": "Failed to send message, added to pending list"}), 200 return jsonify({"error": "Failed to send message, added to pending list"}), 200
if __name__ == "__main__": if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000) config = load_config()
port = config[0].get("port", 5000)
app.run(host='0.0.0.0', port=port)

View File

@ -0,0 +1,74 @@
# Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# 检查是否以管理员权限运行
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
# 请求管理员权限
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
exit
}
# 切换到脚本所在目录
Set-Location $PSScriptRoot
Write-Host "当前目录已切换为脚本所在目录: $PSScriptRoot"
# 获取当前日期和时间
$dateTime = Get-Date -Format "yyyyMMdd"
Write-Host "当前日期: $dateTime"
# 输入提示并获取版本的最后一位
$revision = Read-Host -Prompt "请输入今天的版本次 ($dateTime,如果没有次,请直接回车)"
Write-Host "输入的版本次: $revision"
# 构建版本号
if ([string]::IsNullOrWhiteSpace($revision)) {
$version = "$dateTime"
} else {
$version = "$dateTime" + "_$revision"
}
Write-Host "构建的版本号: $version"
# 构建并打上版本号标签的 Docker 镜像
Write-Host "正在构建 Docker 镜像..."
$tempFileBuild = [System.IO.Path]::GetTempFileName()
docker build -t yshtcn/serverchanpush2telegrambot:$version . 2> $tempFileBuild
if ($LASTEXITCODE -ne 0) {
Write-Host "Docker 镜像构建失败" -ForegroundColor Red
Write-Host (Get-Content $tempFileBuild) -ForegroundColor Red
Remove-Item $tempFileBuild
exit
}
Write-Host "Docker 镜像构建成功"
Remove-Item $tempFileBuild
# 推送带有版本号标签的 Docker 镜像到 Docker Hub
Write-Host "正在推送 Docker 镜像到 Docker Hub..."
$tempFilePush = [System.IO.Path]::GetTempFileName()
docker push yshtcn/serverchanpush2telegrambot:$version 2> $tempFilePush
if ($LASTEXITCODE -ne 0) {
Write-Host "Docker 镜像推送失败" -ForegroundColor Red
Write-Host (Get-Content $tempFilePush) -ForegroundColor Red
Remove-Item $tempFilePush
exit
}
Write-Host "Docker 镜像推送成功"
Remove-Item $tempFilePush
# 为镜像打上 'latest' 标签并推送
Write-Host "正在为镜像打上 'latest' 标签并推送..."
$tempFilePushLatest = [System.IO.Path]::GetTempFileName()
docker tag yshtcn/serverchanpush2telegrambot:$version yshtcn/serverchanpush2telegrambot:latest
docker push yshtcn/serverchanpush2telegrambot:latest 2> $tempFilePushLatest
if ($LASTEXITCODE -ne 0) {
Write-Host "Docker 镜像 'latest' 标签推送失败" -ForegroundColor Red
Write-Host (Get-Content $tempFilePushLatest) -ForegroundColor Red
Remove-Item $tempFilePushLatest
exit
}
Write-Host "Docker 镜像 'latest' 标签推送成功"
Remove-Item $tempFilePushLatest
Write-Host "Docker 镜像构建和推送流程全部完成"

View File

@ -1,9 +1,8 @@
[ [
{ {
"main_bot_id": "YOUR_MAIN_BOT_ID_HERE", "main_bot_id": "YOUR_MAIN_BOT_ID_HERE",
"main_chat_id": "YOUR_MAIN_CHAT_ID_HERE", "main_chat_id": "YOUR_MAIN_CHAT_ID_HERE",
"sub_bots": [ "sub_bots": [
{ {
"bot_id": "YOUR_SUB_BOT_ID_1", "bot_id": "YOUR_SUB_BOT_ID_1",
"chat_id": "YOUR_SUB_CHAT_ID_1", "chat_id": "YOUR_SUB_CHAT_ID_1",
@ -15,6 +14,12 @@
"chat_id": "YOUR_SUB_CHAT_ID_2", "chat_id": "YOUR_SUB_CHAT_ID_2",
"keywords": ["keyword3", "keyword4"] "keywords": ["keyword3", "keyword4"]
} }
] ],
"api_url": "https://api.telegram.org/bot[AUTO_REPLACE_MAIN_BOT_ID]/sendMessage",
"proxies": {
"http": "http://127.0.0.1:7890",
"https": "http://127.0.0.1:7890"
},
"port": 5000
} }
] ]

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
Flask
requests
gunicorn

4
wsgi.py Normal file
View File

@ -0,0 +1,4 @@
from ServerChanPush2TelegramBot import app
if __name__ == "__main__":
app.run()