Skip to content

Commit 6fdfb5c

Browse files
committed
Merge dev for v0.1.2 release
2 parents 53b9717 + ffcbaf1 commit 6fdfb5c

File tree

13 files changed

+575
-328
lines changed

13 files changed

+575
-328
lines changed

Dockerfile

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ COPY requirements.txt .
3434
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt \
3535
&& pip install --no-cache-dir --upgrade pip setuptools wheel \
3636
|| (echo "Wheel build failed, trying with pre-built packages..." && \
37-
pip install --no-cache-dir --only-binary=all -r requirements.txt && \
38-
pip wheel --no-cache-dir --wheel-dir /wheels --only-binary=all -r requirements.txt)
37+
pip install --no-cache-dir --only-binary=all -r requirements.txt && \
38+
pip wheel --no-cache-dir --wheel-dir /wheels --only-binary=all -r requirements.txt)
3939

4040
# 运行阶段:使用最小化镜像
4141
FROM python:3.11-alpine
@@ -64,15 +64,10 @@ COPY --from=builder /wheels /wheels
6464
# 安装预编译的包(避免编译)
6565
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
6666

67-
# 复制应用代码并编译为字节码
67+
# 复制应用代码
6868
COPY src/ ./src/
6969
COPY start.sh .
70-
71-
# 编译所有 Python 文件为字节码,删除源码
72-
RUN python -m compileall -b src/ && \
73-
find src/ -name "*.py" -delete && \
74-
# 确保 __pycache__ 目录存在且可访问
75-
find src/ -name "__pycache__" -type d -exec chmod 755 {} \;
70+
RUN chmod +x start.sh
7671

7772
# 使用非root用户运行(安全考虑)
7873
RUN addgroup -g 1000 appuser && \

README.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ services:
2828
# 可选配置参数
2929
# - PROXY_RULE_FILE=your_proxy_rule_file_path
3030
31-
- LOG_LEVEL=INFO
31+
3232
# - REQUIRED_GROUP_ID=your_group_id_here
3333
# - REQUIRED_GROUP_NAME=Your Group Name
3434
# - REQUIRED_GROUP_LINK=https://t.me/your_group_link
@@ -207,8 +207,7 @@ services:
207207
# 提交者名称固定为 Rule-Bot,邮箱可自定义
208208
209209

210-
# 日志级别 (可选: DEBUG, INFO, WARNING, ERROR)
211-
- LOG_LEVEL=INFO
210+
212211

213212
# 群组验证 (可选: 要求用户加入指定群组才能使用机器人)
214213
# 留空则关闭此功能
@@ -344,7 +343,7 @@ docker pull aethersailor/rule-bot:v1.0.0
344343
| **可选参数** | | | | |
345344
| `PROXY_RULE_FILE` | 可选 | 代理规则文件路径(暂不使用) | `rule/Custom_Proxy.list` | 不填写 |
346345
| `GITHUB_COMMIT_EMAIL` | 可选 | 自定义提交邮箱地址 | `[email protected]` | 系统默认 |
347-
| `LOG_LEVEL` | 可选 | 日志级别 | `INFO` | `INFO` |
346+
348347
| `REQUIRED_GROUP_ID` | 可选 | 群组 ID | `-1002413971610` | 不填写 |
349348
| `REQUIRED_GROUP_NAME` | 可选 | 群组名称 | `Custom_OpenClash_Rules | 交流群` | 不填写 |
350349
| `REQUIRED_GROUP_LINK` | 可选 | 群组链接 | `https://t.me/custom_openclash_rules_group` | 不填写 |
@@ -397,9 +396,8 @@ environment:
397396
- GITHUB_REPO=Aethersailor/Custom_OpenClash_Rules
398397
- DIRECT_RULE_FILE=rule/Custom_Direct.list
399398

400-
# 可选参数(可以不填写,使用默认值)
401-
# - LOG_LEVEL=INFO # 默认值
402-
# - PROXY_RULE_FILE=rule/Custom_Proxy.list # 暂不使用
399+
# 可选参数(可以不填写,使用默认值)
400+
# - PROXY_RULE_FILE=rule/Custom_Proxy.list # 暂不使用
403401
# - [email protected] # 使用系统默认
404402
# - REQUIRED_GROUP_ID=-1002413971610 # 群组验证默认关闭
405403
# - REQUIRED_GROUP_NAME=Custom_OpenClash_Rules | 交流群
@@ -415,9 +413,8 @@ environment:
415413
- GITHUB_REPO=Aethersailor/Custom_OpenClash_Rules
416414
- DIRECT_RULE_FILE=rule/Custom_Direct.list
417415

418-
# 可选参数(根据需要选择填写)
419-
- LOG_LEVEL=INFO # 日志级别
420-
# - PROXY_RULE_FILE=rule/Custom_Proxy.list # 代理规则(暂不使用)
416+
# 可选参数(根据需要选择填写)
417+
# - PROXY_RULE_FILE=rule/Custom_Proxy.list # 代理规则(暂不使用)
421418
# - [email protected] # 自定义邮箱
422419

423420
# 群组验证(需要同时配置三个参数才生效)

docker-compose.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ services:
2626
# 提交者名称固定为 Rule-Bot,邮箱可自定义
2727
2828

29-
# 日志级别 (可选: DEBUG, INFO, WARNING, ERROR)
30-
- LOG_LEVEL=INFO
29+
3130

3231
# 群组验证 (可选: 要求用户加入指定群组才能使用机器人)
3332
# 留空则关闭此功能

requirements.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
python-telegram-bot==20.7
2+
<<<<<<< HEAD
23
aiohttp==3.12.14
34
PyGithub==2.1.1
45
dnspython==2.6.1
56
requests==2.32.4
7+
=======
8+
aiohttp==3.9.1
9+
PyGithub==2.8.1
10+
dnspython==2.4.2
11+
requests==2.31.0
12+
>>>>>>> dev
613
python-dotenv==1.0.0
714
loguru==0.7.2
815
schedule==1.2.0
9-
pyinstaller==6.3.0
16+
pyinstaller==6.3.0
17+
psutil==5.9.6
18+
geoip2==4.7.0
19+
maxminddb==2.5.2

src/bot.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ def __init__(self, config: Config, data_manager: DataManager):
2626
self.app: Optional[Application] = None
2727
self.handler_manager = None # 延迟初始化
2828

29+
async def stop(self):
30+
"""停止机器人"""
31+
logger.info("正在停止机器人...")
32+
if self.handler_manager:
33+
await self.handler_manager.stop()
34+
if self.app:
35+
await self.app.stop()
36+
await self.app.shutdown()
37+
logger.info("机器人已停止")
38+
2939
def start(self):
3040
"""启动机器人"""
3141
try:
@@ -45,14 +55,18 @@ def start(self):
4555
import asyncio
4656

4757
async def run_bot():
48-
async with self.app:
49-
await self.app.start()
50-
await self.app.updater.start_polling(
51-
allowed_updates=Update.ALL_TYPES,
52-
drop_pending_updates=True # 丢弃待处理的更新,避免发送旧消息
53-
)
54-
# 保持运行
55-
await asyncio.Event().wait()
58+
try:
59+
async with self.app:
60+
await self.handler_manager.start() # 显式启动服务(如DNS Session)
61+
await self.app.start()
62+
await self.app.updater.start_polling(
63+
allowed_updates=Update.ALL_TYPES,
64+
drop_pending_updates=True # 丢弃待处理的更新,避免发送旧消息
65+
)
66+
# 保持运行
67+
await asyncio.Event().wait()
68+
finally:
69+
await self.stop()
5670

5771
# 使用新的事件循环运行
5872
asyncio.run(run_bot())

src/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ def __init__(self):
2525
self.PROXY_RULE_FILE = os.getenv("PROXY_RULE_FILE", "") # 可选,暂未启用
2626

2727
# 日志配置
28-
self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
28+
self.LOG_LEVEL = os.getenv("LOG_LEVEL", "WARNING")
2929

