Compare commits
No commits in common. "bb5f4d3154518df6a2310427ac1a5bd51274fbe9" and "8278ec5893d83c1df024d7f73f32cdc7dfaa5c58" have entirely different histories.
bb5f4d3154
...
8278ec5893
11 changed files with 16 additions and 313 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
||||||
.idea/
|
.idea/
|
||||||
.venv/
|
.venv/
|
||||||
secrets/
|
|
||||||
message_stats.json
|
message_stats.json
|
||||||
config.yaml
|
config.yaml
|
|
@ -10,14 +10,10 @@ from aiogram.filters import CommandStart, Command
|
||||||
from aiogram.client.session.aiohttp import AiohttpSession
|
from aiogram.client.session.aiohttp import AiohttpSession
|
||||||
from aiogram import F
|
from aiogram import F
|
||||||
|
|
||||||
from core.post_to_fedi import router as fedi_router
|
|
||||||
|
|
||||||
from core.bitflip import handle_bitflip_command
|
from core.bitflip import handle_bitflip_command
|
||||||
from core.link import handle_links
|
from core.link import handle_links
|
||||||
from core.post_to_fedi import handle_auth, handle_post_to_fedi
|
|
||||||
from core.promote import handle_promote_command
|
from core.promote import handle_promote_command
|
||||||
from core.repeater import MessageRepeater
|
from core.repeater import MessageRepeater
|
||||||
from core.report_links import report_broken_links
|
|
||||||
from core.simple import handle_start_command, handle_baka, dummy_handler, handle_info_command
|
from core.simple import handle_start_command, handle_baka, dummy_handler, handle_info_command
|
||||||
from core.actions import handle_actions, handle_reverse_actions
|
from core.actions import handle_actions, handle_reverse_actions
|
||||||
from core.stats import handle_stats_command
|
from core.stats import handle_stats_command
|
||||||
|
@ -48,30 +44,25 @@ class TelegramAdapter:
|
||||||
router.message(Command('t'))(handle_promote_command)
|
router.message(Command('t'))(handle_promote_command)
|
||||||
# stats 模块
|
# stats 模块
|
||||||
router.message(Command('stats'))(handle_stats_command)
|
router.message(Command('stats'))(handle_stats_command)
|
||||||
# fedi 模块
|
|
||||||
router.message(Command('fauth'))(handle_auth)
|
|
||||||
router.message(Command('post'))(handle_post_to_fedi)
|
|
||||||
# unpin 模块
|
# unpin 模块
|
||||||
# 不知道为什么检测不到频道的消息被置顶这个事件,暂时认为所有的频道消息都是被置顶的
|
# 不知道为什么检测不到频道的消息被置顶这个事件,暂时认为所有的频道消息都是被置顶的
|
||||||
router.message(F.chat.type.in_({'group', 'supergroup'}) & F.sender_chat & (
|
router.message(F.chat.type.in_({'group', 'supergroup'}) & F.sender_chat & (
|
||||||
F.sender_chat.type == 'channel') & F.is_automatic_forward)(
|
F.sender_chat.type == 'channel') & F.is_automatic_forward)(
|
||||||
handle_unpin_channel_message)
|
handle_unpin_channel_message)
|
||||||
# link 模块
|
|
||||||
router.message(Command('report_broken_links'))(report_broken_links)
|
|
||||||
router.message(F.text.contains('http') & ~F.text.contains('/report_broken_links'))(handle_links)
|
|
||||||
# repeater 模块
|
|
||||||
router.message(F.chat.type.in_({'group', 'supergroup'}))(MessageRepeater().handle_message)
|
|
||||||
# actions 模块
|
# actions 模块
|
||||||
router.message(F.text.startswith('/'))(handle_actions)
|
router.message(F.text.startswith('/'))(handle_actions)
|
||||||
router.message(F.text.startswith('\\'))(handle_reverse_actions)
|
router.message(F.text.startswith('\\'))(handle_reverse_actions)
|
||||||
router.message(F.text == '我是笨蛋')(handle_baka)
|
router.message(F.text == '我是笨蛋')(handle_baka)
|
||||||
|
# link 模块
|
||||||
|
router.message(F.text.contains('http'))(handle_links)
|
||||||
|
# repeater 模块
|
||||||
|
router.message(F.chat.type.in_({'group', 'supergroup'}))(MessageRepeater().handle_message)
|
||||||
|
|
||||||
# 捕获所有其他消息
|
# 捕获所有其他消息
|
||||||
router.message(F.chat.type.in_({'group', 'supergroup'}))(dummy_handler)
|
router.message(F.chat.type.in_({'group', 'supergroup'}))(dummy_handler)
|
||||||
|
|
||||||
# Include router in dispatcher
|
# Include router in dispatcher
|
||||||
self.dp.include_router(router)
|
self.dp.include_router(router)
|
||||||
# 处理联邦宇宙认证相关
|
|
||||||
self.dp.include_router(fedi_router)
|
|
||||||
|
|
||||||
def _setup_middleware(self):
|
def _setup_middleware(self):
|
||||||
"""注册中间件"""
|
"""注册中间件"""
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
# 管理员对应的 Telegram 用户 ID
|
# 管理员对应的 Telegram 用户 ID
|
||||||
# 你可以通过 /info 命令获取你的用户 ID
|
# 你可以通过 /info 命令获取你的用户 ID
|
||||||
admin: 123456789
|
admin: 123456789
|
||||||
# 开发者对应的 Telegram 用户 ID
|
|
||||||
# 如果你希望 fork 这个项目并进行开发,你可以在这里更改成你的用户 ID,用户不推荐更改这部分
|
|
||||||
# 这部分仅限于汇报链接跟踪参数去除的问题
|
|
||||||
dev: 616760897
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# global features settings
|
# global features settings
|
||||||
features:
|
features:
|
||||||
|
@ -16,9 +10,6 @@ features:
|
||||||
# 启用 1/0 翻转
|
# 启用 1/0 翻转
|
||||||
bitflip:
|
bitflip:
|
||||||
enable: true
|
enable: true
|
||||||
# 启用转发到联邦宇宙
|
|
||||||
fedi:
|
|
||||||
enable: true
|
|
||||||
# 启用链接解析/清理
|
# 启用链接解析/清理
|
||||||
link:
|
link:
|
||||||
enable: true
|
enable: true
|
||||||
|
|
|
@ -23,10 +23,6 @@ class Config:
|
||||||
"""Get admin user ID"""
|
"""Get admin user ID"""
|
||||||
return self.config_data.get('admin')
|
return self.config_data.get('admin')
|
||||||
|
|
||||||
def get_developer_id(self) -> Optional[int]:
|
|
||||||
"""Get developer user ID"""
|
|
||||||
return self.config_data.get('dev')
|
|
||||||
|
|
||||||
def is_feature_enabled(self, feature_name: str, chat_id: Optional[int] = None) -> bool:
|
def is_feature_enabled(self, feature_name: str, chat_id: Optional[int] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if a feature is enabled for a specific chat or globally
|
Check if a feature is enabled for a specific chat or globally
|
||||||
|
|
|
@ -6,8 +6,6 @@ from config import config
|
||||||
async def handle_actions(message: Message) -> None:
|
async def handle_actions(message: Message) -> None:
|
||||||
if not config.is_feature_enabled('actions', message.chat.id):
|
if not config.is_feature_enabled('actions', message.chat.id):
|
||||||
return
|
return
|
||||||
if not message.chat.type in ['group', 'supergroup']:
|
|
||||||
return
|
|
||||||
rawtext = message.text
|
rawtext = message.text
|
||||||
from_user = message.from_user.mention_html(message.sender_chat.title) if message.sender_chat else message.from_user.mention_html()
|
from_user = message.from_user.mention_html(message.sender_chat.title) if message.sender_chat else message.from_user.mention_html()
|
||||||
replied_user = message.reply_to_message.from_user.mention_html(message.reply_to_message.sender_chat.title) if message.reply_to_message and message.reply_to_message.sender_chat else message.reply_to_message.from_user.mention_html()
|
replied_user = message.reply_to_message.from_user.mention_html(message.reply_to_message.sender_chat.title) if message.reply_to_message and message.reply_to_message.sender_chat else message.reply_to_message.from_user.mention_html()
|
||||||
|
|
32
core/link.py
32
core/link.py
|
@ -1,4 +1,5 @@
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import requests
|
||||||
import re
|
import re
|
||||||
import html
|
import html
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -43,6 +44,7 @@ async def extend_short_urls(url):
|
||||||
""" 扩展短链接 """
|
""" 扩展短链接 """
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url,allow_redirects=False) as r:
|
async with session.get(url,allow_redirects=False) as r:
|
||||||
|
|
||||||
if 'tb.cn' in urlparse(url).hostname:
|
if 'tb.cn' in urlparse(url).hostname:
|
||||||
# 淘宝短链接特殊处理
|
# 淘宝短链接特殊处理
|
||||||
html_content = await r.text()
|
html_content = await r.text()
|
||||||
|
@ -50,7 +52,7 @@ async def extend_short_urls(url):
|
||||||
if not url:
|
if not url:
|
||||||
return url
|
return url
|
||||||
if r.status in [301, 302, 304, 307, 308] and 'Location' in r.headers:
|
if r.status in [301, 302, 304, 307, 308] and 'Location' in r.headers:
|
||||||
if r.headers['Location'].startswith(('http://', 'https://')):
|
if 'http' in r.headers['Location']:
|
||||||
return r.headers['Location']
|
return r.headers['Location']
|
||||||
else:
|
else:
|
||||||
# 如果 Location 头部没有 http 前缀,可能是相对路径
|
# 如果 Location 头部没有 http 前缀,可能是相对路径
|
||||||
|
@ -129,7 +131,7 @@ def reserve_whitelisted_params(url):
|
||||||
# 重新构建URL
|
# 重新构建URL
|
||||||
cleaned_query = urlencode(new_query_params, doseq=True)
|
cleaned_query = urlencode(new_query_params, doseq=True)
|
||||||
return urlunparse(parsed_url._replace(query=cleaned_query))
|
return urlunparse(parsed_url._replace(query=cleaned_query))
|
||||||
elif parsed_url.hostname in ['www.iesdouyin.com','www.bilibili.com','m.bilibili.com','bilibili.com','mall.bilibili.com','space.bilibili.com','live.bilibili.com']:
|
elif parsed_url.hostname in ['www.bilibili.com','m.bilibili.com','bilibili.com','mall.bilibili.com','space.bilibili.com','live.bilibili.com']:
|
||||||
# 不保留任何参数
|
# 不保留任何参数
|
||||||
new_query_params = {}
|
new_query_params = {}
|
||||||
# 重新构建URL
|
# 重新构建URL
|
||||||
|
@ -147,33 +149,21 @@ def transform_into_fixed_url(url):
|
||||||
if parsed_url.hostname in ['bilibili.com', 'm.bilibili.com']:
|
if parsed_url.hostname in ['bilibili.com', 'm.bilibili.com']:
|
||||||
# 把 bilibili 的链接转换为桌面端的 www.bilibili.com
|
# 把 bilibili 的链接转换为桌面端的 www.bilibili.com
|
||||||
return urlunparse(parsed_url._replace(netloc='www.bilibili.com'))
|
return urlunparse(parsed_url._replace(netloc='www.bilibili.com'))
|
||||||
if parsed_url.hostname in ['www.iesdouyin.com']:
|
|
||||||
# 把抖音分享链接转换为正常的 www.douyin.com
|
|
||||||
return urlunparse(parsed_url._replace(netloc='www.douyin.com'))
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
async def process_url(url):
|
async def process_url(url):
|
||||||
# 对于适配的网站,直接保留白名单参数并返回
|
# 首先清理跟踪参数
|
||||||
if urlparse(url).hostname in ['www.iesdouyin.com','item.taobao.com', 'detail.tmall.com', 'h5.m.goofish.com', 'music.163.com',
|
|
||||||
'www.bilibili.com', 'm.bilibili.com', 'bilibili.com', 'mall.bilibili.com',
|
|
||||||
'space.bilibili.com', 'live.bilibili.com']:
|
|
||||||
final_url = reserve_whitelisted_params(url)
|
|
||||||
if urlparse(final_url).hostname in ['www.iesdouyin.com','bilibili.com', 'm.bilibili.com']:
|
|
||||||
final_url = transform_into_fixed_url(final_url)
|
|
||||||
return final_url
|
|
||||||
# 对于其它的网站,首先清理跟踪参数
|
|
||||||
cleaned_url = remove_tracking_params(url)
|
cleaned_url = remove_tracking_params(url)
|
||||||
# 扩展短链接
|
# 扩展短链接
|
||||||
extended_url = await extend_short_urls(cleaned_url)
|
extended_url = await extend_short_urls(cleaned_url)
|
||||||
# 对于扩展短链接之后的适配的网站,直接保留白名单参数并返回
|
# 对于一些网站,只保留白名单中的参数
|
||||||
if urlparse(extended_url).hostname in ['www.iesdouyin.com','item.taobao.com', 'detail.tmall.com', 'h5.m.goofish.com', 'music.163.com',
|
if urlparse(extended_url).hostname in ['item.taobao.com', 'detail.tmall.com', 'h5.m.goofish.com', 'music.163.com',
|
||||||
'www.bilibili.com', 'm.bilibili.com', 'bilibili.com', 'mall.bilibili.com',
|
'www.bilibili.com', 'm.bilibili.com', 'bilibili.com', 'mall.bilibili.com',
|
||||||
'space.bilibili.com', 'live.bilibili.com']:
|
'space.bilibili.com', 'live.bilibili.com']:
|
||||||
final_url = reserve_whitelisted_params(extended_url)
|
final_url = reserve_whitelisted_params(extended_url)
|
||||||
if urlparse(final_url).hostname in ['www.iesdouyin.com','bilibili.com', 'm.bilibili.com']:
|
if urlparse(extended_url).hostname in ['bilibili.com', 'm.bilibili.com']:
|
||||||
final_url = transform_into_fixed_url(final_url)
|
final_url = transform_into_fixed_url(final_url)
|
||||||
return final_url
|
elif urlparse(extended_url).hostname in ['x.com', 'twitter.com']:
|
||||||
if urlparse(extended_url).hostname in ['x.com', 'twitter.com']:
|
|
||||||
# 对于 Twitter 链接,转换为 fixupx.com
|
# 对于 Twitter 链接,转换为 fixupx.com
|
||||||
removed_tracking_url = remove_tracking_params(extended_url)
|
removed_tracking_url = remove_tracking_params(extended_url)
|
||||||
final_url = transform_into_fixed_url(removed_tracking_url)
|
final_url = transform_into_fixed_url(removed_tracking_url)
|
||||||
|
@ -202,6 +192,4 @@ async def handle_links(message: Message):
|
||||||
|
|
||||||
# 回复处理后的链接
|
# 回复处理后的链接
|
||||||
if final_urls:
|
if final_urls:
|
||||||
await message.reply(f"{"\n".join(final_urls)}\n消息里有包含跟踪参数的链接,已经帮你转换了哦~\n\n注意:"
|
await message.reply(f"{"\n".join(final_urls)}\n消息里有包含跟踪参数的链接,已经帮你转换了哦")
|
||||||
f"这个功能是试验性的,可能会出现链接无法访问等问题,如果出现链接没有清理干净的情况,"
|
|
||||||
f"可以将返回的结果再次发送给bot,或者尝试手动清理。\n如果我没有回复链接,说明链接不需要被清理\n如果你找到了这个工具的问题,欢迎把它通过 `/report_broken_links 链接 需要去除的参数等等` 报告给开发者!")
|
|
|
@ -1,147 +0,0 @@
|
||||||
from aiogram.types import Message
|
|
||||||
from aiogram.fsm.context import FSMContext
|
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
|
||||||
from aiogram import Router
|
|
||||||
|
|
||||||
from config import config
|
|
||||||
from mastodon import Mastodon
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
class AuthStates(StatesGroup):
|
|
||||||
waiting_for_token = State()
|
|
||||||
|
|
||||||
def check_client_cred_exists(instance: str) -> bool:
|
|
||||||
"""
|
|
||||||
检查实例的凭据文件是否存在
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with open(f'secrets/realbot_{instance}_clientcred.secret', 'r'):
|
|
||||||
return True
|
|
||||||
except FileNotFoundError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def check_user_cred_exists(instance: str, userid: int) -> bool:
|
|
||||||
"""
|
|
||||||
检查用户凭据文件是否存在
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with open(f'secrets/realbot_{instance}_{userid}_usercred.secret', 'r'):
|
|
||||||
return True
|
|
||||||
except FileNotFoundError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def handle_token(instance,mastodon, message: Message):
|
|
||||||
mastodon.log_in(
|
|
||||||
code=message.text,
|
|
||||||
to_file=f"secrets/realbot_{instance}_{message.from_user.id}_usercred.secret"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def handle_auth(message: Message, state: FSMContext):
|
|
||||||
"""
|
|
||||||
处理身份验证
|
|
||||||
"""
|
|
||||||
if not config.is_feature_enabled('fedi', message.chat.id):
|
|
||||||
return
|
|
||||||
if not message.chat.type == 'private':
|
|
||||||
await message.reply('请在私聊中使用此命令')
|
|
||||||
return
|
|
||||||
instance = message.text.replace('/fauth', '').strip()
|
|
||||||
if instance == '':
|
|
||||||
await message.reply('请输入实例域名,例如:`example.com`')
|
|
||||||
return
|
|
||||||
if not check_client_cred_exists(instance):
|
|
||||||
Mastodon.create_app(
|
|
||||||
'realbot',
|
|
||||||
api_base_url='https://{}'.format(instance),
|
|
||||||
to_file='secrets/realbot_{}_clientcred.secret'.format(instance),
|
|
||||||
scopes=['read:accounts', 'read:statuses','write:media','write:statuses']
|
|
||||||
)
|
|
||||||
mastodon = Mastodon(client_id=f'secrets/realbot_{instance}_clientcred.secret', )
|
|
||||||
auth_url = mastodon.auth_request_url()
|
|
||||||
await message.reply('请在浏览器中打开链接进行身份验证:\n{}\n验证完成后,请用得到的 token 回复这条消息'.format(auth_url))
|
|
||||||
# 在发送消息后设置状态
|
|
||||||
await state.update_data(instance=instance)
|
|
||||||
await state.set_state(AuthStates.waiting_for_token)
|
|
||||||
|
|
||||||
# 创建处理回复的 handler
|
|
||||||
@router.message(AuthStates.waiting_for_token)
|
|
||||||
async def handle_token_reply(message: Message, state: FSMContext):
|
|
||||||
data = await state.get_data()
|
|
||||||
instance = data.get('instance')
|
|
||||||
mastodon = Mastodon(client_id=f'secrets/realbot_{instance}_clientcred.secret')
|
|
||||||
status = await message.reply('正在处理身份验证,请稍候...')
|
|
||||||
await handle_token(instance,mastodon,message)
|
|
||||||
await status.edit_text('身份验证成功!\n现在你可以使用 /post 命令将消息发布到联邦网络。')
|
|
||||||
# 清除状态
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
async def handle_post_to_fedi(message: Message):
|
|
||||||
"""
|
|
||||||
处理发布到联邦网络的消息
|
|
||||||
"""
|
|
||||||
if not config.is_feature_enabled('fedi', message.chat.id):
|
|
||||||
return
|
|
||||||
if not message.reply_to_message:
|
|
||||||
await message.reply('请回复要发布的消息')
|
|
||||||
return
|
|
||||||
|
|
||||||
user_id = message.from_user.id
|
|
||||||
|
|
||||||
# 查找用户绑定的实例
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
|
|
||||||
user_cred_pattern = f'secrets/realbot_*_{user_id}_usercred.secret'
|
|
||||||
user_cred_files = glob.glob(user_cred_pattern)
|
|
||||||
|
|
||||||
if not user_cred_files:
|
|
||||||
await message.reply('请先使用 /fauth 命令进行身份验证')
|
|
||||||
return
|
|
||||||
|
|
||||||
# 从文件名中提取实例名
|
|
||||||
cred_file = user_cred_files[0] # 假设用户只绑定一个实例
|
|
||||||
filename = os.path.basename(cred_file)
|
|
||||||
# 格式: realbot_{instance}_{userid}_usercred.secret
|
|
||||||
parts = filename.split('_')
|
|
||||||
instance = '_'.join(parts[1:-2]) # 提取实例名部分
|
|
||||||
|
|
||||||
mastodon = Mastodon(
|
|
||||||
access_token=f'{cred_file}',
|
|
||||||
api_base_url=f'https://{instance}'
|
|
||||||
)
|
|
||||||
|
|
||||||
# 发布消息到联邦网络
|
|
||||||
try:
|
|
||||||
arguments = message.text.replace('/post', '', 1).strip().split(' ')
|
|
||||||
if len(arguments) >= 1 and arguments[0]:
|
|
||||||
visibility = arguments[0] # 默认可见性为账号设置的可见性
|
|
||||||
else:
|
|
||||||
visibility = None
|
|
||||||
|
|
||||||
status_message = await message.reply('尝试发布消息到联邦网络...')
|
|
||||||
# 处理图片附件
|
|
||||||
media_ids = []
|
|
||||||
if message.reply_to_message.photo:
|
|
||||||
await status_message.edit_text('正在处理图片附件...')
|
|
||||||
# 获取最大尺寸的图片
|
|
||||||
photo = message.reply_to_message.photo[-1]
|
|
||||||
file_info = await message.bot.get_file(photo.file_id)
|
|
||||||
file_data = await message.bot.download_file(file_info.file_path)
|
|
||||||
|
|
||||||
# 上传图片到Mastodon
|
|
||||||
media = mastodon.media_post(file_data, mime_type='image/png')
|
|
||||||
media_ids.append(media['id'])
|
|
||||||
text = message.reply_to_message.text
|
|
||||||
if media_ids:
|
|
||||||
text = message.reply_to_message.caption
|
|
||||||
# 发布消息
|
|
||||||
status = mastodon.status_post(
|
|
||||||
text,
|
|
||||||
media_ids=media_ids if media_ids else None,
|
|
||||||
visibility = visibility
|
|
||||||
)
|
|
||||||
status_url = status['url']
|
|
||||||
await status_message.edit_text(f'消息已成功发布到联邦网络!\n{status_url}')
|
|
||||||
except Exception as e:
|
|
||||||
await message.reply(f'发布失败: {str(e)}')
|
|
|
@ -16,14 +16,11 @@ class MessageRepeater:
|
||||||
async def handle_message(self, message: aiogram.types.Message):
|
async def handle_message(self, message: aiogram.types.Message):
|
||||||
"""Handle incoming messages and repeat when a threshold is met"""
|
"""Handle incoming messages and repeat when a threshold is met"""
|
||||||
chat_id = message.chat.id
|
chat_id = message.chat.id
|
||||||
|
content = ''
|
||||||
if message.text:
|
if message.text:
|
||||||
content = message.text
|
content = message.text
|
||||||
elif message.sticker:
|
elif message.sticker:
|
||||||
content = message.sticker.file_id
|
content = message.sticker.file_id
|
||||||
elif message.photo:
|
|
||||||
content = message.photo[0].file_id
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not config.is_feature_enabled('repeater', message.chat.id):
|
if not config.is_feature_enabled('repeater', message.chat.id):
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import re
|
|
||||||
|
|
||||||
from aiogram.types import Message
|
|
||||||
|
|
||||||
from config import config
|
|
||||||
|
|
||||||
|
|
||||||
async def report_broken_links(message: Message):
|
|
||||||
if not config.is_feature_enabled('link', message.chat.id):
|
|
||||||
return
|
|
||||||
# 获取被回复的消息中的链接
|
|
||||||
links = []
|
|
||||||
# 链接正则表达式
|
|
||||||
url_pattern = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
|
|
||||||
text = message.text or message.caption
|
|
||||||
# Extract URLs from message text
|
|
||||||
if text:
|
|
||||||
links = re.findall(url_pattern, text)
|
|
||||||
|
|
||||||
if not links:
|
|
||||||
await message.reply("没有找到链接。请提供链接以及希望得到的清理结果。格式最好是 `/report_broken_links 链接 描述文本`。")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 处理报告逻辑(例如,保存到数据库或发送给开发者)
|
|
||||||
report_content = f"用户 {message.from_user.full_name} ({message.from_user.id}) 报告了以下链接的问题:\n"
|
|
||||||
report_content += "\n".join(links) + "\n"
|
|
||||||
report_content += f"描述:{' '.join(text.split(' ')[2:]) if ' ' in text and len(text.split(' ')) >= 3 else ''.join(text.split(' ')[1:])}\n"
|
|
||||||
|
|
||||||
# 将 report_content 发送到开发者
|
|
||||||
developer_id = config.get_developer_id() # 从配置获取开发者ID
|
|
||||||
await message.bot.send_message(chat_id=developer_id, text=report_content)
|
|
||||||
|
|
||||||
await message.reply("感谢您的报告,我们会尽快处理!")
|
|
|
@ -6,7 +6,6 @@ requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiodns==3.5.0",
|
"aiodns==3.5.0",
|
||||||
"aiogram==3.21.0",
|
"aiogram==3.21.0",
|
||||||
"mastodon-py==2.0.1",
|
|
||||||
"matrix-nio==0.25.2",
|
"matrix-nio==0.25.2",
|
||||||
"python-abp==0.2.0",
|
"python-abp==0.2.0",
|
||||||
"pyyaml>=6.0.2",
|
"pyyaml>=6.0.2",
|
||||||
|
|
76
uv.lock
generated
76
uv.lock
generated
|
@ -126,15 +126,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
|
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "blurhash"
|
|
||||||
version = "1.1.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/02/96/363eae896ec6a699dfc63f19f9b857c09294fe4d791198f002baa495fc4e/blurhash-1.1.4.tar.gz", hash = "sha256:da56b163e5a816e4ad07172f5639287698e09d7f3dc38d18d9726d9c1dbc4cee", size = 4738, upload-time = "2019-10-11T19:02:28.868Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/08/163cb166a2464223a5eb8890a92cc1cf7e8c7c79a2c75e497e3d8f3a4711/blurhash-1.1.4-py2.py3-none-any.whl", hash = "sha256:7611c1bc41383d2349b6129208587b5d61e8792ce953893cb49c38beeb400d1d", size = 5307, upload-time = "2019-10-11T19:02:27.484Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2025.7.14"
|
version = "2025.7.14"
|
||||||
|
@ -188,15 +179,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "decorator"
|
|
||||||
version = "5.2.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "frozenlist"
|
name = "frozenlist"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
|
@ -325,23 +307,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/75/f620449f0056eff0ec7c1b1e088f71068eb4e47a46eb54f6c065c6ad7675/magic_filter-1.0.12-py3-none-any.whl", hash = "sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6", size = 11335, upload-time = "2023-10-01T12:33:17.711Z" },
|
{ url = "https://files.pythonhosted.org/packages/cc/75/f620449f0056eff0ec7c1b1e088f71068eb4e47a46eb54f6c065c6ad7675/magic_filter-1.0.12-py3-none-any.whl", hash = "sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6", size = 11335, upload-time = "2023-10-01T12:33:17.711Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mastodon-py"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "blurhash" },
|
|
||||||
{ name = "decorator" },
|
|
||||||
{ name = "python-dateutil" },
|
|
||||||
{ name = "python-magic", marker = "sys_platform != 'win32'" },
|
|
||||||
{ name = "python-magic-bin", marker = "sys_platform == 'win32'" },
|
|
||||||
{ name = "requests" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/40/8f962b2d1782fd097ea45a344094df7d56f980f1d64c62de0bfa1ef5b0fc/mastodon_py-2.0.1.tar.gz", hash = "sha256:f0a9cf59071347c7ff2ee49487d2520ca661f349f369b68845bdf3e43db1fff3", size = 10988936, upload-time = "2025-03-02T09:46:27.279Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/d7/632b8b14a13eb33a9fcc5c3354f878fa39dfb5f51ef6283b24fce84c5796/mastodon_py-2.0.1-py3-none-any.whl", hash = "sha256:5bb543ecbd7526bd50675d5d617ec04caa6b10d4002454cbf5641ad612723455", size = 108485, upload-time = "2025-03-02T09:46:22.308Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matrix-nio"
|
name = "matrix-nio"
|
||||||
version = "0.25.2"
|
version = "0.25.2"
|
||||||
|
@ -561,36 +526,6 @@ version = "0.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/30/31c6e4ca48992ee5f4bb8325f249f944ac493898606ca83a7642ff5ee18b/python-abp-0.2.0.tar.gz", hash = "sha256:f36d0e9fdc089587c26036e0403f36d729395fc9f4dbce45baf3a493d1de8112", size = 80013, upload-time = "2020-05-20T13:09:55.536Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/3b/30/31c6e4ca48992ee5f4bb8325f249f944ac493898606ca83a7642ff5ee18b/python-abp-0.2.0.tar.gz", hash = "sha256:f36d0e9fdc089587c26036e0403f36d729395fc9f4dbce45baf3a493d1de8112", size = 80013, upload-time = "2020-05-20T13:09:55.536Z" }
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-dateutil"
|
|
||||||
version = "2.9.0.post0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "six" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-magic"
|
|
||||||
version = "0.4.27"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-magic-bin"
|
|
||||||
version = "0.4.14"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/5d/10b9ac745d9fd2f7151a2ab901e6bb6983dbd70e87c71111f54859d1ca2e/python_magic_bin-0.4.14-py2.py3-none-win32.whl", hash = "sha256:34a788c03adde7608028203e2dbb208f1f62225ad91518787ae26d603ae68892", size = 397784, upload-time = "2017-10-02T16:30:15.806Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/c2/094e3d62b906d952537196603a23aec4bcd7c6126bf80eb14e6f9f4be3a2/python_magic_bin-0.4.14-py2.py3-none-win_amd64.whl", hash = "sha256:90be6206ad31071a36065a2fc169c5afb5e0355cbe6030e87641c6c62edc2b69", size = 409299, upload-time = "2017-10-02T16:30:18.545Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-socks"
|
name = "python-socks"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
|
@ -624,7 +559,6 @@ source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiodns" },
|
{ name = "aiodns" },
|
||||||
{ name = "aiogram" },
|
{ name = "aiogram" },
|
||||||
{ name = "mastodon-py" },
|
|
||||||
{ name = "matrix-nio" },
|
{ name = "matrix-nio" },
|
||||||
{ name = "python-abp" },
|
{ name = "python-abp" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
|
@ -635,7 +569,6 @@ dependencies = [
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aiodns", specifier = "==3.5.0" },
|
{ name = "aiodns", specifier = "==3.5.0" },
|
||||||
{ name = "aiogram", specifier = "==3.21.0" },
|
{ name = "aiogram", specifier = "==3.21.0" },
|
||||||
{ name = "mastodon-py", specifier = "==2.0.1" },
|
|
||||||
{ name = "matrix-nio", specifier = "==0.25.2" },
|
{ name = "matrix-nio", specifier = "==0.25.2" },
|
||||||
{ name = "python-abp", specifier = "==0.2.0" },
|
{ name = "python-abp", specifier = "==0.2.0" },
|
||||||
{ name = "pyyaml", specifier = ">=6.0.2" },
|
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||||
|
@ -732,15 +665,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" },
|
{ url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "six"
|
|
||||||
version = "1.17.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.14.1"
|
version = "4.14.1"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue