├── LICENSE ├── README.md ├── README_EN.md ├── monitor.py └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Amchii 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram 消息监控程序 2 | 3 | [English](./README_EN.md) 4 | 5 | ## 项目简介 6 | 7 | 本项目是一个基于 Python 的 Telegram 消息监控程序,利用 [Telethon](https://github.com/LonamiWebs/Telethon) 库与 Telegram API 进行交互。 8 | 在最新版本中,程序在原有功能基础上新增并改进了以下主要特性: 9 | 10 | - **消息监控** 11 | 程序可监听指定对话中的消息,支持关键词匹配、正则表达式匹配以及文件后缀名匹配。 12 | 当匹配成功时,程序可以自动转发消息、发送邮件通知或将匹配结果记录到本地文件。 13 | 此外,新增了**回复功能**: 14 | 当检测到设定关键词时,可根据用户配置的回复词组,从中随机选择一条作为回复内容,在同一对话中直接回复该消息。 15 | 用户还可设置回复前的随机延时(在指定的时间范围内),使回复更自然。 16 | 17 | - **文件后缀名监控** 18 | 针对特定文件后缀(如 `.pdf`、`.docx` 等)的消息自动进行处理,支持自动转发与邮件通知。 19 | 同时新增了用户过滤功能,允许基于用户 ID、用户名或昵称过滤消息。 20 | 新增了文件保存功能,可保存到本地指定文件夹,可指定保存文件的范围 21 | 22 | - **全量监控与用户过滤** 23 | 对指定频道、群组或对话进行全量监控,并支持基于用户 ID、用户名或昵称进行过滤, 24 | 仅处理符合条件的消息,避免无关日志输出。 25 | 此外,还支持将匹配的消息保存到本地文件。 26 | 27 | - **定时消息** 28 | 通过 Cron 表达式配置定时消息任务,支持设置随机延时以及发送后自动删除消息。 29 | 同时,新增了定时开关机功能,可以指定时间开关机和支持 Cron 表达式。 30 | 31 | - **按钮与图片处理** 32 | 针对带有 Inline 按钮和图片的消息,程序会自动调用 AI 模型识别图片内容, 33 | 并根据 AI 的反馈自动选择并点击对应按钮。 34 | 若 AI 模型调用失败,程序会重试(最多一次),若仍失败则放弃处理并删除本地图片。 35 | 36 | - **账号管理(数字序号管理)** 37 | 账号管理现已优化: 38 | - 系统按账号添加顺序为每个账号分配一个数字序号(从 1 开始,添加顺序越早序号越小)。 39 | - 在显示账号列表时,将同时展示账号的序号与手机号; 40 | - 切换账号时,用户只需输入对应的序号即可完成切换,操作更简便直观。 41 | 42 | - **配置管理** 43 | - **一键导出所有账号配置**:将所有已登录账号的配置信息(包括关键词、文件后缀、全量监控、按钮监控、图片监听和定时任务配置)导出到一个 JSON 文件,文件中以账号标识(手机号)作为 key。 44 | - **选择性导入配置**:支持根据导出文件中保存的账号标识,为当前已登录的账号导入配置,且新增的回复功能配置项(回复开关、回复词组、回复延时范围)也会被导入,不足项自动补充默认值。 45 | 46 | - **代理支持** 47 | 支持用户配置代理(socks5、socks4 或 HTTP),方便在受限网络环境下连接 Telegram。 48 | 49 | - **日志记录** 50 | 程序通过日志文件记录关键事件、错误信息及匹配处理过程。 51 | 改进后的日志输出仅记录实际匹配并处理的事件,避免产生冗余日志,使日志内容更加清晰。 52 | 53 | - **新增运行指定次数** 54 | 所有监控配置新增可指定运行次数功能,运行指定次数后配置会自动将配置删除 55 | 56 | - **新增账号频道链接导出** 57 | 将账号所加入的频道和群聊的链接导出为json格式和csv格式 58 | 59 | ## 环境要求 60 | 61 | - Python 3.7 及以上版本 62 | - 依赖库:Telethon、openai、pytz、apscheduler、PySocks、smtplib 等 63 | - 需获取 Telegram 的 `api_id` 与 `api_hash` 64 | - 配置 SMTP 邮箱信息(如需邮件通知) 65 | - 配置 One/NEW API 的 `api_key` 与 `api_base_url` 以调用 AI 模型服务(如需图片识别) 66 | 67 | ## 安装指南 68 | 69 | 1. 克隆或下载本项目代码: 70 | ```bash 71 | git clone https://github.com/djksps1/telegram-monitor.git 72 | 73 | 74 | 2. 安装依赖: 75 | ```bash 76 | pip install -r requirements.txt 77 | 78 | 79 | 3. 获取 Telegram API 凭证(`api_id` 和 `api_hash`)。 80 | 81 | 4. 配置 One/NEW API 的 `api_key` 与 `api_base_url` 以使用 AI 模型服务。 82 | 83 | 5. 配置 SMTP 邮箱信息(如需邮件通知)。 84 | 85 | ## 使用说明 86 | 87 | 1. **运行程序** 88 | 启动程序: 89 | 90 | ```bash 91 | python monitor.py 92 | ``` 93 | 94 | 1. **登录 Telegram** 95 | 程序首次运行时会提示输入 `api_id`、`api_hash`、手机号及验证码(如启用两步验证,还需输入密码)。 96 | 97 | 2. **配置监控参数** 98 | 程序启动后进入交互式命令模式,用户可根据需求添加或修改各类监控配置(包括关键词监控、文件后缀监控、全量监控、按钮监控、定时任务、图片监听)。 99 | 注意:在关键词配置中,您可以启用回复功能,配置回复词组以及回复延时范围,程序将在检测到关键词时自动在原消息所在对话中回复一条随机回复内容。 100 | 101 | 3. **配置管理** 102 | 103 | - 使用 `exportallconfig` 命令可以一键导出所有账号配置,生成的 JSON 文件中包含完整的监控参数(包括新增的回复功能配置)。 104 | 105 | - 使用 `importallconfig` 命令时,程序将读取配置文件中的账号标识(手机号),并自动补充缺失的回复功能字段(如 `reply_enabled`、`reply_texts`、`reply_delay_min`、`reply_delay_max`)到当前配置中。 106 | 107 | 1. **账号管理** 108 | 109 | - 在 `listaccount` 命令中,将显示所有已登录账号的数字序号及对应手机号; 110 | 111 | - 切换账号时,请输入显示的数字序号(例如输入 `1` 切换到第一个添加的账号)。 112 | 113 | 1. **启动监控** 114 | 完成配置后,输入 `start` 命令开始监控消息。 115 | 116 | ## 可用命令列表 117 | 118 | - **账号管理** 119 | - `addaccount`:添加新账号 120 | 121 | - `removeaccount`:移除账号 122 | 123 | - `listaccount`:列出所有账号(以数字序号显示) 124 | 125 | - `switchaccount`:通过输入数字序号切换当前工作账号 126 | 127 | - **配置管理** 128 | - `exportallconfig`:一键导出所有账号配置 129 | 130 | - `importallconfig`:选择性导入配置(支持自动补全新增字段) 131 | 132 | - `blockbot` / `unblockbot`:屏蔽或取消屏蔽指定 Telegram Bot 133 | 134 | - **监控配置管理** 135 | - `addkeyword` / `modifykeyword` / `removekeyword` / `showkeywords`:管理关键词监控 136 | (注:在添加或修改关键词配置时,支持设置自动转发、邮件通知、日志记录以及回复功能) 137 | 138 | - `addext` / `modifyext` / `removeext` / `showext`:管理文件后缀监控(支持用户过滤功能) 139 | 140 | - `addall` / `modifyall` / `removeall` / `showall`:管理全量监控配置(支持消息记录保存到本地) 141 | 142 | - `addbutton` / `modifybutton` / `removebutton` / `showbuttons`:管理按钮关键词监控 143 | 144 | - `addlistener` / `removelistener` / `showlistener`:管理图片+按钮监听对话ID 145 | 146 | - **定时任务管理** 147 | - `schedule` / `modifyschedule` / `removeschedule` / `showschedule`:管理定时消息任务(支持随机延时及自动删除) 148 | 149 | - **监控控制** 150 | - `start` / `stop`:启动或停止监控 151 | 152 | - `exit`:退出程序 153 | 154 | - **频道群聊链接导出** 155 | - `exportlinks` : 频道群聊链接以json或csv格式导出 156 | 157 | ## 注意事项 158 | 159 | - 请遵守 Telegram 使用条款,避免滥用监控功能导致账号受限。 160 | 161 | - 妥善保管您的 `api_id`、`api_hash` 及会话文件,防止账号信息泄露。 162 | 163 | - 如需邮件通知,请正确配置 SMTP 信息及授权码。 164 | 165 | - 配置 One/NEW API 的 `api_key` 与 `api_base_url` 以使用 AI 模型服务进行图片识别。 166 | 167 | - 屏蔽或监测群聊中指定频道或机器人时,频道id输入格式为去除前面的"-100",如频道id为:-1001234567,则输入:1234567,机器人则输入bot_id,监控频道时则正常输入频道id 168 | 169 | ## 常见问题 170 | 171 | 1. **无法连接 Telegram** 172 | 检查网络、代理配置及 API 凭证是否正确。 173 | 174 | 2. **邮件发送失败** 175 | 请确认 SMTP 配置正确,并设置了正确的邮箱授权码。 176 | 177 | 3. **AI 模型调用失败** 178 | 程序会自动重试一次,若仍失败则放弃本次处理并删除本地图片。 179 | 180 | 4. **定时任务不执行** 181 | 检查 Cron 表达式、时区设置,并确认调度器已启动。 182 | 183 | ## 贡献与支持 184 | 185 | 欢迎大家提出建议和改进意见。如有问题或希望新增功能,请提交 Issue 或 Pull Request。 186 | 187 | ## 许可证 188 | 189 | 本项目采用 MIT 许可证。 190 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Telegram Message Monitoring Program 2 | 3 | [中文](./README.md) 4 | 5 | ## Project Introduction 6 | 7 | This project is a Telegram message monitoring program based on Python, using the [Telethon](https://github.com/LonamiWebs/Telethon) library to interact with the Telegram API. 8 | In the latest version, the program introduces and improves the following main features: 9 | 10 | - **Message Monitoring** 11 | The program can monitor messages in specified conversations, supporting keyword matching, regular expression matching, and file extension matching. 12 | When a match is found, the program can automatically forward the message, send an email notification, or log the result to a local file. 13 | Additionally, a **reply feature** has been added: 14 | When a keyword is detected, the program can randomly select a reply from a set of pre-configured phrases and reply directly in the same conversation. 15 | Users can also set a random delay before the reply (within a specified time range), making the reply feel more natural. 16 | 17 | - **Document suffix monitoring** 18 | Automatically process messages with specific file extensions (e.g. `.pdf`, `.docx`, etc.), supporting automatic forwarding and email notification. 19 | A new user filtering feature has also been added, allowing messages to be filtered based on user ID, username or nickname. 20 | Added file saving function, can save to local specified folder, can specify the scope of the saved file 21 | 22 | - **Full Monitoring and User Filtering** 23 | The program can monitor all messages in specified channels, groups, or conversations and supports filtering based on user ID, username, or nickname. 24 | It only processes messages that meet the conditions, avoiding irrelevant log outputs. 25 | Furthermore, it supports saving matched messages to local files. 26 | 27 | - **Scheduled Messages** 28 | Users can configure scheduled message tasks using Cron expressions, with options for random delays and automatic message deletion after sending. 29 | A new scheduled power on/off feature has been added, allowing the program to be turned on/off at specified times, also supporting Cron expressions. 30 | 31 | - **Button and Image Handling** 32 | For messages with Inline buttons and images, the program will automatically call an AI model to recognize the image content, 33 | and based on the AI feedback, automatically select and click the corresponding button. 34 | If the AI model call fails, the program will retry (up to once), and if it still fails, it will skip the processing and delete the local image. 35 | 36 | - **Account Management (Numeric Serial Number Management)** 37 | Account management has been optimized: 38 | - The system assigns a numeric serial number to each account based on the order of addition (starting from 1, with earlier additions having smaller numbers). 39 | - The account list will show both the serial number and the phone number; 40 | - When switching accounts, users can simply input the corresponding serial number to switch, making the operation more intuitive. 41 | 42 | - **Configuration Management** 43 | - **One-click Export All Account Configurations**: Export the configuration information (including keywords, file extensions, full monitoring, button monitoring, image listening, and scheduled task configurations) of all logged-in accounts to a JSON file, with the account identifier (phone number) as the key. 44 | - **Selective Import of Configurations**: Supports importing configurations for the currently logged-in accounts based on the account identifier in the exported file. The newly added reply feature configuration items (reply switch, reply phrases, reply delay range) will also be imported, and missing items will be automatically filled with default values. 45 | 46 | - **Proxy Support** 47 | Supports user-configured proxies (socks5, socks4, or HTTP) for connecting to Telegram in restricted network environments. 48 | 49 | - **Log Recording** 50 | The program records key events, error information, and the matching processing process in log files. 51 | The improved log output only records actual matched and processed events, avoiding redundant logs, making the log content clearer. 52 | 53 | - **New Feature: Run a Specified Number of Times** 54 | All monitoring configurations now support specifying the number of runs. After the specified number of runs, the program will automatically delete the configuration. 55 | 56 | - **New Account Channel Link Export** 57 | Export the links of channels and group chats that the account has joined into JSON and CSV formats. 58 | 59 | ## Environment Requirements 60 | 61 | - Python 3.7 or higher 62 | - Dependencies: Telethon, openai, pytz, apscheduler, PySocks, smtplib, etc. 63 | - Telegram `api_id` and `api_hash` are required 64 | - SMTP email information (if email notifications are needed) 65 | - One/NEW API's `api_key` and `api_base_url` for AI model services (if image recognition is required) 66 | 67 | ## Installation Guide 68 | 69 | 1. Clone or download the project code: 70 | ```bash 71 | git clone https://github.com/djksps1/telegram-monitor.git 72 | 73 | 74 | 2. Install dependencies: 75 | ```bash 76 | pip install -r requirements.txt 77 | 78 | 79 | 3. Obtain Telegram API credentials (`api_id` and `api_hash`). 80 | 81 | 4. Configure One/NEW API's `api_key` and `api_base_url` for AI model services. 82 | 83 | 5. Configure SMTP email information (if email notifications are needed). 84 | 85 | ## Usage Instructions 86 | 87 | 1. **Run the Program** 88 | Start the program: 89 | 90 | ```bash 91 | python monitor.py 92 | ``` 93 | 94 | 2. **Login to Telegram** 95 | When running the program for the first time, it will prompt you to enter `api_id`, `api_hash`, phone number, and verification code (if two-step verification is enabled, a password is also required). 96 | 97 | 3. **Configure Monitoring Parameters** 98 | After the program starts, it enters an interactive command mode where users can add or modify various monitoring configurations (including keyword monitoring, file extension monitoring, full monitoring, button monitoring, scheduled tasks, and image listening). 99 | Note: In the keyword configuration, you can enable the reply feature, configure reply phrases, and set the reply delay range. The program will automatically reply with a random reply in the same conversation when the keyword is detected. 100 | 101 | 4. **Configuration Management** 102 | - Use the `exportallconfig` command to export all account configurations, generating a JSON file that includes the full monitoring parameters (including the new reply feature configurations). 103 | 104 | - Use the `importallconfig` command to import configurations based on the account identifier (phone number) in the exported file, and it will automatically fill in the missing reply feature fields (such as `reply_enabled`, `reply_texts`, `reply_delay_min`, `reply_delay_max`). 105 | 106 | 5. **Account Management** 107 | - In the `listaccount` command, all logged-in accounts will be displayed with numeric serial numbers and corresponding phone numbers; 108 | 109 | - When switching accounts, simply input the displayed numeric serial number (e.g., input `1` to switch to the first added account). 110 | 111 | 6. **Start Monitoring** 112 | After configuration, enter the `start` command to begin monitoring messages. 113 | 114 | ## Available Commands List 115 | 116 | - **Account Management** 117 | - `addaccount`: Add a new account 118 | 119 | - `removeaccount`: Remove an account 120 | 121 | - `listaccount`: List all accounts (displayed by numeric serial number) 122 | 123 | - `switchaccount`: Switch to the current working account by entering the numeric serial number 124 | 125 | - **Configuration Management** 126 | - `exportallconfig`: Export all account configurations 127 | 128 | - `importallconfig`: Selectively import configurations (supports automatic filling of new fields) 129 | 130 | - `blockbot` / `unblockbot`: Block or unblock a specified Telegram Bot 131 | 132 | - **Monitoring Configuration Management** 133 | - `addkeyword` / `modifykeyword` / `removekeyword` / `showkeywords`: Manage keyword monitoring 134 | 135 | - `addext` / `modifyext` / `removeext` / `showext`: Manage file extension monitoring (supports user filtering) 136 | 137 | - `addall` / `modifyall` / `removeall` / `showall`: Manage full monitoring configurations (supports saving messages to local files) 138 | 139 | - `addbutton` / `modifybutton` / `removebutton` / `showbuttons`: Manage button keyword monitoring 140 | 141 | - `addistener` / `removelistener` / `showlistener`: Manage image + button listening conversation IDs 142 | 143 | - **Scheduled Task Management** 144 | - `schedule` / `modifyschedule` / `removeschedule` / `showschedule`: Manage scheduled message tasks (supports random delays and automatic deletion) 145 | 146 | - **Monitoring Control** 147 | - `start` / `stop`: Start or stop monitoring 148 | 149 | - `exit`: Exit the program 150 | 151 | - **Channel Group Chat Link Export** 152 | - `exportlinks`: Export channel and group chat links in JSON or CSV format 153 | 154 | 155 | ## Notes 156 | 157 | - Please comply with Telegram's terms of use to avoid account limitations due to improper use of monitoring features. 158 | 159 | - Safeguard your `api_id`, `api_hash`, and session files to prevent account information leakage. 160 | 161 | - If you require email notifications, please configure the SMTP information and authorization code correctly. 162 | 163 | - Configure One/NEW API's `api_key` and `api_base_url` to use AI model services for image recognition. 164 | 165 | - When blocking or monitoring a specific channel or bot in a group chat, input the channel ID without the leading "-100", for example, if the channel ID is `-1001234567`, enter `1234567`. For bots, input the `bot_id`. When monitoring channels, enter the channel ID normally. 166 | 167 | ## Frequently Asked Questions 168 | 169 | 1. **Unable to connect to Telegram** 170 | Check the network, proxy configuration, and API credentials. 171 | 172 | 2. **Email sending failure** 173 | Ensure that the SMTP configuration is correct and the correct email authorization code is set. 174 | 175 | 3. **AI model call failure** 176 | The program will retry once, and if it fails again, it will skip the processing and delete the local image. 177 | 178 | 4. **Scheduled task not executing** 179 | Check the Cron expression, time zone settings, and ensure the scheduler is running. 180 | 181 | ## Contributions and Support 182 | 183 | We welcome suggestions and improvements. If you have any questions or wish to request new features, please submit an Issue or Pull Request. 184 | 185 | ## License 186 | 187 | This project is licensed under the MIT License. 188 | -------------------------------------------------------------------------------- /monitor.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from telethon import TelegramClient, events 3 | from telethon.tl.types import Channel, Chat, MessageMediaDocument 4 | import asyncio 5 | import logging 6 | from datetime import datetime, timedelta 7 | from telethon.errors import SessionPasswordNeededError 8 | import smtplib 9 | from email.mime.text import MIMEText 10 | from email.mime.multipart import MIMEMultipart 11 | from email.header import Header 12 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 13 | from apscheduler.triggers.cron import CronTrigger 14 | import os 15 | import pytz 16 | import re 17 | import random 18 | import shutil 19 | import base64 20 | import json 21 | from openai import OpenAI 22 | import socks 23 | import csv 24 | from telethon import functions, types 25 | 26 | # 配置One/New API 27 | ONEAPI_KEY = "你的 API 令牌" # 替换为实际的 API 令牌 28 | ONEAPI_BASE_URL = "http://你的 API 地址/v1" # API 的服务地址 29 | client_ai = OpenAI(api_key=ONEAPI_KEY, base_url=ONEAPI_BASE_URL) 30 | 31 | # === 配置部分 === 32 | SMTP_SERVER = "smtp.qq.com" # SMTP 服务器,例如 QQ 邮箱 33 | SMTP_PORT = 465 # SMTP 端口,通常为 465 34 | SENDER_EMAIL = "您的邮箱@example.com" # 发件人邮箱 35 | EMAIL_PASSWORD = "您的邮箱授权码" # 邮箱授权码或密码 36 | RECIPIENT_EMAIL = "收件人邮箱@example.com" # 收件人邮箱 37 | 38 | ACCOUNTS = {} 39 | BLOCKED_BOTS = set() 40 | current_account = None 41 | processed_messages = set() 42 | 43 | 44 | def setup_logger(): 45 | logger = logging.getLogger('telegram_monitor') 46 | logger.setLevel(logging.INFO) 47 | fh = logging.FileHandler('telegram_monitor.log', encoding='utf-8') 48 | ch = logging.StreamHandler(stream=sys.stdout) 49 | ch.setLevel(logging.INFO) 50 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 51 | fh.setFormatter(formatter) 52 | ch.setFormatter(formatter) 53 | logger.addHandler(fh) 54 | logger.addHandler(ch) 55 | return logger 56 | 57 | 58 | logger = setup_logger() 59 | 60 | 61 | async def ainput(prompt: str = '') -> str: 62 | loop = asyncio.get_event_loop() 63 | print(prompt, end='', flush=True) 64 | return (await loop.run_in_executor(None, sys.stdin.readline)).rstrip('\n') 65 | 66 | 67 | def send_email(message_text): 68 | try: 69 | message = MIMEMultipart() 70 | message["From"] = SENDER_EMAIL 71 | message["To"] = RECIPIENT_EMAIL 72 | message["Subject"] = Header("Telegram 监控消息", "utf-8") 73 | body = MIMEText(message_text, "plain", "utf-8") 74 | message.attach(body) 75 | server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) 76 | server.login(SENDER_EMAIL, EMAIL_PASSWORD) 77 | server.sendmail(SENDER_EMAIL, RECIPIENT_EMAIL, message.as_string()) 78 | logger.info("邮件发送成功") 79 | except Exception as e: 80 | logger.error(f"邮件发送失败: {e}") 81 | finally: 82 | try: 83 | server.quit() 84 | except: 85 | pass 86 | 87 | async def export_channels_links(account_id=None, export_format='json'): 88 | 89 | logger.info(f"开始导出频道群组链接,格式:{export_format}") 90 | 91 | result = {} 92 | export_accounts = [] 93 | 94 | if account_id: 95 | if account_id in ACCOUNTS: 96 | export_accounts.append(account_id) 97 | else: 98 | logger.error(f"账号 {account_id} 不存在") 99 | return 100 | else: 101 | export_accounts = list(ACCOUNTS.keys()) 102 | 103 | if not export_accounts: 104 | logger.warning("没有可导出的账号") 105 | return 106 | 107 | for acc_id in export_accounts: 108 | try: 109 | client = ACCOUNTS[acc_id]["client"] 110 | 111 | if not client.is_connected(): 112 | await client.connect() 113 | 114 | logger.info(f"获取账号 {acc_id} 的对话列表") 115 | channels_groups = [] 116 | 117 | async for dialog in client.iter_dialogs(): 118 | entity = dialog.entity 119 | 120 | if (isinstance(entity, types.Channel) or isinstance(entity, types.Chat)): 121 | chat_info = { 122 | "id": entity.id, 123 | "title": entity.title, 124 | "type": "channel" if isinstance(entity, types.Channel) and entity.broadcast else "group", 125 | } 126 | 127 | if hasattr(entity, 'username') and entity.username: 128 | if isinstance(entity, types.Channel): 129 | if entity.broadcast: 130 | chat_info["link"] = f"https://t.me/{entity.username}" 131 | else: 132 | chat_info["link"] = f"https://t.me/{entity.username}" 133 | else: 134 | chat_info["link"] = f"https://t.me/{entity.username}" 135 | else: 136 | try: 137 | if isinstance(entity, types.Channel): 138 | full_entity = await client(functions.channels.GetFullChannelRequest(entity)) 139 | if hasattr(full_entity, 'full_chat') and hasattr(full_entity.full_chat, 140 | 'exported_invite'): 141 | chat_info["link"] = full_entity.full_chat.exported_invite.link 142 | else: 143 | chat_info["link"] = "私有频道/群组,无法获取链接" 144 | else: 145 | chat_info["link"] = "私有频道/群组,无法获取链接" 146 | except Exception as e: 147 | logger.debug(f"获取频道 {entity.title} 邀请链接失败: {str(e)}") 148 | chat_info["link"] = "私有频道/群组,无法获取链接" 149 | 150 | channels_groups.append(chat_info) 151 | 152 | result[acc_id] = channels_groups 153 | logger.info(f"账号 {acc_id} 共有 {len(channels_groups)} 个频道/群组") 154 | 155 | except Exception as e: 156 | logger.error(f"导出账号 {acc_id} 的频道群组链接时出错: {str(e)}") 157 | 158 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 159 | if export_format.lower() == 'json': 160 | filename = f"channel_links_{timestamp}.json" 161 | with open(filename, 'w', encoding='utf-8') as f: 162 | json.dump(result, f, ensure_ascii=False, indent=4) 163 | else: # csv format 164 | filename = f"channel_links_{timestamp}.csv" 165 | with open(filename, 'w', newline='', encoding='utf-8') as f: 166 | writer = csv.writer(f) 167 | writer.writerow(['账号ID', '频道/群组ID', '名称', '类型', '链接']) 168 | 169 | for acc_id, channels in result.items(): 170 | for channel in channels: 171 | writer.writerow([ 172 | acc_id, 173 | channel['id'], 174 | channel['title'], 175 | channel['type'], 176 | channel['link'] 177 | ]) 178 | 179 | logger.info(f"频道群组链接已导出到文件: {filename}") 180 | return filename 181 | def match_user(sender, user_set, user_option): 182 | if not user_set: 183 | return True 184 | if not sender: 185 | return False 186 | if user_option == '1': 187 | sender_id = sender.id 188 | sender_id_str = str(sender_id) 189 | if sender_id_str.startswith("-100"): 190 | short_id = sender_id_str[4:] 191 | else: 192 | short_id = sender_id_str 193 | user_set_str = {str(x) for x in user_set} 194 | if sender_id_str in user_set_str or short_id in user_set_str: 195 | return True 196 | return False 197 | elif user_option == '2': 198 | sender_username = getattr(sender, 'username', '').lower() 199 | return sender_username in {u.lower() for u in user_set} 200 | elif user_option == '3': 201 | if hasattr(sender, 'first_name'): 202 | sender_full = f"{sender.first_name or ''} {sender.last_name or ''}".strip() 203 | else: 204 | sender_full = getattr(sender, 'title', '').strip() 205 | return sender_full in user_set 206 | else: 207 | return True 208 | 209 | 210 | def default_config(): 211 | return { 212 | "keyword_config": {}, 213 | "file_extension_config": {}, 214 | "all_messages_config": {}, 215 | "button_keyword_config": {}, 216 | "image_button_monitor": [], 217 | "scheduled_messages": [], 218 | "channel_in_group_config": [] 219 | } 220 | 221 | 222 | def set_account_monitor_active(account_id, status: bool): 223 | if account_id in ACCOUNTS: 224 | ACCOUNTS[account_id]['monitor_active'] = status 225 | logger.info(f"账号 {account_id} 的监控状态已设置为: {'开启' if status else '关闭'}") 226 | 227 | 228 | def set_monitor_active(status: bool): 229 | for acc in ACCOUNTS.values(): 230 | acc['monitor_active'] = status 231 | logger.info(f"全局监控状态已设置为: {'开启' if status else '关闭'}") 232 | 233 | 234 | async def add_account(): 235 | global ACCOUNTS, current_account 236 | print("=== 添加账号 ===") 237 | phone = (await ainput('请输入您的Telegram手机号 (格式如: +8613800138000): ')).strip() 238 | api_id = int((await ainput('请输入您的 api_id: ')).strip()) 239 | api_hash = (await ainput('请输入您的 api_hash: ')).strip() 240 | use_proxy = (await ainput("是否配置代理?(yes/no): ")).strip().lower() == 'yes' 241 | proxy = None 242 | if use_proxy: 243 | print("\n支持的代理类型:socks5, socks4, http") 244 | proxy_type_input = (await ainput("请输入代理类型(例如:socks5): ")).strip().lower() 245 | proxy_host = (await ainput("请输入代理服务器地址(例如:127.0.0.1): ")).strip() 246 | proxy_port_str = (await ainput("请输入代理服务器端口号(例如:1080): ")).strip() 247 | if proxy_host and proxy_port_str.isdigit(): 248 | proxy_port = int(proxy_port_str) 249 | if proxy_type_input == 'socks5': 250 | proxy_type = socks.SOCKS5 251 | elif proxy_type_input == 'socks4': 252 | proxy_type = socks.SOCKS4 253 | elif proxy_type_input == 'http': 254 | proxy_type = socks.HTTP 255 | else: 256 | proxy_type = None 257 | print("不支持的代理类型,将不使用代理连接。") 258 | if proxy_type: 259 | proxy_user = (await ainput("请输入代理用户名(如果不需要认证可回车跳过): ")).strip() 260 | proxy_pass = (await ainput("请输入代理密码(如果不需要认证可回车跳过): ")).strip() 261 | if proxy_user and proxy_pass: 262 | proxy = (proxy_type, proxy_host, proxy_port, True, proxy_user, proxy_pass) 263 | else: 264 | proxy = (proxy_type, proxy_host, proxy_port) 265 | else: 266 | print("代理地址或端口无效,将使用本地网络连接。") 267 | session_name = f"session_{phone.replace('+', '')}" 268 | client = TelegramClient(session_name, api_id, api_hash, proxy=proxy) 269 | try: 270 | await client.connect() 271 | if not await client.is_user_authorized(): 272 | await telegram_login(client) 273 | me = await client.get_me() 274 | own_user_id = me.id 275 | account_id = phone 276 | ACCOUNTS[account_id] = { 277 | "client": client, 278 | "own_user_id": own_user_id, 279 | "phone": phone, 280 | "config": default_config(), 281 | "monitor_active": False 282 | } 283 | client.add_event_handler(lambda event, account_id=account_id: message_handler(event, account_id), 284 | events.NewMessage()) 285 | print(f"账号 {phone} 登录成功,用户ID: {own_user_id}") 286 | asyncio.create_task(client.run_until_disconnected()) 287 | if current_account is None: 288 | current_account = account_id 289 | print(f"当前工作账号设置为:{current_account}") 290 | except Exception as e: 291 | logger.error(f"账号 {phone} 登录失败: {repr(e)}") 292 | print(f"账号 {phone} 登录失败: {repr(e)}") 293 | 294 | 295 | async def remove_account(): 296 | global ACCOUNTS, current_account 297 | account_id = (await ainput("请输入要移除的账号标识(例如手机号): ")).strip() 298 | if account_id in ACCOUNTS: 299 | account = ACCOUNTS.pop(account_id) 300 | try: 301 | await account["client"].disconnect() 302 | print(f"账号 {account_id} 已成功移除并断开连接。") 303 | if current_account == account_id: 304 | current_account = None 305 | except Exception as e: 306 | logger.error(f"断开账号 {account_id} 时出错: {repr(e)}") 307 | print(f"断开账号 {account_id} 时出错: {repr(e)}") 308 | else: 309 | print("未找到该账号。") 310 | 311 | 312 | async def list_accounts(): 313 | if not ACCOUNTS: 314 | print("当前没有登录的账号。") 315 | else: 316 | print("=== 已登录账号列表 ===") 317 | for idx, (account_id, info) in enumerate(ACCOUNTS.items(), start=1): 318 | print(f"{idx}. 电话: {info['phone']}, 用户ID: {info['own_user_id']}") 319 | 320 | 321 | def ensure_keyword_config_defaults(keyword_cfg): 322 | if 'reply_enabled' not in keyword_cfg: 323 | keyword_cfg['reply_enabled'] = False 324 | if 'reply_texts' not in keyword_cfg: 325 | keyword_cfg['reply_texts'] = [] 326 | if 'reply_delay_min' not in keyword_cfg: 327 | keyword_cfg['reply_delay_min'] = 0 328 | if 'reply_delay_max' not in keyword_cfg: 329 | keyword_cfg['reply_delay_max'] = 0 330 | return keyword_cfg 331 | 332 | 333 | def convert_sets_to_lists(obj): 334 | if isinstance(obj, dict): 335 | return {k: convert_sets_to_lists(v) for k, v in obj.items()} 336 | elif isinstance(obj, set): 337 | return list(obj) 338 | elif isinstance(obj, list): 339 | return [convert_sets_to_lists(item) for item in obj] 340 | else: 341 | return obj 342 | 343 | 344 | async def export_all_configs(): 345 | global ACCOUNTS 346 | filepath = (await ainput("请输入导出所有账号配置文件的路径: ")).strip() 347 | if os.path.isdir(filepath): 348 | filepath = os.path.join(filepath, "all_configs.json") 349 | all_configs = {} 350 | for account_id, info in ACCOUNTS.items(): 351 | all_configs[account_id] = { 352 | "phone": info["phone"], 353 | "config": convert_sets_to_lists(info["config"]) 354 | } 355 | try: 356 | with open(filepath, "w", encoding="utf-8") as f: 357 | json.dump(all_configs, f, ensure_ascii=False, indent=4) 358 | print(f"所有账号配置已导出到 {filepath}") 359 | except Exception as e: 360 | logger.error(f"导出配置时发生错误:{repr(e)}") 361 | print(f"导出配置时发生错误:{repr(e)}") 362 | 363 | 364 | async def import_all_configs(): 365 | global ACCOUNTS 366 | filepath = (await ainput("请输入要导入配置文件的路径: ")).strip() 367 | try: 368 | with open(filepath, "r", encoding="utf-8") as f: 369 | all_configs = json.load(f) 370 | 371 | account_keys = list(all_configs.keys()) 372 | print("配置文件中包含以下账号:") 373 | for idx, account_id in enumerate(account_keys, start=1): 374 | print(f"{idx}. {account_id}") 375 | selection = (await ainput("请输入要导入配置的账号序号(多个逗号分隔): ")).strip() 376 | selected_numbers = [s.strip() for s in selection.split(',') if s.strip()] 377 | selected_ids = [] 378 | for num_str in selected_numbers: 379 | if num_str.isdigit(): 380 | index = int(num_str) - 1 381 | if 0 <= index < len(account_keys): 382 | selected_ids.append(account_keys[index]) 383 | else: 384 | print(f"无效的序号: {num_str}") 385 | else: 386 | print(f"无效输入: {num_str}") 387 | imported = 0 388 | for account_id in selected_ids: 389 | if account_id in all_configs: 390 | config = all_configs[account_id]["config"] 391 | keyword_cfg = config.get("keyword_config", {}) 392 | for key, cfg_item in keyword_cfg.items(): 393 | if 'max_executions' not in cfg_item: 394 | cfg_item['max_executions'] = None 395 | if 'execution_count' not in cfg_item: 396 | cfg_item['execution_count'] = 0 397 | if 'blocked_users' not in cfg_item: 398 | cfg_item['blocked_users'] = [] 399 | if 'reply_enabled' not in cfg_item: 400 | cfg_item['reply_enabled'] = False 401 | if 'reply_texts' not in cfg_item: 402 | cfg_item['reply_texts'] = [] 403 | if 'reply_delay_min' not in cfg_item: 404 | cfg_item['reply_delay_min'] = 0 405 | if 'reply_delay_max' not in cfg_item: 406 | cfg_item['reply_delay_max'] = 0 407 | config["keyword_config"] = keyword_cfg 408 | 409 | ext_cfg = config.get("file_extension_config", {}) 410 | for key, cfg_item in ext_cfg.items(): 411 | if 'max_executions' not in cfg_item: 412 | cfg_item['max_executions'] = None 413 | if 'execution_count' not in cfg_item: 414 | cfg_item['execution_count'] = 0 415 | if 'blocked_users' not in cfg_item: 416 | cfg_item['blocked_users'] = [] 417 | if 'save_folder' not in cfg_item: 418 | cfg_item['save_folder'] = None 419 | if 'min_size' not in cfg_item: 420 | cfg_item['min_size'] = None 421 | if 'max_size' not in cfg_item: 422 | cfg_item['max_size'] = None 423 | config["file_extension_config"] = ext_cfg 424 | 425 | all_msg_cfg = config.get("all_messages_config", {}) 426 | new_all_msg_cfg = {} 427 | for chat_id_key, cfg_item in all_msg_cfg.items(): 428 | try: 429 | chat_id_int = int(chat_id_key) 430 | except Exception as e: 431 | chat_id_int = chat_id_key 432 | if 'max_executions' not in cfg_item: 433 | cfg_item['max_executions'] = None 434 | if 'execution_count' not in cfg_item: 435 | cfg_item['execution_count'] = 0 436 | if 'blocked_users' not in cfg_item: 437 | cfg_item['blocked_users'] = [] 438 | new_all_msg_cfg[chat_id_int] = cfg_item 439 | config["all_messages_config"] = new_all_msg_cfg 440 | 441 | button_cfg = config.get("button_keyword_config", {}) 442 | for key, b_config in button_cfg.items(): 443 | b_config['chats'] = set(b_config.get('chats', [])) 444 | b_config['users'] = set(b_config.get('users', [])) 445 | if 'mode' not in b_config: 446 | b_config['mode'] = "manual" 447 | if 'ai_prompt' not in b_config: 448 | b_config['ai_prompt'] = "" 449 | if 'button_keyword' not in b_config: 450 | b_config['button_keyword'] = key 451 | if 'max_executions' not in b_config: 452 | b_config['max_executions'] = None 453 | if 'execution_count' not in b_config: 454 | b_config['execution_count'] = 0 455 | config["button_keyword_config"] = button_cfg 456 | 457 | image_button_monitor = config.get("image_button_monitor", []) 458 | new_image_button_monitor = [] 459 | for item in image_button_monitor: 460 | if isinstance(item, dict): 461 | if 'max_executions' not in item: 462 | item['max_executions'] = None 463 | if 'execution_count' not in item: 464 | item['execution_count'] = 0 465 | new_image_button_monitor.append(item) 466 | else: 467 | try: 468 | chat_id_val = int(item) 469 | new_image_button_monitor.append({ 470 | "chat_id": chat_id_val, 471 | "max_executions": None, 472 | "execution_count": 0 473 | }) 474 | except: 475 | continue 476 | config["image_button_monitor"] = new_image_button_monitor 477 | 478 | scheduled_messages = config.get("scheduled_messages", []) 479 | config["scheduled_messages"] = scheduled_messages 480 | for sched in scheduled_messages: 481 | if not scheduler.get_job(sched["job_id"]): 482 | try: 483 | job = scheduler.add_job( 484 | send_scheduled_message_wrapper, 485 | CronTrigger.from_crontab(sched['cron'], timezone=pytz.timezone('Asia/Shanghai')), 486 | args=[sched["job_id"], sched['target_id'], sched['message'], 487 | sched.get('random_offset', 0), 488 | sched.get('delete_after_sending', False), 489 | sched.get('account_id')], 490 | id=sched["job_id"] 491 | ) 492 | logger.info(f"重新加载定时任务,Job ID: {job.id}") 493 | except Exception as e: 494 | logger.error(f"添加定时任务时出错: {e}") 495 | 496 | if account_id in ACCOUNTS: 497 | ACCOUNTS[account_id]["config"] = config 498 | imported += 1 499 | 500 | for sched in config.get("scheduled_messages", []): 501 | if not scheduler.get_job(sched["job_id"]): 502 | try: 503 | job = scheduler.add_job( 504 | send_scheduled_message, 505 | CronTrigger.from_crontab(sched['cron'], timezone=pytz.timezone('Asia/Shanghai')), 506 | args=[sched['target_id'], sched['message'], 507 | sched.get('random_offset', 0), 508 | sched.get('delete_after_sending', False), 509 | sched.get('account_id')], 510 | id=sched["job_id"] 511 | ) 512 | logger.info(f"重新加载定时任务,Job ID: {job.id}") 513 | except Exception as e: 514 | logger.error(f"添加定时任务时出错: {e}") 515 | else: 516 | print(f"当前系统中不存在账号 {account_id},请先添加该账号") 517 | else: 518 | print(f"配置文件中未找到账号 {account_id}") 519 | print(f"已成功导入 {imported} 个账号的配置") 520 | except Exception as e: 521 | logger.error(f"导入配置时发生错误:{repr(e)}") 522 | print(f"导入配置时发生错误:{repr(e)}") 523 | 524 | 525 | async def get_ai_answer(ai_messages, max_retries=2, retry_delay=10): 526 | attempt = 0 527 | while attempt < max_retries: 528 | attempt += 1 529 | try: 530 | response = client_ai.chat.completions.create( 531 | model="gpt-4o", 532 | messages=ai_messages 533 | ) 534 | ai_answer = response.choices[0].message.content.strip().lower() 535 | logger.info(f"AI模型返回的结果: {ai_answer}") 536 | return ai_answer 537 | except Exception as e: 538 | logger.error(f"AI模型调用时发生错误(第{attempt}次): {e}") 539 | if attempt < max_retries: 540 | logger.info(f"{retry_delay}秒后重试...") 541 | await asyncio.sleep(retry_delay) 542 | else: 543 | logger.info("多次尝试仍失败,放弃AI调用。") 544 | return None 545 | 546 | 547 | async def click_button_by_ai_answer(message, ai_answer, exact=False): 548 | if not ai_answer: 549 | return False 550 | for row_i, row in enumerate(message.buttons): 551 | for col_i, button in enumerate(row): 552 | btn_text = button.text.strip().lower() 553 | if exact: 554 | if btn_text == ai_answer: 555 | await message.click(row_i, col_i) 556 | logger.info(f"已点击通过AI选择的按钮: {button.text}") 557 | return True 558 | else: 559 | if ai_answer in btn_text: 560 | await message.click(row_i, col_i) 561 | logger.info(f"已点击通过AI选择的按钮: {button.text}") 562 | return True 563 | return False 564 | 565 | 566 | async def message_handler(event, account_id): 567 | account = ACCOUNTS.get(account_id) 568 | if not account: 569 | return 570 | if not account.get('monitor_active', False): 571 | return 572 | 573 | own_user_id = account["own_user_id"] 574 | keyword_config = account["config"]["keyword_config"] 575 | file_extension_config = account["config"]["file_extension_config"] 576 | all_messages_config = account["config"]["all_messages_config"] 577 | button_keyword_config = account["config"]["button_keyword_config"] 578 | image_button_monitor = set(account["config"]["image_button_monitor"]) 579 | channel_in_group_config = account["config"].get("channel_in_group_config", []) 580 | 581 | chat_id = event.chat_id 582 | message_id = event.message.id 583 | if (account_id, chat_id, message_id) in processed_messages: 584 | return 585 | processed_messages.add((account_id, chat_id, message_id)) 586 | 587 | def get_fwd_channel_id(fwd): 588 | if not fwd: 589 | return None 590 | if hasattr(fwd, 'from_chat') and fwd.from_chat is not None: 591 | return getattr(fwd.from_chat, 'id', None) 592 | if hasattr(fwd, 'from_id') and fwd.from_id: 593 | return getattr(fwd.from_id, 'channel_id', None) 594 | return None 595 | 596 | try: 597 | message_text = event.raw_text or '' 598 | message_text_lower = message_text.lower().strip() 599 | sender = await event.get_sender() 600 | if not sender: 601 | post_author = event.message.post_author 602 | if post_author: 603 | class PseudoSender: 604 | def __init__(self, name, channel_id): 605 | self.id = channel_id # 用群组ID当作 sender.id 606 | self.username = None 607 | self.first_name = name 608 | self.last_name = "" 609 | self.bot = False 610 | 611 | sender = PseudoSender(post_author, event.chat_id) 612 | else: 613 | class PseudoSender: 614 | def __init__(self, channel_id): 615 | self.id = channel_id 616 | self.username = "" 617 | self.first_name = "未知" 618 | self.last_name = "" 619 | self.bot = False 620 | 621 | sender = PseudoSender(event.chat_id) 622 | 623 | sender_id = sender.id 624 | username = getattr(sender, 'username', '') 625 | if hasattr(sender, 'first_name'): 626 | name = f"{sender.first_name or ''} {sender.last_name or ''}".strip() 627 | else: 628 | name = getattr(sender, 'title', '') 629 | 630 | if getattr(sender, "bot", False) and sender_id in BLOCKED_BOTS: 631 | return 632 | if sender_id == own_user_id: 633 | return 634 | 635 | def is_not_blocked(config): 636 | if 'blocked_users' in config and str(sender_id) in config['blocked_users']: 637 | return False 638 | local_fwd = get_fwd_channel_id(event.message.fwd_from) 639 | if local_fwd is not None and 'blocked_channels' in config and local_fwd in config['blocked_channels']: 640 | return False 641 | if 'blocked_bots' in config and sender_id in config['blocked_bots']: 642 | return False 643 | return True 644 | 645 | def should_listen(config, sender, event): 646 | user_filter = config.get('users', []) 647 | bot_filter = config.get('match_bots', []) 648 | channel_filter = config.get('match_channels', []) 649 | if not (user_filter or bot_filter or channel_filter): 650 | return True 651 | allowed = False 652 | if user_filter and match_user(sender, set(user_filter), config.get('user_option')): 653 | allowed = True 654 | if not allowed and bot_filter and getattr(sender, "bot", False) and sender_id in bot_filter: 655 | allowed = True 656 | if not allowed: 657 | local_fwd = get_fwd_channel_id(event.message.fwd_from) 658 | if local_fwd is None and isinstance(sender, Channel): 659 | local_fwd = sender.id 660 | if channel_filter and local_fwd is not None and local_fwd in channel_filter: 661 | allowed = True 662 | return allowed 663 | 664 | if chat_id in all_messages_config: 665 | config = all_messages_config[chat_id] 666 | if is_not_blocked(config) and should_listen(config, sender, event): 667 | logger.info( 668 | f"匹配到全量监控消息: 对话ID={chat_id}, 发送者: id={sender_id}, 名称={name}, 用户名={username}") 669 | if config.get('email_notify'): 670 | send_email(f"检测到来自对话 {chat_id} 的消息: {message_text}") 671 | if config.get('auto_forward'): 672 | for target_id in config.get('forward_targets', []): 673 | await account["client"].forward_messages(target_id, event.message) 674 | if config.get('log_file'): 675 | try: 676 | with open(config.get('log_file'), 'a', encoding='utf-8') as f: 677 | f.write( 678 | f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 全量监控 - 对话 {chat_id} 消息: {message_text}\n") 679 | except Exception as e: 680 | logger.error(f"写入全量监控文件时出错: {e}") 681 | if config.get('reply_enabled'): 682 | delay = (random.uniform(config.get('reply_delay_min', 0), config.get('reply_delay_max', 0)) 683 | if config.get('reply_delay_max', 0) > config.get('reply_delay_min', 0) 684 | else config.get('reply_delay_min', 0)) 685 | if delay > 0: 686 | await asyncio.sleep(delay) 687 | reply_texts = config.get('reply_texts', []) 688 | if reply_texts: 689 | reply_msg = random.choice(reply_texts) 690 | await event.message.reply(reply_msg) 691 | if config.get('max_executions'): 692 | config['execution_count'] = config.get('execution_count', 0) + 1 693 | if config['execution_count'] >= config['max_executions']: 694 | del all_messages_config[chat_id] 695 | logger.info(f"全量监控配置(对话ID={chat_id})已达到最大执行次数,自动删除") 696 | 697 | if isinstance(event.chat, Chat): 698 | local_fwd = get_fwd_channel_id(event.message.fwd_from) 699 | if local_fwd is None and isinstance(sender, Channel): 700 | local_fwd = sender.id 701 | if local_fwd and local_fwd in channel_in_group_config: 702 | logger.info(f"匹配到频道发言: 对话ID={chat_id}, 频道ID={local_fwd}") 703 | if chat_id in all_messages_config: 704 | config = all_messages_config[chat_id] 705 | if config.get('email_notify'): 706 | send_email(f"检测到指定频道 {local_fwd} 在群聊中的消息: {message_text}") 707 | if config.get('log_file'): 708 | try: 709 | with open(config.get('log_file'), 'a', encoding='utf-8') as f: 710 | f.write( 711 | f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 频道转发 - 对话 {chat_id} 消息: {message_text}\n") 712 | except Exception as e: 713 | logger.error(f"写入频道转发文件时出错: {e}") 714 | else: 715 | send_email(f"检测到指定频道 {local_fwd} 在群聊中的消息: {message_text}") 716 | 717 | handled = False 718 | for keyword, config in keyword_config.items(): 719 | if chat_id not in config.get('chats', []): 720 | continue 721 | if not is_not_blocked(config) or not should_listen(config, sender, event): 722 | continue 723 | mtype = config.get('match_type', 'partial') 724 | normal_match = False 725 | if mtype == 'exact' and message_text_lower == keyword: 726 | normal_match = True 727 | elif mtype == 'partial' and keyword in message_text_lower: 728 | normal_match = True 729 | elif mtype == 'regex': 730 | pattern = re.compile(rf'{keyword}') 731 | if pattern.search(message_text): 732 | normal_match = True 733 | if normal_match: 734 | logger.info( 735 | f"匹配到关键词 '{keyword}': 对话ID={chat_id}, 发送者: id={sender_id}, 名称={name}, 用户名={username}") 736 | if config.get('email_notify'): 737 | send_email(f"检测到关键词 '{keyword}' 的消息: {message_text}") 738 | if config.get('auto_forward'): 739 | await auto_forward_message(event, keyword, account_id) 740 | if config.get('log_file'): 741 | try: 742 | with open(config.get('log_file'), 'a', encoding='utf-8') as f: 743 | if mtype == 'exact': 744 | f.write( 745 | f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 对话 {chat_id} 完全匹配 '{keyword}': {message_text}\n") 746 | elif mtype == 'partial': 747 | f.write( 748 | f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 对话 {chat_id} 关键词 '{keyword}': {message_text}\n") 749 | else: 750 | f.write( 751 | f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 对话 {chat_id} 正则匹配 '{keyword}': {message_text}\n") 752 | except Exception as e: 753 | logger.error(f"写入关键词匹配文件时出错: {e}") 754 | if config.get('reply_enabled'): 755 | delay = (random.uniform(config.get('reply_delay_min', 0), config.get('reply_delay_max', 0)) 756 | if config.get('reply_delay_max', 0) > config.get('reply_delay_min', 0) 757 | else config.get('reply_delay_min', 0)) 758 | if delay > 0: 759 | await asyncio.sleep(delay) 760 | reply_texts = config.get('reply_texts', []) 761 | if reply_texts: 762 | reply_msg = random.choice(reply_texts) 763 | await event.message.reply(reply_msg) 764 | if config.get('max_executions'): 765 | config['execution_count'] = config.get('execution_count', 0) + 1 766 | if config['execution_count'] >= config['max_executions']: 767 | del keyword_config[keyword] 768 | logger.info(f"关键词配置 '{keyword}' 达到最大执行次数,已删除") 769 | handled = True 770 | break 771 | 772 | if not handled: 773 | if event.message.media and isinstance(event.message.media, MessageMediaDocument): 774 | file_attr = event.message.media.document.attributes 775 | file_name = None 776 | for attr in file_attr: 777 | if hasattr(attr, 'file_name'): 778 | file_name = attr.file_name 779 | break 780 | if file_name: 781 | file_extension = os.path.splitext(file_name)[1].lower() 782 | config = file_extension_config.get(file_extension) 783 | if config and chat_id in config['chats']: 784 | if is_not_blocked(config) and should_listen(config, sender, event): 785 | user_set = set(config.get('users', [])) 786 | user_option = config.get('user_option') 787 | if not match_user(sender, user_set, user_option): 788 | return 789 | logger.info( 790 | f"匹配到文件后缀监控: 对话ID={chat_id}, 文件后缀={file_extension}, 文件名={file_name}") 791 | if config.get('email_notify'): 792 | send_email(f"检测到文件后缀名 '{file_extension}' 的消息:{file_name}") 793 | if config.get('auto_forward'): 794 | await auto_forward_file_message(event, file_extension, account_id) 795 | 796 | if config.get('save_folder'): 797 | file_size = event.message.media.document.size 798 | file_size_mb = file_size / (1024 * 1024) 799 | min_size = config.get('min_size') 800 | max_size = config.get('max_size') 801 | size_ok = True 802 | if min_size is not None and file_size_mb < min_size: 803 | size_ok = False 804 | if max_size is not None and file_size_mb > max_size: 805 | size_ok = False 806 | if size_ok: 807 | save_folder = config.get('save_folder') 808 | os.makedirs(save_folder, exist_ok=True) 809 | file_path = await event.message.download_media(file=save_folder) 810 | logger.info(f"已保存文件 '{file_name}' 到: {file_path}") 811 | else: 812 | logger.info(f"文件大小 {file_size_mb:.2f} MB 不在设定范围内,未保存文件") 813 | 814 | if config.get('max_executions'): 815 | config['execution_count'] = config.get('execution_count', 0) + 1 816 | if config['execution_count'] >= config['max_executions']: 817 | del file_extension_config[file_extension] 818 | logger.info(f"文件后缀配置 '{file_extension}' 达到最大执行次数,已删除") 819 | 820 | if event.message.buttons: 821 | for b_keyword, b_config in button_keyword_config.items(): 822 | if chat_id in b_config.get('chats', []): 823 | if match_user(sender, b_config.get('users', set()), b_config.get('user_option')): 824 | if b_config.get('mode') == "ai": 825 | buttons_text_list = [button.text.strip() for row in event.message.buttons for button in row] 826 | ai_prompt = b_config.get('ai_prompt', 827 | "请根据下面的消息内容和按钮选项,选择最合适的按钮,返回该按钮包含的关键字。") 828 | full_prompt = f"{ai_prompt}\n消息内容: {message_text}\n按钮选项:\n" + "\n".join( 829 | buttons_text_list) 830 | ai_messages = [{"role": "user", "content": full_prompt}] 831 | ai_answer = await get_ai_answer(ai_messages) 832 | if ai_answer: 833 | clicked = await click_button_by_ai_answer(event.message, ai_answer, exact=False) 834 | if clicked: 835 | if b_config.get('max_executions') is not None: 836 | b_config['execution_count'] = b_config.get('execution_count', 0) + 1 837 | if b_config['execution_count'] >= b_config['max_executions']: 838 | del button_keyword_config[b_keyword] 839 | logger.info(f"按钮配置 '{b_keyword}' 达到最大执行次数,已删除") 840 | return 841 | else: 842 | manual_keyword = b_config.get('button_keyword', b_keyword) 843 | for row_i, row in enumerate(event.message.buttons): 844 | for col_i, button in enumerate(row): 845 | if manual_keyword in button.text.lower(): 846 | await event.message.click(row_i, col_i) 847 | logger.info( 848 | f"已点击对话 {chat_id} 中包含按钮关键词 '{manual_keyword}' 的按钮: {button.text}") 849 | # 更新执行次数 850 | if b_config.get('max_executions') is not None: 851 | b_config['execution_count'] = b_config.get('execution_count', 0) + 1 852 | if b_config['execution_count'] >= b_config['max_executions']: 853 | del button_keyword_config[b_keyword] 854 | logger.info(f"按钮配置 '{b_keyword}' 达到最大执行次数,已删除") 855 | return 856 | 857 | if chat_id in image_button_monitor and event.message.buttons: 858 | image_path = None 859 | if event.message.photo or (event.message.document and 'image' in event.message.document.mime_type): 860 | image_path = await event.message.download_media() 861 | if image_path and event.message.buttons: 862 | base, ext = os.path.splitext(image_path) 863 | if ext.lower() != '.jpg': 864 | new_image_path = base + '.jpg' 865 | shutil.move(image_path, new_image_path) 866 | image_path = new_image_path 867 | options = [button.text.strip() for row in event.message.buttons for button in row] 868 | prompt_options = "\n".join(options) 869 | ai_prompt = f"请根据图中的内容从下列选项中选出符合图片的选项,你的回答只需要包含选项的内容,不用包含其他内容:\n{prompt_options}" 870 | 871 | def encode_image(image_path): 872 | with open(image_path, "rb") as image_file: 873 | return base64.b64encode(image_file.read()).decode("utf-8") 874 | 875 | base64_image = encode_image(image_path) 876 | ai_messages = [ 877 | { 878 | "role": "user", 879 | "content": [ 880 | {"type": "text", "text": ai_prompt}, 881 | {"type": "image_url", "image_url": {"url": f"data:image/jpg;base64,{base64_image}"}} 882 | ] 883 | } 884 | ] 885 | ai_answer = await get_ai_answer(ai_messages) 886 | clicked = False 887 | if ai_answer: 888 | clicked = await click_button_by_ai_answer(event.message, ai_answer, exact=True) 889 | if clicked: 890 | for idx, item in enumerate(account["config"]["image_button_monitor"]): 891 | if isinstance(item, dict) and item.get("chat_id") == chat_id: 892 | item["execution_count"] = item.get("execution_count", 0) + 1 893 | if item.get("max_executions") is not None and item["execution_count"] >= item[ 894 | "max_executions"]: 895 | account["config"]["image_button_monitor"].pop(idx) 896 | logger.info(f"图片+按钮配置 for chat {chat_id} 达到最大执行次数,已删除") 897 | break 898 | try: 899 | if os.path.exists(image_path): 900 | os.remove(image_path) 901 | logger.info(f"已删除图片文件:{image_path}") 902 | except Exception as e: 903 | logger.error(f"删除图片文件时发生错误: {e}") 904 | 905 | except Exception as e: 906 | logger.error(f"处理消息时出错:{repr(e)}") 907 | 908 | 909 | async def auto_forward_message(event, keyword, account_id): 910 | try: 911 | config = ACCOUNTS[account_id]["config"]["keyword_config"].get(keyword, {}) 912 | target_ids = config.get('forward_targets', []) 913 | chat_id = event.chat_id 914 | if account_id and account_id in ACCOUNTS: 915 | client_instance = ACCOUNTS[account_id]["client"] 916 | else: 917 | logger.error("auto_forward_message 未指定有效账号") 918 | return 919 | for target_id in target_ids: 920 | if target_id == chat_id: 921 | logger.info(f"转发目标ID({target_id})与监控群组({chat_id})相同,跳过转发以避免循环。") 922 | continue 923 | await client_instance.forward_messages(target_id, event.message) 924 | logger.info(f"已将关键词 '{keyword}' 消息转发到ID: {target_id}") 925 | except Exception as e: 926 | error_message = repr(e) 927 | logger.error(f"自动转发消息时发生错误:{error_message}") 928 | 929 | 930 | async def auto_forward_file_message(event, file_extension, account_id): 931 | try: 932 | config = ACCOUNTS[account_id]["config"]["file_extension_config"].get(file_extension, {}) 933 | target_ids = config.get('forward_targets', []) 934 | chat_id = event.chat_id 935 | if account_id and account_id in ACCOUNTS: 936 | client_instance = ACCOUNTS[account_id]["client"] 937 | else: 938 | logger.error("auto_forward_file_message 未指定有效账号") 939 | return 940 | for target_id in target_ids: 941 | if target_id == chat_id: 942 | logger.info(f"转发目标ID({target_id})与监控群组({chat_id})相同,跳过转发以避免循环。") 943 | continue 944 | await client_instance.forward_messages(target_id, event.message) 945 | logger.info(f"已将包含文件 {file_extension} 的消息转发到ID: {target_id}") 946 | except Exception as e: 947 | error_message = repr(e) 948 | logger.error(f"自动转发文件消息时发生错误:{error_message}") 949 | 950 | 951 | def schedule_message(target_id, message, cron_expression, random_offset=0, delete_after_sending=False, account_id=None): 952 | job = scheduler.add_job( 953 | send_scheduled_message_wrapper, 954 | CronTrigger.from_crontab(cron_expression, timezone=pytz.timezone('Asia/Shanghai')), 955 | args=[None, target_id, message, random_offset, delete_after_sending, account_id] 956 | ) 957 | job_id = job.id 958 | job.modify(args=[job_id, target_id, message, random_offset, delete_after_sending, account_id]) 959 | logger.info(f"已添加定时消息,Cron表达式: {cron_expression},目标ID: {target_id},账号: {account_id}") 960 | return job 961 | 962 | 963 | async def send_scheduled_message(target_id, message, random_offset=0, delete_after_sending=False, account_id=None, 964 | job_id=None): 965 | try: 966 | if random_offset > 0: 967 | delay = random.uniform(0, random_offset) 968 | logger.info(f"等待 {delay:.2f} 秒后发送定时消息") 969 | await asyncio.sleep(delay) 970 | if account_id and account_id in ACCOUNTS: 971 | client_instance = ACCOUNTS[account_id]["client"] 972 | else: 973 | logger.error("send_scheduled_message 未指定有效账号") 974 | return 975 | 976 | await client_instance.get_dialogs() 977 | 978 | try: 979 | entity = await client_instance.get_entity(target_id) 980 | except Exception as e: 981 | logger.error(f"获取目标实体出错: {e}") 982 | return 983 | 984 | sent_message = await client_instance.send_message(entity, message) 985 | logger.info(f"已发送定时消息到ID: {target_id},账号: {account_id}") 986 | if delete_after_sending: 987 | await asyncio.sleep(5) 988 | await client_instance.delete_messages(entity, sent_message.id) 989 | logger.info(f"已删除发送的定时消息,消息ID: {sent_message.id}") 990 | 991 | if job_id: 992 | for acc in ACCOUNTS.values(): 993 | sched_list = acc["config"].get("scheduled_messages", []) 994 | for i in range(len(sched_list)): 995 | if sched_list[i].get("job_id") == job_id: 996 | sched_list[i]["execution_count"] = sched_list[i].get("execution_count", 0) + 1 997 | logger.info(f"更新定时消息 {job_id} 执行次数为 {sched_list[i]['execution_count']}") 998 | if (sched_list[i].get("max_executions") is not None and 999 | sched_list[i]["execution_count"] >= sched_list[i]["max_executions"]): 1000 | scheduler.remove_job(job_id) 1001 | logger.info(f"定时消息配置 '{job_id}' 达到最大执行次数,已删除") 1002 | del sched_list[i] 1003 | break 1004 | except Exception as e: 1005 | error_message = repr(e) 1006 | logger.error(f"发送定时消息时发生错误:{error_message}") 1007 | 1008 | 1009 | async def send_scheduled_message_wrapper(job_id, target_id, message, random_offset, delete_after_sending, account_id): 1010 | await send_scheduled_message(target_id, message, random_offset, delete_after_sending, account_id, job_id) 1011 | 1012 | 1013 | async def handle_commands(): 1014 | global monitor_active, ACCOUNTS, current_account 1015 | short_prompt = "请输入命令 (输入 help 查看详细命令): " 1016 | full_commands = """ 1017 | === 可用命令 === 1018 | addaccount - 添加新账号 1019 | removeaccount - 移除账号 1020 | listaccount - 列出所有账号 1021 | switchaccount - 切换当前工作账号 1022 | exportconfig - 导出当前账号配置 1023 | importconfig - 导入当前账号配置 1024 | blockbot - 屏蔽指定 TG Bot(输入 bot id) 1025 | unblockbot - 取消屏蔽 TG Bot 1026 | list - 列出当前账号的所有频道和群组对话 1027 | addkeyword - 添加关键词监控配置 1028 | modifykeyword - 修改关键词监控配置 1029 | removekeyword - 移除关键词监控配置 1030 | showkeywords - 显示当前账号所有关键词配置 1031 | addext - 添加文件后缀监控配置 1032 | modifyext - 修改文件后缀监控配置 1033 | removeext - 移除文件后缀监控配置 1034 | showext - 显示当前账号所有文件后缀监控配置 1035 | addall - 添加全量监控配置(频道或群组) 1036 | modifyall - 修改全量监控配置 1037 | removeall - 移除全量监控配置 1038 | showall - 显示当前账号所有全量监控配置 1039 | addbutton - 添加按钮关键词监控配置 1040 | modifybutton - 修改按钮关键词监控配置 1041 | removebutton - 移除按钮关键词监控配置 1042 | showbuttons - 显示当前账号所有按钮关键词配置 1043 | listchats - 列出当前账号与 Bot 的对话 chat_id 1044 | addlistener - 添加图片+按钮监听的对话ID 1045 | removelistener - 移除图片+按钮监听的对话ID 1046 | showlistener - 显示当前账号所有图片+按钮监听对话ID 1047 | schedule - 添加定时消息配置 1048 | modifyschedule - 修改定时消息配置 1049 | removeschedule - 删除定时消息配置 1050 | showschedule - 显示当前账号所有定时消息配置 1051 | export - 导出所有配置 1052 | import - 导入所有配置 1053 | exportlinks - 导出频道群组链接 1054 | start - 开始监控 1055 | stop - 停止监控 1056 | exit - 退出程序 1057 | """ 1058 | while True: 1059 | command = (await ainput(short_prompt)).strip().lower() 1060 | if command == "help": 1061 | print(full_commands) 1062 | continue 1063 | try: 1064 | if command == 'addaccount': 1065 | await add_account() 1066 | elif command == 'removeaccount': 1067 | await remove_account() 1068 | elif command == 'listaccount': 1069 | await list_accounts() 1070 | elif command == 'switchaccount': 1071 | if not ACCOUNTS: 1072 | print("当前没有登录的账号") 1073 | continue 1074 | accounts_list = list(ACCOUNTS.keys()) 1075 | print("=== 已登录账号列表 ===") 1076 | for idx, account_id in enumerate(accounts_list, start=1): 1077 | info = ACCOUNTS[account_id] 1078 | print(f"{idx}. 电话: {info['phone']}, 用户ID: {info['own_user_id']}") 1079 | selection = (await ainput("请输入要切换到的账号序号: ")).strip() 1080 | if selection.isdigit(): 1081 | index = int(selection) - 1 1082 | if 0 <= index < len(accounts_list): 1083 | current_account = accounts_list[index] 1084 | print(f"当前工作账号已切换为:{current_account}") 1085 | else: 1086 | print("无效的序号") 1087 | else: 1088 | print("请输入数字序号") 1089 | elif command == 'exportconfig': 1090 | await export_all_configs() 1091 | elif command == 'importconfig': 1092 | await import_all_configs() 1093 | elif command == 'blockbot': 1094 | bot_id_str = (await ainput("请输入要屏蔽的 TG Bot 的 bot id: ")).strip() 1095 | if bot_id_str.isdigit(): 1096 | BLOCKED_BOTS.add(int(bot_id_str)) 1097 | print(f"已屏蔽 TG Bot: {bot_id_str}") 1098 | else: 1099 | print("无效的 bot id") 1100 | elif command == 'unblockbot': 1101 | bot_id_str = (await ainput("请输入要取消屏蔽的 TG Bot 的 bot id: ")).strip() 1102 | if bot_id_str.isdigit(): 1103 | bot_id = int(bot_id_str) 1104 | if bot_id in BLOCKED_BOTS: 1105 | BLOCKED_BOTS.remove(bot_id) 1106 | print(f"已取消屏蔽 TG Bot: {bot_id_str}") 1107 | else: 1108 | print("该 bot id 未被屏蔽") 1109 | else: 1110 | print("无效的 bot id") 1111 | elif command == 'list': 1112 | if current_account is None: 1113 | print("当前没有选定工作账号") 1114 | continue 1115 | print(f"\n=== 当前账号 {current_account} 的所有对话 ===") 1116 | async for dialog in ACCOUNTS[current_account]["client"].iter_dialogs(): 1117 | if isinstance(dialog.entity, (Channel, Chat)): 1118 | print( 1119 | f"ID: {dialog.id}, 名称: {dialog.name}, 类型: {'频道' if isinstance(dialog.entity, Channel) else '群组'}") 1120 | elif command == 'addkeyword': 1121 | if current_account is None: 1122 | print("请先切换或添加账号") 1123 | continue 1124 | cfg = ACCOUNTS[current_account]["config"]["keyword_config"] 1125 | 1126 | print("\n请选择关键词匹配类型:") 1127 | print("1. 完全匹配") 1128 | print("2. 关键词匹配") 1129 | print("3. 正则表达式匹配") 1130 | match_option = (await ainput("请输入匹配类型编号 (1/2/3): ")).strip() 1131 | if match_option == '1': 1132 | match_type = 'exact' 1133 | elif match_option == '2': 1134 | match_type = 'partial' 1135 | elif match_option == '3': 1136 | match_type = 'regex' 1137 | else: 1138 | print("无效的匹配类型,默认使用关键词匹配") 1139 | match_type = 'partial' 1140 | 1141 | if match_type != 'regex': 1142 | keywords_input = (await ainput("请输入关键词,多个关键词用逗号分隔: ")).strip() 1143 | keywords = [kw.strip().lower() for kw in keywords_input.split(',')] 1144 | else: 1145 | keywords_input = (await ainput("请输入正则表达式模式(不使用逗号分隔): ")).strip() 1146 | keywords = [keywords_input.strip()] 1147 | 1148 | chat_ids_input = (await ainput("请输入要监听的对话ID(多个逗号分隔): ")).strip() 1149 | chat_ids = [int(x.strip()) for x in chat_ids_input.split(',')] 1150 | 1151 | print("\n请选择要监控用户其类型: 1. 用户ID(频道id与Bot id也可行) 2. 用户名 3. 昵称") 1152 | user_option = (await ainput("请输入选项编号(直接回车表示全部): ")).strip() 1153 | if user_option in ['1', '2', '3']: 1154 | users_input = (await ainput("请输入对应用户标识(回车则监控全部用户,多个逗号分隔): ")).strip() 1155 | if users_input: 1156 | if user_option == '1': 1157 | user_set = [int(u.strip()) for u in users_input.split(',') if u.strip().isdigit()] 1158 | else: 1159 | user_set = [u.strip() for u in users_input.split(',')] 1160 | else: 1161 | user_set = [] 1162 | else: 1163 | user_option = None 1164 | user_set = [] 1165 | 1166 | auto_forward = (await ainput("是否启用自动转发?(yes/no): ")).strip().lower() == 'yes' 1167 | email_notify = (await ainput("是否启用邮件通知?(yes/no): ")).strip().lower() == 'yes' 1168 | log_to_file = (await ainput("是否记录匹配消息到文件?(yes/no): ")).strip().lower() == 'yes' 1169 | if log_to_file: 1170 | log_file = (await ainput("请输入文件名称: ")).strip() 1171 | else: 1172 | log_file = None 1173 | 1174 | if auto_forward: 1175 | target_ids_input = (await ainput("请输入自动转发目标对话ID(多个逗号分隔): ")).strip() 1176 | target_ids = [int(x.strip()) for x in target_ids_input.split(',')] 1177 | else: 1178 | target_ids = [] 1179 | 1180 | filter_choice = (await ainput("是否设置屏蔽过滤?(yes/no): ")).strip().lower() == 'yes' 1181 | if filter_choice: 1182 | blocked_users_input = (await ainput("请输入屏蔽用户(用户ID,多个逗号分隔): ")).strip() 1183 | blocked_users = [x.strip() for x in blocked_users_input.split(',')] if blocked_users_input else [] 1184 | else: 1185 | blocked_users = [] 1186 | 1187 | execution_limit_input = (await ainput("请输入执行次数限制(正整数),直接回车表示不设置: ")).strip() 1188 | if execution_limit_input.isdigit(): 1189 | max_executions = int(execution_limit_input) 1190 | else: 1191 | max_executions = None 1192 | 1193 | reply_choice = (await ainput("是否启用回复功能?(yes/no): ")).strip().lower() == 'yes' 1194 | if reply_choice: 1195 | reply_enabled = True 1196 | reply_texts_input = (await ainput("请输入回复词组,多个词组用逗号分隔: ")).strip() 1197 | reply_texts = [s.strip() for s in reply_texts_input.split(',')] if reply_texts_input else [] 1198 | reply_delay_input = (await ainput("请输入回复延时范围(格式: min,max,单位秒): ")).strip() 1199 | try: 1200 | reply_delay_min, reply_delay_max = map(float, reply_delay_input.split(',')) 1201 | except Exception as e: 1202 | print("回复延时范围输入格式有误,默认设为0") 1203 | reply_delay_min = 0 1204 | reply_delay_max = 0 1205 | else: 1206 | reply_enabled = False 1207 | reply_texts = [] 1208 | reply_delay_min = 0 1209 | reply_delay_max = 0 1210 | 1211 | for keyword in keywords: 1212 | cfg[keyword] = { 1213 | 'chats': chat_ids, 1214 | 'auto_forward': auto_forward, 1215 | 'email_notify': email_notify, 1216 | 'match_type': match_type, 1217 | 'users': user_set, 1218 | 'user_option': user_option, 1219 | 'forward_targets': target_ids, 1220 | 'log_file': log_file, 1221 | 'reply_enabled': reply_enabled, 1222 | 'reply_texts': reply_texts, 1223 | 'reply_delay_min': reply_delay_min, 1224 | 'reply_delay_max': reply_delay_max, 1225 | 'max_executions': max_executions, 1226 | 'execution_count': 0, 1227 | 'blocked_users': blocked_users, 1228 | } 1229 | if match_type == 'regex': 1230 | regex_send = (await ainput( 1231 | "是否发送正则匹配结果到指定对话?(yes/no): ")).strip().lower() == 'yes' 1232 | if regex_send: 1233 | cfg[keyword]['regex_send_target_id'] = int((await ainput("请输入目标对话ID: ")).strip()) 1234 | random_offset_input = (await ainput("请输入随机延时(秒): ")).strip() 1235 | cfg[keyword]['regex_send_random_offset'] = int( 1236 | random_offset_input) if random_offset_input else 0 1237 | cfg[keyword]['regex_send_delete'] = (await ainput( 1238 | "发送后是否删除消息?(yes/no): ")).strip().lower() == 'yes' 1239 | print( 1240 | f"正则匹配结果将发送到ID: {cfg[keyword]['regex_send_target_id']}, 延时: {cfg[keyword]['regex_send_random_offset']}秒, 删除后: {'是' if cfg[keyword]['regex_send_delete'] else '否'}") 1241 | print(f"已添加关键词 '{keyword}' 的配置: {cfg[keyword]}") 1242 | 1243 | 1244 | elif command == 'modifykeyword': 1245 | if current_account is None: 1246 | print("请先切换或添加账号") 1247 | continue 1248 | cfg = ACCOUNTS[current_account]["config"]["keyword_config"] 1249 | keyword_input = (await ainput("请输入要修改的关键词: ")).strip() 1250 | if keyword_input in cfg: 1251 | config = cfg[keyword_input] 1252 | print(f"当前配置:{config}") 1253 | print("请输入要修改的项(逗号分隔):") 1254 | print("1. 关键词 2. 监听对话ID 3. 自动转发 4. 邮件通知") 1255 | print("5. 匹配类型 6. 用户过滤 7. 文件记录 8. 回复功能") 1256 | print("9. 执行次数限制 10. 屏蔽过滤 ") 1257 | options = (await ainput("请输入修改项: ")).strip().split(',') 1258 | options = [x.strip() for x in options] 1259 | if '1' in options: 1260 | new_keyword = (await ainput("请输入新的关键词: ")).strip() 1261 | cfg[new_keyword] = cfg.pop(keyword_input) 1262 | keyword_input = new_keyword 1263 | print(f"关键词修改为: {new_keyword}") 1264 | if '2' in options: 1265 | chat_ids_input = (await ainput("请输入新的监听对话ID(多个逗号分隔): ")).strip() 1266 | cfg[keyword_input]['chats'] = [int(x.strip()) for x in chat_ids_input.split(',')] 1267 | print("监听对话ID已更新") 1268 | if '3' in options: 1269 | auto_forward = (await ainput("是否启用自动转发?(yes/no): ")).strip().lower() == 'yes' 1270 | cfg[keyword_input]['auto_forward'] = auto_forward 1271 | if auto_forward: 1272 | target_ids_input = (await ainput("请输入自动转发目标对话ID(多个逗号分隔): ")).strip() 1273 | cfg[keyword_input]['forward_targets'] = [int(x.strip()) for x in 1274 | target_ids_input.split(',')] 1275 | else: 1276 | cfg[keyword_input]['forward_targets'] = [] 1277 | if '4' in options: 1278 | email_notify = (await ainput("是否启用邮件通知?(yes/no): ")).strip().lower() == 'yes' 1279 | cfg[keyword_input]['email_notify'] = email_notify 1280 | if '5' in options: 1281 | print("请选择匹配类型: 1. 完全匹配 2. 关键词匹配 3. 正则匹配") 1282 | match_option = (await ainput("请输入匹配类型编号: ")).strip() 1283 | if match_option == '1': 1284 | cfg[keyword_input]['match_type'] = 'exact' 1285 | elif match_option == '2': 1286 | cfg[keyword_input]['match_type'] = 'partial' 1287 | elif match_option == '3': 1288 | cfg[keyword_input]['match_type'] = 'regex' 1289 | else: 1290 | print("输入无效,匹配类型保持不变") 1291 | if '6' in options: 1292 | print("请选择用户类型: 1. 用户ID 2. 用户名 3. 昵称") 1293 | user_option = (await ainput("请输入用户类型编号: ")).strip() 1294 | users_input = (await ainput("请输入对应用户标识(多个逗号分隔): ")).strip() 1295 | if users_input: 1296 | if user_option == '1': 1297 | cfg[keyword_input]['users'] = [int(x.strip()) for x in users_input.split(',') if 1298 | x.strip().isdigit()] 1299 | else: 1300 | cfg[keyword_input]['users'] = [x.strip() for x in users_input.split(',')] 1301 | else: 1302 | cfg[keyword_input]['users'] = [] 1303 | cfg[keyword_input]['user_option'] = user_option 1304 | if '7' in options: 1305 | log_to_file = (await ainput("是否记录匹配消息到文件?(yes/no): ")).strip().lower() == 'yes' 1306 | if log_to_file: 1307 | log_file = (await ainput("请输入文件名称: ")).strip() 1308 | cfg[keyword_input]['log_file'] = log_file 1309 | else: 1310 | cfg[keyword_input].pop('log_file', None) 1311 | if '8' in options: 1312 | reply_enabled = (await ainput("是否启用回复?(yes/no): ")).strip().lower() == 'yes' 1313 | cfg[keyword_input]['reply_enabled'] = reply_enabled 1314 | if reply_enabled: 1315 | reply_texts_input = (await ainput("请输入回复词组,多个词组用逗号分隔: ")).strip() 1316 | cfg[keyword_input]['reply_texts'] = [s.strip() for s in reply_texts_input.split(',')] 1317 | reply_delay_input = (await ainput("请输入回复延时范围(格式: min,max,单位秒): ")).strip() 1318 | try: 1319 | reply_delay_min, reply_delay_max = map(float, reply_delay_input.split(',')) 1320 | except Exception as e: 1321 | print("回复延时范围输入格式有误,默认设为0") 1322 | reply_delay_min = 0 1323 | reply_delay_max = 0 1324 | cfg[keyword_input]['reply_delay_min'] = reply_delay_min 1325 | cfg[keyword_input]['reply_delay_max'] = reply_delay_max 1326 | else: 1327 | cfg[keyword_input]['reply_texts'] = [] 1328 | cfg[keyword_input]['reply_delay_min'] = 0 1329 | cfg[keyword_input]['reply_delay_max'] = 0 1330 | if '9' in options: 1331 | execution_limit_input = ( 1332 | await ainput("请输入新的执行次数限制(正整数),直接回车表示不设置: ")).strip() 1333 | if execution_limit_input.isdigit(): 1334 | cfg[keyword_input]['max_executions'] = int(execution_limit_input) 1335 | else: 1336 | cfg[keyword_input]['max_executions'] = None 1337 | cfg[keyword_input]['execution_count'] = 0 1338 | if '10' in options: 1339 | filter_choice = (await ainput("是否设置屏蔽过滤?(yes/no): ")).strip().lower() == 'yes' 1340 | if filter_choice: 1341 | blocked_users_input = (await ainput("请输入屏蔽用户(用户ID,多个逗号分隔): ")).strip() 1342 | blocked_users = [x.strip() for x in 1343 | blocked_users_input.split(',')] if blocked_users_input else [] 1344 | else: 1345 | blocked_users = [] 1346 | cfg[keyword_input]['blocked_users'] = blocked_users 1347 | print(f"关键词 '{keyword_input}' 的新配置: {cfg[keyword_input]}") 1348 | else: 1349 | print("未找到该关键词配置") 1350 | 1351 | elif command == 'removekeyword': 1352 | if current_account is None: 1353 | print("请先切换或添加账号") 1354 | continue 1355 | cfg = ACCOUNTS[current_account]["config"]["keyword_config"] 1356 | keyword_input = (await ainput("请输入要移除的关键词: ")).strip() 1357 | if keyword_input in cfg: 1358 | del cfg[keyword_input] 1359 | print(f"已移除关键词 '{keyword_input}'") 1360 | else: 1361 | print("未找到该关键词配置") 1362 | 1363 | elif command == 'showkeywords': 1364 | if current_account is None: 1365 | print("请先切换或添加账号") 1366 | continue 1367 | cfg = ACCOUNTS[current_account]["config"]["keyword_config"] 1368 | print("\n=== 当前关键词配置 ===") 1369 | for k, v in cfg.items(): 1370 | print(f"{k} : {v}") 1371 | 1372 | elif command == 'addext': 1373 | if current_account is None: 1374 | print("请先切换或添加账号") 1375 | continue 1376 | cfg = ACCOUNTS[current_account]["config"]["file_extension_config"] 1377 | extensions_input = (await ainput("请输入文件后缀(多个逗号分隔): ")).strip().lower() 1378 | extensions = [ext.strip() if ext.startswith('.') else '.' + ext.strip() for ext in 1379 | extensions_input.split(',')] 1380 | chat_ids_input = (await ainput("请输入监听对话ID(多个逗号分隔): ")).strip() 1381 | chat_ids = [int(x.strip()) for x in chat_ids_input.split(',')] 1382 | print("请选择要监控用户其类型: 1. 用户ID(频道id与Bot id也可行) 2. 用户名 3. 昵称") 1383 | user_option = (await ainput("请输入选项编号(可留空表示全部): ")).strip() 1384 | if user_option in ['1', '2', '3']: 1385 | users_input = (await ainput("请输入对应用户标识(多个逗号分隔): ")).strip() 1386 | if users_input: 1387 | if user_option == '1': 1388 | user_set = [int(x.strip()) for x in users_input.split(',') if x.strip().isdigit()] 1389 | else: 1390 | user_set = [x.strip() for x in users_input.split(',')] 1391 | else: 1392 | user_set = [] 1393 | else: 1394 | user_option = None 1395 | user_set = [] 1396 | auto_forward = (await ainput("启用自动转发?(yes/no): ")).strip().lower() == 'yes' 1397 | email_notify = (await ainput("启用邮件通知?(yes/no): ")).strip().lower() == 'yes' 1398 | if auto_forward: 1399 | target_ids_input = (await ainput("请输入转发目标对话ID(多个逗号分隔): ")).strip() 1400 | target_ids = [int(x.strip()) for x in target_ids_input.split(',')] 1401 | else: 1402 | target_ids = [] 1403 | filter_choice = (await ainput("是否设置屏蔽过滤?(yes/no): ")).strip().lower() == 'yes' 1404 | if filter_choice: 1405 | blocked_users_input = (await ainput("请输入屏蔽用户、频道或Bot(ID,多个逗号分隔): ")).strip() 1406 | blocked_users = [x.strip() for x in blocked_users_input.split(',')] if blocked_users_input else [] 1407 | else: 1408 | blocked_users = [] 1409 | execution_limit_input = (await ainput("请输入执行次数限制(正整数),直接回车表示不设置: ")).strip() 1410 | if execution_limit_input.isdigit(): 1411 | max_executions = int(execution_limit_input) 1412 | else: 1413 | max_executions = None 1414 | 1415 | save_choice = (await ainput("是否保存匹配到的文件到本地?(yes/no): ")).strip().lower() == 'yes' 1416 | if save_choice: 1417 | save_folder = (await ainput("请输入保存文件的本地文件夹路径: ")).strip() 1418 | size_limit_choice = (await ainput("是否设置文件大小范围限制?(yes/no): ")).strip().lower() == 'yes' 1419 | if size_limit_choice: 1420 | min_size_input = (await ainput("请输入文件最小大小(MB): ")).strip() 1421 | max_size_input = (await ainput("请输入文件最大大小(MB): ")).strip() 1422 | try: 1423 | min_size = float(min_size_input) if min_size_input else None 1424 | except: 1425 | min_size = None 1426 | try: 1427 | max_size = float(max_size_input) if max_size_input else None 1428 | except: 1429 | max_size = None 1430 | else: 1431 | min_size = None 1432 | max_size = None 1433 | else: 1434 | save_folder = None 1435 | min_size = None 1436 | max_size = None 1437 | 1438 | for ext in extensions: 1439 | cfg[ext] = { 1440 | 'chats': chat_ids, 1441 | 'auto_forward': auto_forward, 1442 | 'email_notify': email_notify, 1443 | 'users': user_set, 1444 | 'user_option': user_option, 1445 | 'forward_targets': target_ids, 1446 | 'max_executions': max_executions, 1447 | 'execution_count': 0, 1448 | 'blocked_users': blocked_users, 1449 | 'save_folder': save_folder, 1450 | 'min_size': min_size, 1451 | 'max_size': max_size, 1452 | } 1453 | print(f"已添加文件后缀 '{ext}' 的配置: {cfg[ext]}") 1454 | 1455 | 1456 | elif command == 'modifyext': 1457 | if current_account is None: 1458 | print("请先切换或添加账号") 1459 | continue 1460 | cfg = ACCOUNTS[current_account]["config"]["file_extension_config"] 1461 | ext = (await ainput("请输入要修改的文件后缀(如 .pdf): ")).strip().lower() 1462 | if not ext.startswith('.'): 1463 | ext = '.' + ext 1464 | if ext in cfg: 1465 | config = cfg[ext] 1466 | print(f"当前配置:{config}") 1467 | print("请输入要修改的项(逗号分隔):") 1468 | print("1. 文件后缀") 1469 | print("2. 监听对话ID") 1470 | print("3. 自动转发") 1471 | print("4. 邮件通知") 1472 | print("5. 用户过滤") 1473 | print("6. 执行次数限制") 1474 | print("7. 屏蔽过滤") 1475 | print("8. 保存文件设置(包括保存路径及大小限制)") 1476 | options = (await ainput("请输入修改项: ")).strip().split(',') 1477 | options = [x.strip() for x in options] 1478 | if '1' in options: 1479 | new_ext = (await ainput("请输入新的文件后缀: ")).strip().lower() 1480 | if not new_ext.startswith('.'): 1481 | new_ext = '.' + new_ext 1482 | cfg[new_ext] = cfg.pop(ext) 1483 | ext = new_ext 1484 | print(f"文件后缀修改为: {new_ext}") 1485 | if '2' in options: 1486 | chat_ids_input = (await ainput("请输入新的监听对话ID(多个逗号分隔): ")).strip() 1487 | cfg[ext]['chats'] = [int(x.strip()) for x in chat_ids_input.split(',')] 1488 | print("监听对话ID已更新") 1489 | if '3' in options: 1490 | auto_forward = (await ainput("是否启用自动转发?(yes/no): ")).strip().lower() == 'yes' 1491 | cfg[ext]['auto_forward'] = auto_forward 1492 | if auto_forward: 1493 | target_ids_input = (await ainput("请输入自动转发目标对话ID(多个逗号分隔): ")).strip() 1494 | cfg[ext]['forward_targets'] = [int(x.strip()) for x in target_ids_input.split(',')] 1495 | else: 1496 | cfg[ext]['forward_targets'] = [] 1497 | if '4' in options: 1498 | email_notify = (await ainput("是否启用邮件通知?(yes/no): ")).strip().lower() == 'yes' 1499 | cfg[ext]['email_notify'] = email_notify 1500 | if '5' in options: 1501 | print("请选择要监控用户其类型: 1. 用户ID(频道id与Bot id也可行) 2. 用户名 3. 昵称") 1502 | user_option = (await ainput("请输入用户类型编号: ")).strip() 1503 | users_input = (await ainput("请输入对应用户标识(多个逗号分隔): ")).strip() 1504 | if users_input: 1505 | if user_option == '1': 1506 | cfg[ext]['users'] = [int(x.strip()) for x in users_input.split(',') if 1507 | x.strip().isdigit()] 1508 | else: 1509 | cfg[ext]['users'] = [x.strip() for x in users_input.split(',')] 1510 | else: 1511 | cfg[ext]['users'] = [] 1512 | cfg[ext]['user_option'] = user_option 1513 | if '6' in options: 1514 | execution_limit_input = ( 1515 | await ainput("请输入新的执行次数限制(正整数),直接回车表示不设置: ")).strip() 1516 | if execution_limit_input.isdigit(): 1517 | cfg[ext]['max_executions'] = int(execution_limit_input) 1518 | else: 1519 | cfg[ext]['max_executions'] = None 1520 | cfg[ext]['execution_count'] = 0 1521 | if '7' in options: 1522 | filter_choice = (await ainput("是否设置屏蔽过滤?(yes/no): ")).strip().lower() == 'yes' 1523 | if filter_choice: 1524 | blocked_users_input = (await ainput("请输入屏蔽用户、频道或Bot(ID,多个逗号分隔): ")).strip() 1525 | blocked_users = [x.strip() for x in 1526 | blocked_users_input.split(',')] if blocked_users_input else [] 1527 | else: 1528 | blocked_users = [] 1529 | cfg[ext]['blocked_users'] = blocked_users 1530 | if '8' in options: 1531 | save_choice = (await ainput("是否启用保存文件到本地?(yes/no): ")).strip().lower() == 'yes' 1532 | if save_choice: 1533 | save_folder = (await ainput("请输入保存文件的本地文件夹路径: ")).strip() 1534 | size_limit_choice = (await ainput( 1535 | "是否设置文件大小范围限制?(yes/no): ")).strip().lower() == 'yes' 1536 | if size_limit_choice: 1537 | min_size_input = (await ainput("请输入文件最小大小(MB): ")).strip() 1538 | max_size_input = (await ainput("请输入文件最大大小(MB): ")).strip() 1539 | try: 1540 | min_size = float(min_size_input) if min_size_input else None 1541 | except: 1542 | min_size = None 1543 | try: 1544 | max_size = float(max_size_input) if max_size_input else None 1545 | except: 1546 | max_size = None 1547 | else: 1548 | min_size = None 1549 | max_size = None 1550 | cfg[ext]['save_folder'] = save_folder 1551 | cfg[ext]['min_size'] = min_size 1552 | cfg[ext]['max_size'] = max_size 1553 | print( 1554 | f"保存文件设置已更新:保存路径:{save_folder}, 最小大小:{min_size} MB, 最大大小:{max_size} MB") 1555 | else: 1556 | cfg[ext]['save_folder'] = None 1557 | cfg[ext]['min_size'] = None 1558 | cfg[ext]['max_size'] = None 1559 | print("已关闭保存文件功能") 1560 | print(f"文件后缀配置更新为: {cfg[ext]}") 1561 | else: 1562 | print("未找到该文件后缀配置") 1563 | 1564 | elif command == 'removeext': 1565 | if current_account is None: 1566 | print("请先切换或添加账号") 1567 | continue 1568 | cfg = ACCOUNTS[current_account]["config"]["file_extension_config"] 1569 | ext = (await ainput("请输入要移除的文件后缀(如 .pdf): ")).strip().lower() 1570 | if not ext.startswith('.'): 1571 | ext = '.' + ext 1572 | if ext in cfg: 1573 | del cfg[ext] 1574 | print(f"已移除文件后缀 '{ext}' 的配置") 1575 | else: 1576 | print("未找到该文件后缀配置") 1577 | 1578 | elif command == 'showext': 1579 | if current_account is None: 1580 | print("请先切换或添加账号") 1581 | continue 1582 | cfg = ACCOUNTS[current_account]["config"]["file_extension_config"] 1583 | print("\n=== 当前文件后缀配置 ===") 1584 | for k, v in cfg.items(): 1585 | print(f"{k} : {v}") 1586 | 1587 | elif command == 'addall': 1588 | if current_account is None: 1589 | print("请先切换或添加账号") 1590 | continue 1591 | cfg = ACCOUNTS[current_account]["config"]["all_messages_config"] 1592 | chat_id = int((await ainput("请输入全量监控对话ID: ")).strip()) 1593 | print("请选择用户类型: 1. 用户ID 2. 用户名 3. 昵称") 1594 | user_option = (await ainput("请输入选项编号(可留空表示全部): ")).strip() 1595 | users_input = (await ainput("请输入对应用户标识(多个逗号分隔,可留空): ")).strip() 1596 | if users_input: 1597 | if user_option == '1': 1598 | user_set = [int(x.strip()) for x in users_input.split(',') if x.strip().isdigit()] 1599 | else: 1600 | user_set = [x.strip() for x in users_input.split(',')] 1601 | else: 1602 | user_set = [] 1603 | auto_forward = (await ainput("启用自动转发?(yes/no): ")).strip().lower() == 'yes' 1604 | email_notify = (await ainput("启用邮件通知?(yes/no): ")).strip().lower() == 'yes' 1605 | if auto_forward: 1606 | target_ids_input = (await ainput("请输入转发目标对话ID(多个逗号分隔): ")).strip() 1607 | target_ids = [int(x.strip()) for x in target_ids_input.split(',')] 1608 | else: 1609 | target_ids = [] 1610 | execution_limit_input = (await ainput("请输入执行次数限制(正整数),直接回车表示不设置: ")).strip() 1611 | if execution_limit_input.isdigit(): 1612 | max_executions = int(execution_limit_input) 1613 | else: 1614 | max_executions = None 1615 | log_file = (await ainput("请输入日志文件路径(直接回车则不记录): ")).strip() or None 1616 | filter_choice = (await ainput("是否设置屏蔽过滤?(yes/no): ")).strip().lower() == 'yes' 1617 | if filter_choice: 1618 | blocked_users_input = (await ainput("请输入屏蔽用户(用户ID,多个逗号分隔): ")).strip() 1619 | blocked_users = [x.strip() for x in blocked_users_input.split(',')] if blocked_users_input else [] 1620 | else: 1621 | blocked_users = [] 1622 | cfg[chat_id] = { 1623 | 'auto_forward': auto_forward, 1624 | 'email_notify': email_notify, 1625 | 'forward_targets': target_ids, 1626 | 'users': user_set, 1627 | 'user_option': user_option, 1628 | 'log_file': log_file, 1629 | 'max_executions': max_executions, 1630 | 'execution_count': 0, 1631 | 'blocked_users': blocked_users, 1632 | } 1633 | print(f"已添加全量监控配置: {cfg[chat_id]}") 1634 | 1635 | elif command == 'modifyall': 1636 | if current_account is None: 1637 | print("请先切换或添加账号") 1638 | continue 1639 | cfg = ACCOUNTS[current_account]["config"]["all_messages_config"] 1640 | chat_id = int((await ainput("请输入要修改的全量监控对话ID: ")).strip()) 1641 | if chat_id in cfg: 1642 | config = cfg[chat_id] 1643 | print(f"当前配置:{config}") 1644 | print("请输入修改项(逗号分隔): 1. 自动转发 2. 邮件通知 3. 转发目标 4. 用户过滤") 1645 | print("5. 日志文件 6. 执行次数限制 7. 屏蔽过滤") 1646 | options = (await ainput("请输入修改项: ")).strip().split(',') 1647 | options = [x.strip() for x in options] 1648 | if '1' in options: 1649 | auto_forward = (await ainput("启用自动转发?(yes/no): ")).strip().lower() == 'yes' 1650 | config['auto_forward'] = auto_forward 1651 | if auto_forward: 1652 | target_ids_input = (await ainput("请输入转发目标对话ID(多个逗号分隔): ")).strip() 1653 | config['forward_targets'] = [int(x.strip()) for x in target_ids_input.split(',')] 1654 | else: 1655 | config['forward_targets'] = [] 1656 | if '2' in options: 1657 | email_notify = (await ainput("启用邮件通知?(yes/no): ")).strip().lower() == 'yes' 1658 | config['email_notify'] = email_notify 1659 | if '3' in options: 1660 | if config.get('auto_forward', False): 1661 | target_ids_input = (await ainput("请输入转发目标对话ID(多个逗号分隔): ")).strip() 1662 | config['forward_targets'] = [int(x.strip()) for x in target_ids_input.split(',')] 1663 | else: 1664 | print("未启用自动转发") 1665 | if '4' in options: 1666 | user_option = (await ainput("请输入用户类型(1. 用户ID 2. 用户名 3. 昵称): ")).strip() 1667 | users_input = (await ainput("请输入对应用户标识(多个逗号分隔): ")).strip() 1668 | if users_input: 1669 | if user_option == '1': 1670 | config['users'] = [int(x.strip()) for x in users_input.split(',') if 1671 | x.strip().isdigit()] 1672 | else: 1673 | config['users'] = [x.strip() for x in users_input.split(',')] 1674 | else: 1675 | config['users'] = [] 1676 | config['user_option'] = user_option 1677 | if '5' in options: 1678 | log_file = (await ainput("请输入新的日志文件路径(留空则删除): ")).strip() 1679 | config['log_file'] = log_file or None 1680 | if '6' in options: 1681 | execution_limit_input = ( 1682 | await ainput("请输入新的执行次数限制(正整数),直接回车表示不设置: ")).strip() 1683 | if execution_limit_input.isdigit(): 1684 | config['max_executions'] = int(execution_limit_input) 1685 | else: 1686 | config['max_executions'] = None 1687 | config['execution_count'] = 0 1688 | if '7' in options: 1689 | filter_choice = (await ainput("是否设置屏蔽过滤?(yes/no): ")).strip().lower() == 'yes' 1690 | if filter_choice: 1691 | blocked_users_input = (await ainput("请输入屏蔽用户(用户ID,多个逗号分隔): ")).strip() 1692 | blocked_users = [x.strip() for x in 1693 | blocked_users_input.split(',')] if blocked_users_input else [] 1694 | else: 1695 | blocked_users = [] 1696 | config['blocked_users'] = blocked_users 1697 | print(f"全量监控配置更新为: {config}") 1698 | else: 1699 | print("未找到该全量监控配置") 1700 | 1701 | elif command == 'removeall': 1702 | if current_account is None: 1703 | print("请先切换或添加账号") 1704 | continue 1705 | cfg = ACCOUNTS[current_account]["config"]["all_messages_config"] 1706 | chat_id = int((await ainput("请输入要移除的全量监控对话ID: ")).strip()) 1707 | if chat_id in cfg: 1708 | del cfg[chat_id] 1709 | print("已移除全量监控配置") 1710 | else: 1711 | print("未找到该全量监控配置") 1712 | 1713 | elif command == 'showall': 1714 | if current_account is None: 1715 | print("请先切换或添加账号") 1716 | continue 1717 | cfg = ACCOUNTS[current_account]["config"]["all_messages_config"] 1718 | print("\n=== 当前全量监控配置 ===") 1719 | for k, v in cfg.items(): 1720 | print(f"{k} : {v}") 1721 | 1722 | elif command == 'addbutton': 1723 | if current_account is None: 1724 | print("请先切换或添加账号") 1725 | continue 1726 | cfg = ACCOUNTS[current_account]["config"]["button_keyword_config"] 1727 | # 输入配置标识(用于标识该配置) 1728 | b_identifier = (await ainput("请输入按钮配置标识(例如:关键词): ")).strip().lower() 1729 | chat_ids_input = (await ainput("请输入监听对话ID(多个逗号分隔): ")).strip() 1730 | chat_ids = [int(x.strip()) for x in chat_ids_input.split(',')] 1731 | print("请选择用户类型: 1. 用户ID 2. 用户名 3. 昵称") 1732 | user_option = (await ainput("请输入选项编号(可留空表示全部): ")).strip() 1733 | users_input = (await ainput("请输入对应用户标识(多个逗号分隔): ")).strip() 1734 | if users_input: 1735 | if user_option == '1': 1736 | user_set = [int(x.strip()) for x in users_input.split(',') if x.strip().isdigit()] 1737 | else: 1738 | user_set = [x.strip() for x in users_input.split(',')] 1739 | else: 1740 | user_set = [] 1741 | print("请选择按钮匹配模式:") 1742 | print("1. 手动模式(使用按钮关键词匹配)") 1743 | print("2. AI 自动模式(根据消息内容自动选择按钮)") 1744 | mode_choice = (await ainput("请输入模式编号 (1/2): ")).strip() 1745 | if mode_choice == '2': 1746 | mode = "ai" 1747 | ai_prompt = (await ainput("请输入 AI 提示语(可留空则使用默认提示): ")).strip() 1748 | if not ai_prompt: 1749 | ai_prompt = "请根据下面的消息内容和按钮选项,选择最合适的按钮,返回该按钮包含的关键字。" 1750 | button_keyword = "" 1751 | else: 1752 | mode = "manual" 1753 | button_keyword = b_identifier 1754 | ai_prompt = "" 1755 | max_executions_input = (await ainput("请输入最大执行次数(正整数),直接回车表示不设置: ")).strip() 1756 | if max_executions_input.isdigit(): 1757 | max_executions = int(max_executions_input) 1758 | else: 1759 | max_executions = None 1760 | cfg[b_identifier] = { 1761 | 'chats': chat_ids, 1762 | 'users': user_set, 1763 | 'user_option': user_option, 1764 | 'mode': mode, 1765 | 'ai_prompt': ai_prompt, 1766 | 'button_keyword': button_keyword, 1767 | 'max_executions': max_executions, 1768 | 'execution_count': 0 1769 | } 1770 | print(f"已添加按钮监控配置: {cfg[b_identifier]}") 1771 | 1772 | elif command == 'modifybutton': 1773 | if current_account is None: 1774 | print("请先切换或添加账号") 1775 | continue 1776 | cfg = ACCOUNTS[current_account]["config"]["button_keyword_config"] 1777 | identifier = (await ainput("请输入要修改的按钮配置标识: ")).strip().lower() 1778 | if identifier in cfg: 1779 | config = cfg[identifier] 1780 | print(f"当前配置:{config}") 1781 | print( 1782 | "请输入要修改的项(逗号分隔): 1. 配置标识 2. 监听对话ID 3. 用户过滤 4. 模式选择 5. 最大执行次数") 1783 | options = (await ainput("请输入修改项: ")).strip().split(',') 1784 | options = [x.strip() for x in options] 1785 | if '1' in options: 1786 | new_identifier = (await ainput("请输入新的按钮配置标识: ")).strip().lower() 1787 | if config.get('mode') == 'manual': 1788 | config['button_keyword'] = new_identifier 1789 | else: 1790 | config['button_keyword'] = "" 1791 | cfg[new_identifier] = cfg.pop(identifier) 1792 | identifier = new_identifier 1793 | print(f"配置标识修改为: {new_identifier}") 1794 | if '2' in options: 1795 | chat_ids_input = (await ainput("请输入新的监听对话ID(多个逗号分隔): ")).strip() 1796 | config['chats'] = [int(x.strip()) for x in chat_ids_input.split(',')] 1797 | print("监听对话ID已更新") 1798 | if '3' in options: 1799 | user_option = (await ainput("请输入新的用户类型(1/2/3): ")).strip() 1800 | users_input = (await ainput("请输入对应用户标识(多个逗号分隔): ")).strip() 1801 | if users_input: 1802 | if user_option == '1': 1803 | config['users'] = [int(x.strip()) for x in users_input.split(',') if 1804 | x.strip().isdigit()] 1805 | else: 1806 | config['users'] = [x.strip() for x in users_input.split(',')] 1807 | else: 1808 | config['users'] = [] 1809 | config['user_option'] = user_option 1810 | if '4' in options: 1811 | print("请选择按钮匹配模式:") 1812 | print("1. 手动模式(使用按钮关键词匹配)") 1813 | print("2. AI 自动模式(根据消息内容自动选择按钮)") 1814 | mode_choice = (await ainput("请输入模式编号 (1/2): ")).strip() 1815 | if mode_choice == '2': 1816 | config['mode'] = "ai" 1817 | ai_prompt = (await ainput("请输入 AI 提示语(可留空则使用默认提示): ")).strip() 1818 | if not ai_prompt: 1819 | ai_prompt = "请根据下面的消息内容和按钮选项,选择最合适的按钮,返回该按钮包含的关键字。" 1820 | config['ai_prompt'] = ai_prompt 1821 | config['button_keyword'] = "" 1822 | else: 1823 | config['mode'] = "manual" 1824 | config['ai_prompt'] = "" 1825 | config['button_keyword'] = (await ainput("请输入按钮关键词: ")).strip().lower() 1826 | print("按钮匹配模式已更新") 1827 | if '5' in options: 1828 | max_exec_input = (await ainput("请输入新的最大执行次数(正整数),直接回车表示不设置: ")).strip() 1829 | if max_exec_input.isdigit(): 1830 | config['max_executions'] = int(max_exec_input) 1831 | else: 1832 | config['max_executions'] = None 1833 | config['execution_count'] = 0 1834 | print("最大执行次数已更新") 1835 | print(f"按钮监控配置更新为: {config}") 1836 | else: 1837 | print("未找到该按钮监控配置") 1838 | 1839 | elif command == 'removebutton': 1840 | if current_account is None: 1841 | print("请先切换或添加账号") 1842 | continue 1843 | cfg = ACCOUNTS[current_account]["config"]["button_keyword_config"] 1844 | if not cfg: 1845 | print("当前没有任何按钮配置") 1846 | continue 1847 | print("当前按钮配置如下:") 1848 | keys = list(cfg.keys()) 1849 | for i, key in enumerate(keys, start=1): 1850 | mode = cfg[key].get('mode', 'manual') 1851 | print(f"{i}. 配置标识: '{key}', 模式: {mode}") 1852 | remove_input = (await ainput("请输入要移除的配置标识或序号: ")).strip().lower() 1853 | removed = False 1854 | if remove_input.isdigit(): 1855 | idx = int(remove_input) - 1 1856 | if 0 <= idx < len(keys): 1857 | key_to_remove = keys[idx] 1858 | del cfg[key_to_remove] 1859 | print(f"已移除配置: {key_to_remove}") 1860 | removed = True 1861 | else: 1862 | print("序号超出范围") 1863 | else: 1864 | if remove_input in cfg: 1865 | del cfg[remove_input] 1866 | print(f"已移除配置: {remove_input}") 1867 | removed = True 1868 | if not removed: 1869 | print("未找到对应的按钮配置") 1870 | 1871 | elif command == 'showbuttons': 1872 | if current_account is None: 1873 | print("请先切换或添加账号") 1874 | continue 1875 | cfg = ACCOUNTS[current_account]["config"]["button_keyword_config"] 1876 | if not cfg: 1877 | print("当前没有任何按钮配置") 1878 | else: 1879 | print("\n=== 当前按钮配置 ===") 1880 | for key, conf in cfg.items(): 1881 | mode = conf.get('mode', 'manual') 1882 | ai_prompt = conf.get('ai_prompt', '') 1883 | print(f"配置标识: '{key}' | 模式: {mode} | AI提示: {ai_prompt}") 1884 | 1885 | elif command == 'addlistener': 1886 | if current_account is None: 1887 | print("请先切换或添加账号") 1888 | continue 1889 | cfg = ACCOUNTS[current_account]["config"]["image_button_monitor"] 1890 | chat_id_input = (await ainput("请输入要监听的对话ID: ")).strip() 1891 | try: 1892 | chat_id_val = int(chat_id_input) 1893 | except: 1894 | print("无效的对话ID") 1895 | continue 1896 | max_exec_input = (await ainput("请输入最大执行次数(正整数),直接回车表示不设置: ")).strip() 1897 | if max_exec_input.isdigit(): 1898 | max_executions = int(max_exec_input) 1899 | else: 1900 | max_executions = None 1901 | listener_config = { 1902 | "chat_id": chat_id_val, 1903 | "max_executions": max_executions, 1904 | "execution_count": 0 1905 | } 1906 | if isinstance(cfg, list): 1907 | cfg.append(listener_config) 1908 | else: 1909 | cfg = [listener_config] 1910 | ACCOUNTS[current_account]["config"]["image_button_monitor"] = cfg 1911 | print(f"已添加图片+按钮监听配置: {listener_config}") 1912 | 1913 | elif command == 'modifylistener': 1914 | if current_account is None: 1915 | print("请先切换或添加账号") 1916 | continue 1917 | cfg = ACCOUNTS[current_account]["config"]["image_button_monitor"] 1918 | if not cfg: 1919 | print("当前没有任何图片+按钮监听配置") 1920 | continue 1921 | print("当前图片+按钮监听配置如下:") 1922 | for i, item in enumerate(cfg, start=1): 1923 | print( 1924 | f"{i}. 对话ID: {item.get('chat_id')}, 最大执行次数: {item.get('max_executions')}, 已执行次数: {item.get('execution_count')}") 1925 | choice = (await ainput("请输入要修改的配置序号: ")).strip() 1926 | if not choice.isdigit(): 1927 | print("请输入有效的序号") 1928 | continue 1929 | idx = int(choice) - 1 1930 | if idx < 0 or idx >= len(cfg): 1931 | print("序号超出范围") 1932 | continue 1933 | item = cfg[idx] 1934 | new_chat_id = (await ainput("请输入新的对话ID(直接回车保持不变): ")).strip() 1935 | if new_chat_id: 1936 | try: 1937 | item["chat_id"] = int(new_chat_id) 1938 | except: 1939 | print("无效的对话ID") 1940 | max_exec_input = (await ainput("请输入新的最大执行次数(正整数),直接回车表示不设置: ")).strip() 1941 | if max_exec_input.isdigit(): 1942 | item["max_executions"] = int(max_exec_input) 1943 | else: 1944 | item["max_executions"] = None 1945 | item["execution_count"] = 0 1946 | print(f"图片+按钮监听配置已更新为: {item}") 1947 | 1948 | elif command == 'removelistener': 1949 | if current_account is None: 1950 | print("请先切换或添加账号") 1951 | continue 1952 | cfg_list = ACCOUNTS[current_account]["config"]["image_button_monitor"] 1953 | chat_id = int((await ainput("请输入要移除的监听对话ID: ")).strip()) 1954 | if chat_id in cfg_list: 1955 | cfg_list.remove(chat_id) 1956 | print(f"已移除图片+按钮监听对话ID: {chat_id}") 1957 | else: 1958 | print("未找到该监听配置") 1959 | 1960 | elif command == 'showlistener': 1961 | if current_account is None: 1962 | print("请先切换或添加账号") 1963 | continue 1964 | cfg_list = ACCOUNTS[current_account]["config"]["image_button_monitor"] 1965 | print("\n=== 当前图片+按钮监听对话ID ===") 1966 | for cid in cfg_list: 1967 | print(cid) 1968 | 1969 | elif command == 'schedule': 1970 | if current_account is None: 1971 | print("请先切换或添加账号") 1972 | continue 1973 | cfg_sched = ACCOUNTS[current_account]["config"]["scheduled_messages"] 1974 | target_id = int((await ainput("请输入目标对话ID: ")).strip()) 1975 | message = (await ainput("请输入要发送的消息: ")).strip() 1976 | cron_expression = (await ainput("请输入Cron表达式: ")).strip() 1977 | random_offset_input = (await ainput("请输入随机时间误差(秒),默认为0: ")).strip() 1978 | random_offset = int(random_offset_input) if random_offset_input else 0 1979 | delete_after_sending = (await ainput("是否在发送后删除消息?(yes/no): ")).strip().lower() == 'yes' 1980 | execution_limit_input = (await ainput("请输入执行次数限制(正整数),直接回车表示不设置: ")).strip() 1981 | if execution_limit_input.isdigit(): 1982 | max_executions = int(execution_limit_input) 1983 | else: 1984 | max_executions = None 1985 | 1986 | job = schedule_message(target_id, message, cron_expression, random_offset, delete_after_sending, 1987 | current_account) 1988 | cfg_sched.append({ 1989 | 'job_id': job.id, 1990 | 'target_id': target_id, 1991 | 'message': message, 1992 | 'cron': cron_expression, 1993 | 'random_offset': random_offset, 1994 | 'delete_after_sending': delete_after_sending, 1995 | 'account_id': current_account, 1996 | 'max_executions': max_executions, 1997 | 'execution_count': 0 1998 | }) 1999 | print(f"已添加定时消息配置,Job ID: {job.id}") 2000 | 2001 | elif command == 'modifyschedule': 2002 | if current_account is None: 2003 | print("请先切换或添加账号") 2004 | continue 2005 | job_id = (await ainput("请输入要修改的定时消息的Job ID: ")).strip() 2006 | job = scheduler.get_job(job_id) 2007 | if job: 2008 | sched = next( 2009 | (s for s in ACCOUNTS[current_account]["config"]["scheduled_messages"] if s['job_id'] == job_id), 2010 | None) 2011 | if sched: 2012 | print(f"当前配置:{sched}") 2013 | print( 2014 | "请输入修改项(逗号分隔): 1. 目标对话ID 2. 消息内容 3. Cron表达式 4. 随机时间误差 5. 发送后删除 6. 执行次数限制") 2015 | options = (await ainput("请输入修改项: ")).strip().split(',') 2016 | options = [x.strip() for x in options] 2017 | if '1' in options: 2018 | sched['target_id'] = int((await ainput("请输入新的目标对话ID: ")).strip()) 2019 | if '2' in options: 2020 | sched['message'] = (await ainput("请输入新的消息内容: ")).strip() 2021 | if '3' in options: 2022 | sched['cron'] = (await ainput("请输入新的Cron表达式: ")).strip() 2023 | if '4' in options: 2024 | r = (await ainput("请输入新的随机时间误差(秒): ")).strip() 2025 | sched['random_offset'] = int(r) if r else 0 2026 | if '5' in options: 2027 | sched['delete_after_sending'] = (await ainput( 2028 | "是否在发送后删除?(yes/no): ")).strip().lower() == 'yes' 2029 | if '6' in options: 2030 | execution_limit_input = ( 2031 | await ainput("请输入新的执行次数限制(正整数),直接回车表示不设置: ")).strip() 2032 | if execution_limit_input.isdigit(): 2033 | sched['max_executions'] = int(execution_limit_input) 2034 | else: 2035 | sched['max_executions'] = None 2036 | sched['execution_count'] = 0 2037 | try: 2038 | scheduler.remove_job(job_id) 2039 | except Exception as e: 2040 | logger.error(f"删除定时任务时出错: {repr(e)}") 2041 | new_job = scheduler.add_job( 2042 | send_scheduled_message, 2043 | CronTrigger.from_crontab(sched['cron'], timezone=pytz.timezone('Asia/Shanghai')), 2044 | args=[sched['target_id'], sched['message'], sched.get('random_offset', 0), 2045 | sched.get('delete_after_sending', False), sched.get('account_id')], 2046 | id=job_id 2047 | ) 2048 | print(f"定时消息配置 '{job_id}' 已更新") 2049 | else: 2050 | print("未找到该定时消息配置") 2051 | else: 2052 | print("未找到该定时消息") 2053 | 2054 | elif command == 'removeschedule': 2055 | if current_account is None: 2056 | print("请先切换或添加账号") 2057 | continue 2058 | job_id = (await ainput("请输入要删除的定时消息的Job ID: ")).strip() 2059 | try: 2060 | scheduler.remove_job(job_id) 2061 | sched_list = ACCOUNTS[current_account]["config"]["scheduled_messages"] 2062 | ACCOUNTS[current_account]["config"]["scheduled_messages"] = [s for s in sched_list if 2063 | s['job_id'] != job_id] 2064 | print(f"已删除定时消息配置,Job ID: {job_id}") 2065 | except Exception as e: 2066 | logger.error(f"删除定时消息时出错: {repr(e)}") 2067 | print("未找到该定时消息") 2068 | 2069 | elif command == 'showschedule': 2070 | if current_account is None: 2071 | print("请先切换或添加账号") 2072 | continue 2073 | sched_list = ACCOUNTS[current_account]["config"]["scheduled_messages"] 2074 | print("\n=== 当前定时消息配置 ===") 2075 | for s in sched_list: 2076 | print(s) 2077 | 2078 | elif command == 'export': 2079 | await export_all_configs() 2080 | 2081 | elif command == 'import': 2082 | await import_all_configs() 2083 | 2084 | elif command == 'exportlinks': 2085 | all_accounts = list(ACCOUNTS.keys()) 2086 | 2087 | if not all_accounts: 2088 | print("没有可用的账号") 2089 | continue 2090 | 2091 | print("\n==== 导出频道群组链接 ====") 2092 | print("0. 导出所有账号") 2093 | for i, acc_id in enumerate(all_accounts, 1): 2094 | print(f"{i}. 账号 {acc_id}") 2095 | 2096 | acc_choice = await ainput("请选择账号 (0-所有账号): ") 2097 | try: 2098 | acc_idx = int(acc_choice) 2099 | selected_account = None 2100 | if acc_idx > 0 and acc_idx <= len(all_accounts): 2101 | selected_account = all_accounts[acc_idx - 1] 2102 | except ValueError: 2103 | print("请输入有效的数字") 2104 | continue 2105 | 2106 | format_choice = await ainput("请选择导出格式 (1-JSON, 2-CSV): ") 2107 | export_format = 'json' 2108 | if format_choice == '2': 2109 | export_format = 'csv' 2110 | 2111 | filename = await export_channels_links(selected_account, export_format) 2112 | if filename: 2113 | print(f"链接已导出到文件: {filename}") 2114 | 2115 | elif command == 'start': 2116 | scope = (await ainput("请选择监控开关范围: 1. 当前账号 2. 全局: ")).strip() 2117 | timing = (await ainput("请输入定时开机设置(输入延迟分钟或Cron表达式,直接回车则立即开机): ")).strip() 2118 | if timing: 2119 | if timing.isdigit(): 2120 | delay_minutes = int(timing) 2121 | if scope == '1': 2122 | scheduler.add_job(lambda: set_account_monitor_active(current_account, True), 2123 | 'date', 2124 | run_date=datetime.now() + timedelta(minutes=delay_minutes)) 2125 | print(f"将在 {delay_minutes} 分钟后自动开启当前账号 {current_account} 的监控") 2126 | else: 2127 | scheduler.add_job(lambda: set_monitor_active(True), 2128 | 'date', 2129 | run_date=datetime.now() + timedelta(minutes=delay_minutes)) 2130 | print(f"将在 {delay_minutes} 分钟后自动开启全局监控") 2131 | else: 2132 | try: 2133 | if scope == '1': 2134 | scheduler.add_job(lambda: set_account_monitor_active(current_account, True), 2135 | CronTrigger.from_crontab(timing, 2136 | timezone=pytz.timezone('Asia/Shanghai'))) 2137 | print(f"已添加周期性开机任务(当前账号),Cron表达式: {timing}") 2138 | else: 2139 | scheduler.add_job(lambda: set_monitor_active(True), 2140 | CronTrigger.from_crontab(timing, 2141 | timezone=pytz.timezone('Asia/Shanghai'))) 2142 | print(f"已添加周期性开机任务(全局),Cron表达式: {timing}") 2143 | except Exception as e: 2144 | print(f"Cron表达式设置有误:{e}") 2145 | else: 2146 | if scope == '1': 2147 | set_account_monitor_active(current_account, True) 2148 | print(f"当前账号 {current_account} 的监控已立即开启") 2149 | else: 2150 | set_monitor_active(True) 2151 | print("全局监控已立即开启") 2152 | elif command == 'stop': 2153 | scope = (await ainput("请选择监控开关范围: 1. 当前账号 2. 全局: ")).strip() 2154 | timing = (await ainput("请输入定时关机设置(输入延迟分钟或Cron表达式,直接回车则立即关闭): ")).strip() 2155 | if timing: 2156 | if timing.isdigit(): 2157 | delay_minutes = int(timing) 2158 | if scope == '1': 2159 | scheduler.add_job(lambda: set_account_monitor_active(current_account, False), 2160 | 'date', 2161 | run_date=datetime.now() + timedelta(minutes=delay_minutes)) 2162 | print(f"将在 {delay_minutes} 分钟后自动关闭当前账号 {current_account} 的监控") 2163 | else: 2164 | scheduler.add_job(lambda: set_monitor_active(False), 2165 | 'date', 2166 | run_date=datetime.now() + timedelta(minutes=delay_minutes)) 2167 | print(f"将在 {delay_minutes} 分钟后自动关闭全局监控") 2168 | else: 2169 | try: 2170 | if scope == '1': 2171 | scheduler.add_job(lambda: set_account_monitor_active(current_account, False), 2172 | CronTrigger.from_crontab(timing, 2173 | timezone=pytz.timezone('Asia/Shanghai'))) 2174 | print(f"已添加周期性关机任务(当前账号),Cron表达式: {timing}") 2175 | else: 2176 | scheduler.add_job(lambda: set_monitor_active(False), 2177 | CronTrigger.from_crontab(timing, 2178 | timezone=pytz.timezone('Asia/Shanghai'))) 2179 | print(f"已添加周期性关机任务(全局),Cron表达式: {timing}") 2180 | except Exception as e: 2181 | print(f"Cron表达式设置有误:{e}") 2182 | else: 2183 | if scope == '1': 2184 | set_account_monitor_active(current_account, False) 2185 | print(f"当前账号 {current_account} 的监控已立即关闭") 2186 | else: 2187 | set_monitor_active(False) 2188 | print("全局监控已立即关闭") 2189 | 2190 | elif command == 'exit': 2191 | print("正在退出程序...") 2192 | for account in ACCOUNTS.values(): 2193 | await account["client"].disconnect() 2194 | scheduler.shutdown() 2195 | return 2196 | else: 2197 | print("未知命令,请输入 help 查看所有可用命令") 2198 | except Exception as e: 2199 | logger.error(f"执行命令时出错: {repr(e)}") 2200 | print(f"执行命令时出错: {repr(e)}") 2201 | 2202 | 2203 | async def telegram_login(client): 2204 | logger.info('开始Telegram登录流程...') 2205 | phone = (await ainput('请输入您的Telegram手机号 (格式如: +8613800138000): ')).strip() 2206 | try: 2207 | await client.send_code_request(phone) 2208 | logger.info('验证码已发送到您的Telegram账号') 2209 | code = (await ainput('请输入您收到的验证码: ')).strip() 2210 | try: 2211 | await client.sign_in(phone, code) 2212 | except SessionPasswordNeededError: 2213 | logger.info('检测到两步验证,需要输入密码') 2214 | password = (await ainput('请输入您的两步验证密码: ')).strip() 2215 | await client.sign_in(password=password) 2216 | logger.info('Telegram登录成功!') 2217 | except Exception as e: 2218 | error_message = repr(e) 2219 | logger.error(f'登录过程中发生错误:{error_message}') 2220 | raise 2221 | 2222 | 2223 | async def main(): 2224 | scheduler.start() 2225 | print("\n=== Telegram消息监控程序 ===") 2226 | print("请先设置监控参数") 2227 | await handle_commands() 2228 | 2229 | 2230 | if __name__ == '__main__': 2231 | monitor_active = False 2232 | scheduler = AsyncIOScheduler() 2233 | try: 2234 | asyncio.run(main()) 2235 | except KeyboardInterrupt: 2236 | logger.info('程序被用户中断') 2237 | except Exception as e: 2238 | error_message = repr(e) 2239 | logger.error(f'程序发生错误:{error_message}') 2240 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | telethon 2 | apscheduler 3 | pytz 4 | openai 5 | socks 6 | --------------------------------------------------------------------------------