Skip to content

Commit

Permalink
fix: Compatible with multiple username groups
Browse files Browse the repository at this point in the history
  • Loading branch information
Hootrix authored Nov 19, 2023
1 parent d589fe3 commit 219c284
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 21 deletions.
27 changes: 25 additions & 2 deletions db/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
'db',
'User',
'User_subscribe_list',
'User_block_list',
]

_current_path = os.path.dirname(os.path.realpath(__file__))
Expand Down Expand Up @@ -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:
Expand Down
108 changes: 90 additions & 18 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -201,25 +201,29 @@ 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
from user_subscribe_list as l
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:
Expand All @@ -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):# 编辑事件
Expand All @@ -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()结果
Expand All @@ -261,29 +266,40 @@ 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:
# 已发送该消息
logger.debug(f'REGEX send repeat. rule_name:{config["msg_unique_rule"]} {CACHE_KEY_UNIQUE_SEND}:{channel_msg_url}')
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:
# 已发送该消息
Expand All @@ -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相关操作
Expand Down Expand Up @@ -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}')
Expand Down Expand Up @@ -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('''
Expand Down
73 changes: 72 additions & 1 deletion utils/common.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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
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))



0 comments on commit 219c284

Please sign in to comment.