3030
# 群组验证配置
3131
self.REQUIRED_GROUP_ID = os.getenv("REQUIRED_GROUP_ID", "")
3232
self.REQUIRED_GROUP_NAME = os.getenv("REQUIRED_GROUP_NAME", "")
3333
self.REQUIRED_GROUP_LINK = os.getenv("REQUIRED_GROUP_LINK", "")
3434

3535
# 数据源URL
36-
self.GEOIP_URL = "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip.dat"
36+
# 使用 Loyalsoldier GeoIP 数据库(针对中国 IP 优化)
37+
self.GEOIP_URL = "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country-without-asn.mmdb"
3738
self.GEOSITE_URL = "https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/refs/heads/release/direct-list.txt"
3839

3940
# DoH服务器配置

src/data_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(self, config: Config):
2727
# 使用临时目录,不需要持久化
2828
import tempfile
2929
self.data_dir = Path(tempfile.gettempdir()) / "rule-bot"
30-
self.geoip_file = self.data_dir / "geoip" / "geoip.dat"
30+
self.geoip_file = self.data_dir / "geoip" / "Country-without-asn.mmdb"
3131
self.geosite_file = self.data_dir / "geosite" / "direct-list.txt"
3232

3333
# 确保目录存在

src/handlers/handler_manager.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ def __init__(self, config: Config, data_manager: DataManager, application=None):
3939
self.group_service = None
4040
if application:
4141
self.group_service = GroupService(config, application.bot)
42+
43+
async def start(self):
44+
"""启动服务"""
45+
if self.dns_service:
46+
await self.dns_service.start()
4247

4348
# 用户状态管理
4449
self.user_states: Dict[int, Dict[str, Any]] = {}
@@ -47,6 +52,12 @@ def __init__(self, config: Config, data_manager: DataManager, application=None):
4752
self.user_add_history: Dict[int, list] = defaultdict(list) # 用户添加历史 {user_id: [timestamp1, timestamp2, ...]}
4853
self.MAX_DESCRIPTION_LENGTH = 20 # 域名说明最大字符数
4954
self.MAX_ADDS_PER_HOUR = 50 # 每小时最多添加域名数
55+
56+
async def stop(self):
57+
"""停止服务"""
58+
if self.dns_service:
59+
await self.dns_service.close()
60+
5061

