diff --git a/db/utils.py b/db/utils.py index 10db0be..d6fd018 100644 --- a/db/utils.py +++ b/db/utils.py @@ -11,6 +11,7 @@ 'db', 'User', 'User_subscribe_list', + 'User_block_list', ] _current_path = os.path.dirname(os.path.realpath(__file__)) @@ -57,13 +58,35 @@ class User_subscribe_list(_Base): status = SmallIntegerField(default=0)# 0 正常 1删除 create_time = DateTimeField('%Y-%m-%d %H:%M:%S',null=True) - +class User_block_list(_Base): + """ + 用户屏蔽列表(黑名单设置) + user_block_list + id user_id blacklist_type blacklist_value channel_name chat_id create_time update_time + """ + user_id = IntegerField(index=True) + + blacklist_type = CharField(50, null=False) # 黑名单的类型。比如length_limit、keyword、username + blacklist_value = CharField(120, null=False) # 黑名单值 + + channel_name = CharField(50,null=True,default='')# 应用范围 频道名称 + chat_id = CharField(50, null=True, default='') # 应用范围 群组/频道的非官方id。 e.g. -1001630956637,如果为空或默认值,表示所有群组 + + create_time = DateTimeField('%Y-%m-%d %H:%M:%S', null=True) + update_time = DateTimeField('%Y-%m-%d %H:%M:%S', null=True) + + class Meta: + indexes = ( + # (('user_id', 'channel_name','chat_id', 'blocked_username'), True), # user_id, chat_id和blocked_username整体作为唯一索引 + ) + class _Db: def __init__(self): #创建实例类 init_class = [ User, - User_subscribe_list + User_subscribe_list, + User_block_list, ] for model_class in init_class: try: diff --git a/main.py b/main.py index 50f878f..fd44d9c 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ from telethon.extensions import markdown,html from asyncstdlib.functools import lru_cache as async_lru_cache import asyncio -from utils.common import is_allow_access,banner +from utils.common import is_allow_access,banner,is_msg_block,get_event_chat_username,get_event_chat_username_list # 配置访问tg服务器的代理 proxy = None @@ -185,8 +185,8 @@ async def on_greeting(event): if not hasattr(event_chat,'title'): logger.warning(f'event_chat not found title:{event_chat}') else: - _title = f'event_chat.title:{event_chat.title},' - logger.debug(f'event_chat.username: {event_chat.username},event_chat.id:{event_chat.id},{_title} event.message.id:{event.message.id},text:{text}') + _title = f'event.chat.title:{event_chat.title},' + logger.debug(f'event.chat.username: {get_event_chat_username(event_chat)},event.chat.id:{event_chat.id},{_title} event.message.id:{event.message.id},text:{text}') # 1.方法(失败):转发消息 # chat = 'keyword_alert_bot' #能转发 但是不能真对特定用户。只能转发给当前允许账户的bot @@ -201,10 +201,13 @@ async def on_greeting(event): # 2.方法:直接发送新消息,非转发.但是可以url预览达到效果 # 查找当前频道的所有订阅 + event_chat_username_list = get_event_chat_username_list(event_chat) + event_chat_username = get_event_chat_username(event_chat) + placeholders = ','.join('?' for _ in event_chat_username_list)# 占位符填充 condition_strs = ['l.chat_id = ?'] - if event_chat.username: - condition_strs.append('l.channel_name = ?') + if event_chat_username_list: + condition_strs.append(' l.channel_name in ({placeholders}) ') sql = f""" select u.chat_id,l.keywords,l.id,l.chat_id @@ -212,14 +215,15 @@ async def on_greeting(event): INNER JOIN user as u on u.id = l.user_id where ({' OR '.join(condition_strs)}) and l.status = 0 order by l.create_time desc """ + # bind = [str(event.chat_id)] bind = [str(telethon_utils.get_peer_id(PeerChannel(event.chat_id)))] # 确保查询和入库的id单位统一 marked_id - if event_chat.username: - bind.append(event_chat.username) + if event_chat_username_list: + bind += event_chat_username_list find = utils.db.connect.execute_sql(sql,tuple(bind)).fetchall() if find: - logger.info(f'channel: {event_chat.username}; all chat_id & keywords:{find}') # 打印当前频道,订阅的用户以及关键字 + logger.info(f'channel: {event_chat_username_list}; all chat_id & keywords:{find}') # 打印当前频道,订阅的用户以及关键字 for receiver,keywords,l_id,l_chat_id in find: try: @@ -235,7 +239,8 @@ async def on_greeting(event): logger.debug(f'msg_unique_rule:{config["msg_unique_rule"]} --> {CACHE_KEY_UNIQUE_SEND}') # 优先返回可预览url - channel_url = f'https://t.me/{event_chat.username}/' if event_chat.username else get_channel_url(event_chat.username,event.chat_id) + channel_url = f'https://t.me/{event_chat_username}/' if event_chat_username else get_channel_url(event_chat_username,event.chat_id) + channel_msg_url= f'{channel_url}{message.id}' send_cache_key = f'_LAST_{l_id}_{message.id}_send' if isinstance(event,events.MessageEdited.Event):# 编辑事件 @@ -248,7 +253,7 @@ async def on_greeting(event): re_update = utils.db.user_subscribe_list.update(chat_id = str(event.chat_id) ).where(utils.User_subscribe_list.id == l_id) re_update.execute() - chat_title = event_chat.username or event_chat.title + chat_title = event_chat_username or event.chat.title if is_regex_str(keywords):# 输入为正则字符串 regex_match = js_to_py_re(keywords)(text)# 进行正则匹配 只支持ig两个flag if isinstance(regex_match,regex.Match):#search()结果 @@ -261,12 +266,18 @@ async def on_greeting(event): regex_match_str = list(set(regex_match_str))# 处理重复元素 if regex_match_str:# 默认 findall()结果 # # {chat_title} \n\n - channel_title = f"\n\nCHANNEL: {chat_title}" if not event_chat.username else "" + channel_title = f"\n\nCHANNEL: {chat_title}" if not event_chat_username else "" + message_str = f'[#FOUND]({channel_msg_url}) **{regex_match_str}**{channel_title}' if cache.add(CACHE_KEY_UNIQUE_SEND,1,expire=5): logger.info(f'REGEX: receiver chat_id:{receiver}, l_id:{l_id}, message_str:{message_str}') if isinstance(event,events.NewMessage.Event):# 新建事件 cache.set(send_cache_key,1,expire=86400) # 发送标记缓存一天 + + # 黑名单检查 + if is_msg_block(receiver=receiver,msg=message.text,channel_name=event_chat_username,channel_id=event.chat_id): + continue + await bot.send_message(receiver, message_str,link_preview = True,parse_mode = 'markdown') else: # 已发送该消息 @@ -274,16 +285,21 @@ async def on_greeting(event): continue else: - logger.debug(f'regex_match empty. regex:{keywords} ,message: t.me/{event_chat.username}/{event.message.id}') + logger.debug(f'regex_match empty. regex:{keywords} ,message: t.me/{event_chat_username}/{event.message.id}') else:#普通模式 if keywords in text: # # {chat_title} \n\n - channel_title = f"\n\nCHANNEL: {chat_title}" if not event_chat.username else "" + channel_title = f"\n\nCHANNEL: {chat_title}" if not event_chat_username else "" message_str = f'[#FOUND]({channel_msg_url}) **{keywords}**{channel_title}' if cache.add(CACHE_KEY_UNIQUE_SEND,1,expire=5): logger.info(f'TEXT: receiver chat_id:{receiver}, l_id:{l_id}, message_str:{message_str}') if isinstance(event,events.NewMessage.Event):# 新建事件 cache.set(send_cache_key,1,expire=86400) # 发送标记缓存一天 + + # 黑名单检查 + if is_msg_block(receiver=receiver,msg=message.text,channel_name=event_chat_username,channel_id=event.chat_id): + continue + await bot.send_message(receiver, message_str,link_preview = True,parse_mode = 'markdown') else: # 已发送该消息 @@ -306,12 +322,12 @@ async def on_greeting(event): except Exception as _e: logger.error(f'{_e}') else: - logger.debug(f'sql find empty. event_chat.username:{event_chat.username}, find:{find}, sql:{sql}') + logger.debug(f'sql find empty. event.chat.username:{event_chat_username}, find:{find}, sql:{sql}') if 'auto_leave_channel' in config and config['auto_leave_channel']: - if event_chat.username:# 公开频道/组 - logger.info(f'Leave Channel/group: {event_chat.username}') - await leave_channel(event_chat.username) + if event_chat_username:# 公开频道/组 + logger.info(f'Leave Channel/group: {event_chat_username}') + await leave_channel(event_chat_username) # bot相关操作 @@ -425,7 +441,8 @@ async def join_channel_insert_subscribe(user_id,keyword_channel_list): channel_entity = await client_get_entity(c, time.time() // 86400) chat_id = telethon_utils.get_peer_id(PeerChannel(channel_entity.id)) # 转换为marked_id - if channel_entity and hasattr(channel_entity,'username'): username = channel_entity.username + if channel_entity: + username = get_event_chat_username(channel_entity) or '' if channel_entity and not channel_entity.left: # 已加入该频道 logger.warning(f'user_id:{user_id}触发检查 已加入该私有频道:{chat_id} invite_hash:{c}') @@ -709,6 +726,61 @@ async def unsubscribe(event): raise events.StopPropagation +# 限制消息文本长度 +@bot.on(events.NewMessage(pattern='/setlengthlimit')) +async def setlengthlimit(event): + blacklist_type = 'length_limit' + command = r'/setlengthlimit' + # get chat_id + chat_id = event.message.chat.id + find = utils.db.user.get_or_none(chat_id=chat_id) + user_id = find + if not find: # 用户信息不存在 + await event.respond('Failed. Please input /start') + raise events.StopPropagation + + # parse input + text = event.message.text + text = text.replace(',', ',') # 替换掉中文逗号 + text = regex.sub(f'^{command}', '', text).strip() # 确保英文逗号间隔中间都没有空格 + splitd = [i for i in text.split(',') if i] # 删除空元素 + + find = utils.db.connect.execute_sql('select id,blacklist_value from user_block_list where user_id = ? and blacklist_type=? ' ,(user_id.id,blacklist_type)).fetchone() + if not splitd: + if find is None: + await event.respond(f'lengthlimit not found.') + else: + await event.respond(f'setlengthlimit `{find[1]}`') + else: # 传入多参数 e.g. /setlengthlimit 123 + if len(splitd) == 1 and splitd[0].isdigit(): + blacklist_value = int(splitd[0]) + + if find is None: + # create entry in UserBlockList + insert_res = utils.db.user_block_list.create(**{ + 'user_id': user_id, + 'channel_name': '', + 'chat_id': '', + 'blacklist_type': blacklist_type, + 'blacklist_value': blacklist_value, + 'create_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'update_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + + if insert_res: + await event.respond(f'Success lengthlimit `{blacklist_value}`') + else: + await event.respond(f'Failed lengthlimit `{blacklist_value}`') + else: + update_query = utils.db.user_block_list.update(blacklist_value = blacklist_value,update_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')).where(utils.User_block_list.id == find[0])#更新状态 + update_result = update_query.execute()# 更新成功返回1,不管是否重复执行 + if update_result: + await event.respond(f'Success lengthlimit `{blacklist_value}`') + else: + await event.respond(f'Failed lengthlimit `{blacklist_value}`') + raise events.StopPropagation + + @bot.on(events.NewMessage(pattern='/help')) async def start(event): await event.respond(''' diff --git a/utils/common.py b/utils/common.py index a716db4..a78de0b 100644 --- a/utils/common.py +++ b/utils/common.py @@ -1,7 +1,9 @@ from config import config from colorama import Fore, Style, init from text_box_wrapper import wrap +from logger import logger from .__version__ import __version__ +from db import utils def is_allow_access(chat_id) -> bool: @@ -46,4 +48,73 @@ def banner(): green_circle = f"{Fore.GREEN}● success{Style.RESET_ALL}\n" tag = read_tag_from_file() message = f"{green_circle} 🤖️Telegram keyword alert bot (Version: {tag})" - return message \ No newline at end of file + return message + + +def is_msg_block(receiver,msg,channel_name,channel_id): + """ + 消息黑名单检查 + Args: + receiver : 消息接收用户 chat id + msg : 消息内容 + channel_name : 消息发送的频道名称 + channel_id : 消息发送的频道id + + Returns: + Bool: True 命中黑名单 不发送消息,False 无命中 发送消息 + """ + user = utils.db.user.get_or_none(chat_id=receiver) + + for blacklist_type in ['length_limit']: + find = utils.db.connect.execute_sql('select id,blacklist_value from user_block_list where user_id = ? and blacklist_type=? ' ,(user.id,blacklist_type)).fetchone() + if find: + (id,blacklist_value) = find + if blacklist_type == 'length_limit': + limit = int(blacklist_value) + msg_len = len(msg) + if limit and msg_len > limit: + logger.info(f'block_list_check refuse send. blacklist_type: {blacklist_type}, limit: {limit}, msg_len: {msg_len}') + return True + return False + + +def get_event_chat_username(event_chat): + ''' + 获取群组/频道的单个用户名 + 2023-05-25 发现群组存在多用户名的情况,只在usernames属性中有值 + ''' + + if hasattr(event_chat,'username') and event_chat.username: + return event_chat.username + + if hasattr(event_chat,'usernames') and event_chat.usernames: + standby_username = ''# 备选用户名 + for i in event_chat.usernames: + if i.active and not i.editable and i.username:# 激活的用户名且不可编辑.优先读取 + return i.username + if i.active and i.username:# 激活的用户名且不可编辑.备选读取 + standby_username = i.username + + if standby_username: + return standby_username + + return None + + +def get_event_chat_username_list(event_chat): + ''' + 获取群组/频道的所有用户名列表 + ''' + result = [] + if hasattr(event_chat,'username') and event_chat.username: + result.append(event_chat.username) + + if hasattr(event_chat,'usernames') and event_chat.usernames: + for i in event_chat.usernames: + if i.active and i.username:# 激活的用户名 + result.append(i.username) + + return list(set(result)) + + +