### 更新日志 - 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 datetime import datetime
from logging.handlers import TimedRotatingFileHandler
import os
import sys
import shutil
# 获取当前日期
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"
# 配置日志
@@ -27,16 +53,20 @@ app = Flask(__name__)
# 读取配置文件
def load_config():
with open('bot_config.json', 'r', encoding='utf-8') as f:
return json.load(f)
with open(config_path, 'r', encoding='utf-8') as 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):
try:
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)
f.write("\n")
except Exception as e:
@@ -46,7 +76,7 @@ def save_received_data(received_url, received_data):
def save_sent_data(api_url, payload):
try:
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)
f.write("\n")
except Exception as e:
@@ -61,11 +91,10 @@ def convert_str_gbk_to_utf8(text_str):
except:
return text_str # 如果转换失败,则返回原始字符串
# 读取待发送的消息
def read_pending_messages():
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)
except FileNotFoundError:
return []
@@ -74,7 +103,7 @@ def read_pending_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)
# 发送 Telegram 消息
@@ -84,11 +113,15 @@ def send_telegram_message(bot_id, chat_id, title, desp=None, url=None):
# 用于标记是否找到匹配的关键词
found = False
delimiter = None
api_url = None
proxies = None
# 遍历所有主 bot_id 的配置
for config in all_bots_config:
main_bot_id = config['main_bot_id']
main_chat_id = config.get('main_chat_id', '') # 如果没有main_chat_id默认为空字符串
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
if bot_id == main_bot_id and chat_id == main_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']:
keyword_decode = keyword.decode('utf-8') if isinstance(keyword, bytes) else keyword
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()) or (desp is not None and keyword_decode.lower() in desp_decode.lower()):
if keyword_decode.lower() in title_decode.lower():
bot_id = sub_bot['bot_id']
chat_id = sub_bot['chat_id'] # 替换 chat_id
delimiter = sub_bot.get('delimiter') # 获取隔断符配置
found = True
break
if found:
break
break
# 一旦找到匹配的主 bot_id 和主 chat_id就跳出循环
if found:
break
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',
}
api_url = api_url or f"https://api.telegram.org/bot{bot_id}/sendMessage"
text = title # 初始化 text 为 title
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)
@@ -134,9 +158,7 @@ def send_telegram_message(bot_id, chat_id, title, desp=None, url=None):
if url: # 如果有 url添加到 text
text += f"\n\n<a href=\"{url}\">详情:</a>"
text += f"{url}" # 直接添加 URLTelegram 会自动处理预览
text=unescape_url(text)
text = unescape_url(text)
payload = {
'chat_id': chat_id,
@@ -144,13 +166,14 @@ def send_telegram_message(bot_id, chat_id, title, desp=None, url=None):
'parse_mode': 'HTML',
'disable_web_page_preview': False # 启用网页预览
}
try:
response = requests.post(api_url, data=payload, proxies=proxies, timeout=2)
logging.info(f"response: {response.text}")
if response.status_code == 200 and response.json().get("ok"):
# 保存发送的请求数据
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()
else:
return False, response.json()
@@ -166,18 +189,11 @@ def index():
# 保存接收到的请求数据
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 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)
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)
@@ -197,18 +213,16 @@ def index():
# 如果 error_list 不为空,返回错误信息和 400 状态码
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:
return jsonify({"error": error_list}), 400
else:
return jsonify({"ok": "the test passed"}), 200
pending_messages = read_pending_messages()
success, response = send_telegram_message(bot_id, chat_id, title, desp, url)
if success:
new_pending_messages = []
for msg in pending_messages:
@@ -235,4 +249,6 @@ def index():
return jsonify({"error": "Failed to send message, added to pending list"}), 200
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
# <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7>Թ<EFBFBD><D4B9><EFBFBD>ԱȨ<D4B1><C8A8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
# <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԱȨ<D4B1><C8A8>
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
exit
}
# <20>л<EFBFBD><D0BB><EFBFBD><EFBFBD>ű<EFBFBD><C5B1><EFBFBD><EFBFBD><EFBFBD>Ŀ¼
Set-Location $PSScriptRoot
Write-Host "<EFBFBD><EFBFBD>ǰĿ¼<EFBFBD><EFBFBD><EFBFBD>л<EFBFBD>Ϊ<EFBFBD>ű<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ¼: $PSScriptRoot"
# <20><>ȡ<EFBFBD><C8A1>ǰ<EFBFBD><C7B0><EFBFBD>ں<EFBFBD>ʱ<EFBFBD><CAB1>
$dateTime = Get-Date -Format "yyyyMMdd"
Write-Host "<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>: $dateTime"
# <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><E6B1BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һλ
$revision = Read-Host -Prompt "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>İ汾<EFBFBD><EFBFBD> ($dateTime,<2C><><EFBFBD><EFBFBD>û<EFBFBD>дΣ<D0B4><CEA3><EFBFBD>ֱ<EFBFBD>ӻس<D3BB>)"
Write-Host "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>İ汾<EFBFBD><EFBFBD>: $revision"
# <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E6B1BE>
if ([string]::IsNullOrWhiteSpace($revision)) {
$version = "$dateTime"
} else {
$version = "$dateTime" + "_$revision"
}
Write-Host "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>İ汾<EFBFBD><EFBFBD>: $version"
# <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϰ汾<CFB0>ű<EFBFBD>ǩ<EFBFBD><C7A9> Docker <20><><EFBFBD><EFBFBD>
Write-Host "<EFBFBD><EFBFBD><EFBFBD>ڹ<EFBFBD><EFBFBD><EFBFBD> Docker <20><><EFBFBD><EFBFBD>..."
$tempFileBuild = [System.IO.Path]::GetTempFileName()
docker build -t yshtcn/serverchanpush2telegrambot:$version . 2> $tempFileBuild
if ($LASTEXITCODE -ne 0) {
Write-Host "Docker <20><><EFBFBD>񹹽<EFBFBD>ʧ<EFBFBD><CAA7>" -ForegroundColor Red
Write-Host (Get-Content $tempFileBuild) -ForegroundColor Red
Remove-Item $tempFileBuild
exit
}
Write-Host "Docker <20><><EFBFBD>񹹽<EFBFBD><F1B9B9BD>ɹ<EFBFBD>"
Remove-Item $tempFileBuild
# <20><><EFBFBD>ʹ<EFBFBD><CDB4>а<D0B0>ű<EFBFBD>ǩ<EFBFBD><C7A9> Docker <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Docker Hub
Write-Host "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Docker <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Docker Hub..."
$tempFilePush = [System.IO.Path]::GetTempFileName()
docker push yshtcn/serverchanpush2telegrambot:$version 2> $tempFilePush
if ($LASTEXITCODE -ne 0) {
Write-Host "Docker <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>" -ForegroundColor Red
Write-Host (Get-Content $tempFilePush) -ForegroundColor Red
Remove-Item $tempFilePush
exit
}
Write-Host "Docker <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͳɹ<CDB3>"
Remove-Item $tempFilePush
# Ϊ<><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 'latest' <20><>ǩ<EFBFBD><C7A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Write-Host "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 'latest' <20><>ǩ<EFBFBD><C7A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>..."
$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 <20><><EFBFBD><EFBFBD> 'latest' <20><>ǩ<EFBFBD><C7A9><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>" -ForegroundColor Red
Write-Host (Get-Content $tempFilePushLatest) -ForegroundColor Red
Remove-Item $tempFilePushLatest
exit
}
Write-Host "Docker <20><><EFBFBD><EFBFBD> 'latest' <20><>ǩ<EFBFBD><C7A9><EFBFBD>ͳɹ<CDB3>"
Remove-Item $tempFilePushLatest
Write-Host "Docker <20><><EFBFBD>񹹽<EFBFBD><F1B9B9BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"

View File

@@ -1,4 +1,3 @@
[
{
"main_bot_id": "YOUR_MAIN_BOT_ID_HERE",
@@ -15,6 +14,12 @@
"chat_id": "YOUR_SUB_CHAT_ID_2",
"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()