5162
def get_user_state(self, user_id: int) -> Dict[str, Any]:
5263
"""获取用户状态"""
@@ -1104,4 +1115,6 @@ async def _add_domain_to_github_message(self, message, user_id: int, description
11041115

11051116
except Exception as e:
11061117
logger.error(f"添加域名到GitHub失败: {e}")
1107-
await message.reply_text("添加失败,请重试。")
1118+
await message.reply_text("添加失败,请重试。")
1119+
1120+

src/main.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,95 @@
77
import asyncio
88
import os
99
import sys
10+
import resource
11+
import psutil
12+
import time
1013
from loguru import logger
1114

1215
from .bot import RuleBot
1316
from .config import Config
1417
from .data_manager import DataManager
1518

1619

20+
def set_memory_limit():
21+
"""设置内存限制为256MB(软限制,超出时给出警告)"""
22+
try:
23+
# 256MB = 256 * 1024 * 1024 bytes
24+
memory_limit = 256 * 1024 * 1024
25+
# 设置软限制为256MB,硬限制为512MB(给一些缓冲空间)
26+
resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit * 2))
27+
logger.info(f"已设置内存软限制为 256MB,硬限制为 512MB")
28+
29+
# 记录当前内存使用情况
30+
try:
31+
process = psutil.Process()
32+
current_memory = process.memory_info().rss
33+
logger.info(f"当前内存使用: {current_memory / 1024 / 1024:.1f}MB")
34+
except Exception as e:
35+
logger.warning(f"获取当前内存使用失败: {e}")
36+
37+
except Exception as e:
38+
logger.warning(f"设置内存限制失败: {e}")
39+
# 内存限制设置失败不影响程序运行
40+
41+
def log_memory_usage():
42+
"""记录内存使用情况,接近限制时给出警告"""
43+
# 初始化静态变量(只初始化一次)
44+
if not hasattr(log_memory_usage, '_initialized'):
45+
log_memory_usage.last_warning_time = 0
46+
log_memory_usage.last_warning_level = 0
47+
log_memory_usage.last_normal_log = 0
48+
log_memory_usage._initialized = True
49+
50+
try:
51+
process = psutil.Process()
52+
memory_info = process.memory_info()
53+
memory_mb = memory_info.rss / 1024 / 1024
54+
55+
# 边界检查,确保内存值合理
56+
if memory_mb < 0 or memory_mb > 1000: # 如果内存值异常,记录但不处理
57+
logger.warning(f"内存值异常: {memory_mb:.1f}MB,跳过处理")
58+
return
59+
60+
current_time = time.time()
61+
warning_cooldown = 300 # 5分钟内不重复相同级别的警告
62+
63+
# 检查是否接近硬限制
64+
if memory_mb > 480: # 接近512MB硬限制时紧急警告
65+
if current_time - log_memory_usage.last_warning_time > warning_cooldown or log_memory_usage.last_warning_level != 3:
66+
logger.error(f"🚨 内存使用危急: {memory_mb:.1f}MB (接近512MB硬限制,可能被系统终止)")
67+
# 尝试主动释放一些内存
68+
import gc
69+
gc.collect()
70+
logger.warning("已尝试垃圾回收释放内存")
71+
log_memory_usage.last_warning_time = current_time
72+
log_memory_usage.last_warning_level = 3
73+
elif memory_mb > 240: # 接近256MB软限制时警告
74+
if current_time - log_memory_usage.last_warning_time > warning_cooldown or log_memory_usage.last_warning_level != 2:
75+
logger.warning(f"⚠️ 内存使用过高: {memory_mb:.1f}MB (接近256MB软限制)")
76+
log_memory_usage.last_warning_time = current_time
77+
log_memory_usage.last_warning_level = 2
78+
elif memory_mb > 200: # 超过200MB时提醒
79+
if current_time - log_memory_usage.last_warning_time > warning_cooldown or log_memory_usage.last_warning_level != 1:
80+
logger.warning(f"⚠️ 内存使用较高: {memory_mb:.1f}MB")
81+
log_memory_usage.last_warning_time = current_time
82+
log_memory_usage.last_warning_level = 1
83+
else:
84+
# 正常时只记录一次,避免刷屏
85+
if current_time - log_memory_usage.last_normal_log > 3600: # 1小时记录一次正常状态
86+
logger.info(f"内存使用正常: {memory_mb:.1f}MB")
87+
log_memory_usage.last_normal_log = current_time
88+
log_memory_usage.last_warning_level = 0
89+
90+
except Exception as e:
91+
logger.warning(f"获取内存使用情况失败: {e}")
92+
1793
def main():
1894
"""主程序入口"""
1995
try:
96+
# 设置内存限制
97+
set_memory_limit()
98+
2099
# 初始化配置
21100
config = Config()
22101

@@ -39,11 +118,30 @@ async def init_data():
39118

40119
data_manager = asyncio.run(init_data())
41120

121+
# 记录数据加载后的内存使用
122+
log_memory_usage()
123+
42124
# 初始化机器人
43125
bot = RuleBot(config, data_manager)
44126

45127
# 启动机器人
46128
logger.info("启动Telegram机器人...")
129+
130+
# 启动定期内存检查(每10分钟检查一次)
131+
import threading
132+
133+
def memory_monitor():
134+
while True:
135+
try:
136+
time.sleep(600) # 10分钟
137+
log_memory_usage()
138+
except Exception as e:
139+
logger.warning(f"内存监控出错: {e}")
140+
time.sleep(60) # 出错后等待1分钟再继续
141+
142+
monitor_thread = threading.Thread(target=memory_monitor, daemon=True)
143+
monitor_thread.start()
144+
47145
bot.start()
48146

49147
except KeyboardInterrupt:

0 commit comments

Comments
 (0)