Archived
1
0
Fork 0

Merge pull request #1011 from Teahouse-Studios/dev/NextsTep

Dev/NextsTep
This commit is contained in:
yzhh 2023-11-06 21:13:31 +08:00 committed by GitHub
commit 8c662631e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 832 additions and 13 deletions

3
bot.py
View file

@ -15,7 +15,8 @@ from database import BotDBUtil, session, DBVersion
encode = 'UTF-8'
bots_required_configs = {'aiocqhttp': ['qq_host', 'qq_account'], 'discord': ['dc_token'], 'aiogram': ['tg_token'],
'kook': ['kook_token'], 'matrix': ['matrix_homeserver', 'matrix_user', 'matrix_token'], }
'kook': ['kook_token'], 'matrix': ['matrix_homeserver', 'matrix_user', 'matrix_token'],
'lagrange': ['lagrange_host']}
class RestartBot(Exception):

View file

@ -6,12 +6,14 @@ import sys
import ujson as json
from aiocqhttp import Event
from datetime import datetime
from bots.aiocqhttp.client import bot
from bots.aiocqhttp.info import client_name
from bots.aiocqhttp.message import MessageSession, FetchTarget
from config import Config
from core.builtins import EnableDirtyWordCheck, PrivateAssets, Url
from core.builtins import EnableDirtyWordCheck, PrivateAssets, Url, Temp
from core.scheduler import Scheduler, IntervalTrigger
from core.parser.message import parser
from core.types import MsgInfo, Session
from core.utils.bot import load_prompt, init_async
@ -25,6 +27,12 @@ qq_account = str(Config("qq_account"))
enable_listening_self_message = Config("qq_enable_listening_self_message")
@Scheduler.scheduled_job(IntervalTrigger(seconds=20))
async def check_lagrange_alive():
if 'lagrange_keepalive' in Temp.data:
Temp.data['lagrange_status'] = (datetime.now().timestamp() - Temp.data['lagrange_keepalive']) < 20
@bot.on_startup
async def startup():
await init_async()

View file

@ -20,6 +20,7 @@ from core.builtins.message import MessageSession as MessageSessionT
from core.builtins.message.chain import MessageChain
from core.exceptions import SendMessageFailed
from core.logger import Logger
from core.queue import JobQueue
from core.types import FetchTarget as FetchTargetT, FinishedSession as FinS
from core.utils.image import msgchain2image
from core.utils.storedata import get_stored_list
@ -45,6 +46,7 @@ class FinishedSession(FinS):
last_send_typing_time = {}
Temp.data['is_group_message_blocked'] = False
Temp.data['waiting_for_send_group_message'] = []
group_info = {}
async def resending_group_message():
@ -88,10 +90,28 @@ class MessageSession(MessageSessionT):
async def send_message(self, message_chain, quote=True, disable_secret_check=False,
allow_split_image=True) -> FinishedSession:
message_chain = MessageChain(message_chain)
if self.target.target_from == 'QQ|Group' and Temp.data.get('lagrange_status', False):
lagrange_available_groups = Temp.data.get('lagrange_available_groups', [])
if self.session.target in lagrange_available_groups:
choose = random.randint(0, 1)
Logger.info(f'choose: {choose}')
if choose:
can_sends = []
for x in message_chain.value:
if isinstance(x, (Plain, Image)):
can_sends.append(x)
message_chain.value.remove(x)
if can_sends:
await JobQueue.send_message('Lagrange', self.target.target_id,
MessageChain(can_sends).to_list())
if not message_chain.value:
return
msg = MessageSegment.text('')
if quote and self.target.target_from == 'QQ|Group' and self.session.message:
msg = MessageSegment.reply(self.session.message.message_id)
message_chain = MessageChain(message_chain)
if not message_chain.is_safe and not disable_secret_check:
return await self.send_message(Plain(ErrorMessage(self.locale.t("error.message.chain.unsafe"))))
self.sent.append(message_chain)
@ -113,12 +133,21 @@ class MessageSession(MessageSessionT):
send = await bot.send_group_msg(group_id=self.session.target, message=MessageSegment.text(
self.locale.t("error.message.timeout")))
except aiocqhttp.exceptions.ActionFailed:
message_chain.insert(0, Plain(self.locale.t("error.message.limited.msg2img")))
msg2img = MessageSegment.image(Path(await msgchain2image(message_chain)).as_uri())
img_chain = message_chain.copy()
img_chain.insert(0, Plain(self.locale.t("error.message.limited.msg2img")))
msg2img = MessageSegment.image(Path(await msgchain2image(img_chain)).as_uri())
try:
send = await bot.send_group_msg(group_id=self.session.target, message=msg2img)
except aiocqhttp.exceptions.ActionFailed as e:
raise SendMessageFailed(e.result['wording'])
if self.target.target_from == 'QQ|Group' and Temp.data.get('lagrange_status', False):
lagrange_available_groups = Temp.data.get('lagrange_available_groups', [])
if self.session.target in lagrange_available_groups:
await JobQueue.send_message('Lagrange', self.target.target_id,
message_chain.to_list())
else:
raise SendMessageFailed(e.result['wording'])
else:
raise SendMessageFailed(e.result['wording'])
if Temp.data['is_group_message_blocked']:
asyncio.create_task(resending_group_message())

View file

191
bots/lagrange/bot.py Normal file
View file

@ -0,0 +1,191 @@
import logging
import os
import re
import sys
from aiocqhttp import Event
from bots.lagrange.client import bot
from bots.lagrange.info import client_name
from bots.lagrange.message import MessageSession, FetchTarget
from bots.lagrange.utils import get_plain_msg
from config import Config
from core.builtins import EnableDirtyWordCheck, PrivateAssets, Url
from core.logger import Logger
from core.parser.message import parser
from core.queue import check_job_queue
from core.scheduler import Scheduler, IntervalTrigger
from core.types import MsgInfo, Session
from core.utils.bot import load_prompt, init_async
from core.utils.info import Info
from core.queue import JobQueue
PrivateAssets.set(os.path.abspath(os.path.dirname(__file__) + '/assets'))
EnableDirtyWordCheck.status = True if Config('enable_dirty_check') else False
Url.disable_mm = False if Config('enable_urlmanager') else True
qq_account = str(Config("qq_account"))
@Scheduler.scheduled_job(IntervalTrigger(seconds=1))
async def job():
await check_job_queue()
@bot.on_startup
async def startup():
# await init_async()
Scheduler.start()
bot.logger.setLevel(logging.WARNING)
@bot.on_meta_event('heartbeat')
async def _(event: Event):
if event['status']['online']:
await JobQueue.add_job('QQ', 'lagrange_keepalive', event['status'], wait=False)
glist = await bot.call_action('get_group_list')
g = []
for i in glist:
g.append(i['group_id'])
await JobQueue.add_job('QQ', 'lagrange_available_groups', g, wait=False)
async def message_handler(event: Event):
if event.detail_type == 'private':
if event.sub_type == 'group':
if Config('qq_disable_temp_session'):
return await bot.send(event, '请先添加好友后再进行命令交互。')
message = get_plain_msg(event.message)
"""
filter_msg = re.match(r'.*?\\[CQ:(?:json|xml).*?\\].*?|.*?<\\?xml.*?>.*?', event.message, re.MULTILINE | re.DOTALL)
if filter_msg:
match_json = re.match(r'.*?\\[CQ:json,data=(.*?)\\].*?', event.message, re.MULTILINE | re.DOTALL)
if match_json:
load_json = json.loads(html.unescape(match_json.group(1)))
if load_json['app'] == 'com.tencent.multimsg':
event.message = f'[CQ:forward,id={load_json["meta"]["detail"]["resid"]}]'
else:
return
else:
return
reply_id = None
match_reply = re.match(r'^\\[CQ:reply,id=(.*?)].*', event.message)
if match_reply:
reply_id = int(match_reply.group(1))
prefix = None
if match_at := re.match(r'^\\[CQ:at,qq=(.*?)](.*)', event.message):
if match_at.group(1) == qq_account:
event.message = match_at.group(2)
if event.message in ['', ' ']:
event.message = 'help'
prefix = ['']"""
if event.detail_type == 'group':
if event.group_id not in Config('lagrange_avaliable_groups'):
return
target_id = 'QQ|' + (f'Group|{str(event.group_id)}' if event.detail_type == 'group' else str(event.user_id))
msg = MessageSession(MsgInfo(target_id=target_id,
sender_id=f'QQ|{str(event.user_id)}',
target_from='QQ|Group' if event.detail_type == 'group' else 'QQ',
sender_from='QQ', sender_name=event.sender['nickname'], client_name=client_name,
message_id=event.message_id,
reply_id=None),
Session(message=message,
target=event.group_id if event.detail_type == 'group' else event.user_id,
sender=event.user_id))
await parser(msg, running_mention=True, prefix=['N~'])
"""@bot.on_message('group', 'private')
async def _(event: Event):
await message_handler(event)"""
"""
if enable_listening_self_message:
@bot.on('message_sent')
async def _(event: Event):
await message_handler(event)
class GuildAccountInfo:
tiny_id = None
@bot.on_message('guild')
async def _(event):
if GuildAccountInfo.tiny_id is None:
profile = await bot.call_action('get_guild_service_profile')
GuildAccountInfo.tiny_id = profile['tiny_id']
tiny_id = event.user_id
if tiny_id == GuildAccountInfo.tiny_id:
return
reply_id = None
match_reply = re.match(r'^\\[CQ:reply,id=(.*?)].*', event.message)
if match_reply:
reply_id = int(match_reply.group(1))
target_id = f'QQ|Guild|{str(event.guild_id)}|{str(event.channel_id)}'
msg = MessageSession(MsgInfo(target_id=target_id,
sender_id=f'QQ|Tiny|{str(event.user_id)}',
target_from='QQ|Guild',
sender_from='QQ|Tiny', sender_name=event.sender['nickname'], client_name=client_name,
message_id=event.message_id,
reply_id=reply_id),
Session(message=event,
target=f'{str(event.guild_id)}|{str(event.channel_id)}',
sender=event.user_id))
await parser(msg, running_mention=True)
@bot.on('request.friend')
async def _(event: Event):
if BotDBUtil.SenderInfo('QQ|' + str(event.user_id)).query.isInBlockList:
return {'approve': False}
return {'approve': True}
@bot.on('request.group.invite')
async def _(event: Event):
if BotDBUtil.SenderInfo('QQ|' + str(event.user_id)).query.isSuperUser:
return {'approve': True}
if not Config('allow_bot_auto_approve_group_invite'):
await bot.send_private_msg(user_id=event.user_id,
message='你好!本机器人暂时不主动同意入群请求。\n'
f'请至{Config("qq_join_group_application_link")}申请入群。')
else:
return {'approve': True}
@bot.on_notice('group_ban')
async def _(event: Event):
if event.user_id == int(qq_account):
result = BotDBUtil.UnfriendlyActions(target_id=event.group_id,
sender_id=event.operator_id).add_and_check('mute', str(event.duration))
if event.duration >= 259200:
result = True
if result:
await bot.call_action('set_group_leave', group_id=event.group_id)
BotDBUtil.SenderInfo('QQ|' + str(event.operator_id)).edit('isInBlockList', True)
await bot.call_action('delete_friend', friend_id=event.operator_id)
@bot.on_message('group')
async def _(event: Event):
result = BotDBUtil.isGroupInAllowList(f'QQ|Group|{str(event.group_id)}')
if not result:
await bot.send(event=event, message='此群不在白名单中,已自动退群。'
'\n如需申请白名单请至https://github.com/Teahouse-Studios/bot/issues/new/choose发起issue。')
await bot.call_action('set_group_leave', group_id=event.group_id)
"""
qq_host = Config("lagrange_host")
if qq_host:
argv = sys.argv
if 'subprocess' in sys.argv:
Info.subprocess = True
host, port = qq_host.split(':')
bot.run(host=host, port=port, debug=False)

52
bots/lagrange/client.py Normal file
View file

@ -0,0 +1,52 @@
import traceback
from typing import Dict, Any, Optional
from aiocqhttp import CQHttp
from aiocqhttp.event import Event
class EventModded(Event):
@staticmethod
def from_payload(payload: Dict[str, Any]) -> 'Optional[Event]':
"""
OneBot 事件数据构造 `Event` 对象
"""
try:
e = EventModded(payload)
_ = e.type, e.detail_type
return e
except KeyError:
traceback.print_exc()
return None
@property
def detail_type(self) -> str:
"""
事件具体类型 `type` 的不同而不同 ``message`` 类型为例
``private````group````discuss``
"""
if self.type == 'message_sent':
return self['message_type']
return self[f'{self.type}_type']
class CQHttpModded(CQHttp):
async def _handle_event(self, payload: Dict[str, Any]) -> Any:
ev = EventModded.from_payload(payload)
if not ev:
return
event_name = ev.name
self.logger.info(f'received event: {event_name}')
if self._message_class and 'message' in ev:
ev['message'] = self._message_class(ev['message'])
results = list(
filter(lambda r: r is not None, await
self._bus.emit(event_name, ev)))
# return the first non-none result
return results[0] if results else None
bot = CQHttpModded()

1
bots/lagrange/info.py Normal file
View file

@ -0,0 +1 @@
client_name = 'Lagrange'

422
bots/lagrange/message.py Normal file
View file

@ -0,0 +1,422 @@
import asyncio
import datetime
import html
import random
import re
import traceback
from pathlib import Path
from typing import List, Union
import aiocqhttp.exceptions
import ujson as json
from aiocqhttp import MessageSegment
from bots.lagrange.client import bot
from bots.lagrange.info import client_name
from config import Config
from core.builtins import Bot, ErrorMessage, base_superuser_list
from core.builtins import Plain, Image, Voice, Temp, command_prefix
from core.builtins.message import MessageSession as MessageSessionT
from core.builtins.message.chain import MessageChain
from core.exceptions import SendMessageFailed
from core.logger import Logger
from core.types import FetchTarget as FetchTargetT, FinishedSession as FinS
from core.utils.image import msgchain2image
from core.utils.storedata import get_stored_list
from database import BotDBUtil
enable_analytics = Config('enable_analytics')
class FinishedSession(FinS):
async def delete(self):
"""
用于删除这条消息
"""
if self.session.target.target_from in ['QQ|Group', 'QQ']:
try:
for x in self.message_id:
if x != 0:
await bot.call_action('delete_msg', message_id=x)
except Exception:
Logger.error(traceback.format_exc())
last_send_typing_time = {}
Temp.data['is_group_message_blocked'] = False
Temp.data['waiting_for_send_group_message'] = []
async def resending_group_message():
falied_list = []
try:
if targets := Temp.data['waiting_for_send_group_message']:
for x in targets:
try:
if x['i18n']:
await x['fetch'].send_direct_message(x['fetch'].parent.locale.t(x['message'], **x['kwargs']))
else:
await x['fetch'].send_direct_message(x['message'])
Temp.data['waiting_for_send_group_message'].remove(x)
await asyncio.sleep(30)
except SendMessageFailed:
Logger.error(traceback.format_exc())
falied_list.append(x)
if len(falied_list) > 3:
raise SendMessageFailed
Temp.data['is_group_message_blocked'] = False
except SendMessageFailed:
Logger.error(traceback.format_exc())
Temp.data['is_group_message_blocked'] = True
for bu in base_superuser_list:
fetch_base_superuser = await FetchTarget.fetch_target(bu)
if fetch_base_superuser:
await fetch_base_superuser. \
send_direct_message(fetch_base_superuser.parent.locale.t("error.message.paused",
prefix=command_prefix[0]))
class MessageSession(MessageSessionT):
class Feature:
image = True
voice = False
embed = False
forward = False
delete = True
wait = True
quote = False
async def send_message(self, message_chain, quote=True, disable_secret_check=False,
allow_split_image=True) -> FinishedSession:
msg = []
"""
if quote and self.target.target_from == 'QQ|Group' and self.session.message:
msg = MessageSegment.reply(self.session.message.message_id)
"""
message_chain = MessageChain(message_chain)
if not message_chain.is_safe and not disable_secret_check:
return await self.send_message(Plain(ErrorMessage(self.locale.t("error.message.chain.unsafe"))))
self.sent.append(message_chain)
count = 0
for x in message_chain.as_sendable(locale=self.locale.locale, embed=False):
if isinstance(x, Plain):
msg.append({
"type": "text",
"data": {
"text": x.text
}
})
elif isinstance(x, Image):
msg.append({
"type": "image",
"data": {
"file": "base64://" + await x.get_base64()
}
})
count += 1
Logger.info(f'[Bot] -> [{self.target.target_id}]: {msg}')
if self.target.target_from == 'QQ|Group':
try:
send = await bot.send_group_msg(group_id=int(self.session.target), message=msg)
except aiocqhttp.exceptions.NetworkError:
send = await bot.send_group_msg(group_id=int(self.session.target), message=MessageSegment.text(
self.locale.t("error.message.timeout")))
except aiocqhttp.exceptions.ActionFailed:
message_chain.insert(0, Plain(self.locale.t("error.message.limited.msg2img")))
msg2img = MessageSegment.image(Path(await msgchain2image(message_chain)).as_uri())
try:
send = await bot.send_group_msg(group_id=int(self.session.target), message=msg2img)
except aiocqhttp.exceptions.ActionFailed as e:
raise SendMessageFailed(e.result['wording'])
if Temp.data['is_group_message_blocked']:
asyncio.create_task(resending_group_message())
elif self.target.target_from == 'QQ|Guild':
match_guild = re.match(r'(.*)\|(.*)', self.session.target)
send = await bot.call_action('send_guild_channel_msg', guild_id=int(match_guild.group(1)),
channel_id=int(match_guild.group(2)), message=msg)
else:
try:
send = await bot.send_private_msg(user_id=self.session.target, message=msg)
except aiocqhttp.exceptions.ActionFailed as e:
if self.session.message.detail_type == 'private' and self.session.message.sub_type == 'group':
return FinishedSession(self, 0, [{}])
else:
raise e
return FinishedSession(self, send['message_id'], [send])
async def check_native_permission(self):
if self.target.target_from == 'QQ':
return True
elif self.target.target_from == 'QQ|Group':
get_member_info = await bot.call_action('get_group_member_info', group_id=self.session.target,
user_id=self.session.sender)
if get_member_info['role'] in ['owner', 'admin']:
return True
elif self.target.target_from == 'QQ|Guild':
match_guild = re.match(r'(.*)\|(.*)', self.session.target)
get_member_info = await bot.call_action('get_guild_member_profile', guild_id=match_guild.group(1),
user_id=self.session.sender)
for m in get_member_info['roles']:
if m['role_id'] == "2":
return True
get_guild_info = await bot.call_action('get_guild_meta_by_guest', guild_id=match_guild.group(1))
if get_guild_info['owner_id'] == self.session.sender:
return True
return False
return False
def as_display(self, text_only=False):
"""m = html.unescape(self.session.message.message)
if text_only:
return ''.join(
re.split(r'\\[CQ:.*?]', m)).strip()
m = re.sub(r'\\[CQ:at,qq=(.*?)]', r'QQ|\1', m)
m = re.sub(r'\\[CQ:forward,id=(.*?)]', r'\\[Ke:forward,id=\1]', m)
return ''.join(
re.split(r'\\[CQ:.*?]', m)).strip()"""
return self.session.message
async def fake_forward_msg(self, nodelist):
if self.target.target_from == 'QQ|Group':
get_ = get_stored_list(Bot.FetchTarget, 'forward_msg')
if not get_['status']:
await self.send_message(self.locale.t("core.message.forward_msg.disabled"))
raise
await bot.call_action('send_group_forward_msg', group_id=int(self.session.target), messages=nodelist)
async def delete(self):
if self.target.target_from in ['QQ', 'QQ|Group']:
try:
if isinstance(self.session.message, list):
for x in self.session.message:
await bot.call_action('delete_msg', message_id=x['message_id'])
else:
await bot.call_action('delete_msg', message_id=self.session.message['message_id'])
except Exception:
Logger.error(traceback.format_exc())
async def get_text_channel_list(self):
match_guild = re.match(r'(.*)\|(.*)', self.session.target)
get_channels_info = await bot.call_action('get_guild_channel_list', guild_id=match_guild.group(1),
no_cache=True)
lst = []
for m in get_channels_info:
if m['channel_type'] == 1:
lst.append(f'{m["owner_guild_id"]}|{m["channel_id"]}')
return lst
async def to_message_chain(self):
m = html.unescape(self.session.message.message)
m = re.sub(r'\[CQ:at,qq=(.*?)]', r'QQ|\1', m)
m = re.sub(r'\[CQ:forward,id=(.*?)]', r'\[Ke:forward,id=\1]', m)
spl = re.split(r'(\[CQ:.*?])', m)
lst = []
for s in spl:
if s == '':
continue
if s.startswith('[CQ:'):
if s.startswith('[CQ:image'):
sspl = s.split(',')
for ss in sspl:
if ss.startswith('url='):
lst.append(Image(ss[4:-1]))
else:
lst.append(Plain(s))
return MessageChain(lst)
async def call_api(self, action, **params):
return await bot.call_action(action, **params)
sendMessage = send_message
asDisplay = as_display
toMessageChain = to_message_chain
checkNativePermission = check_native_permission
class Typing:
def __init__(self, msg: MessageSessionT):
self.msg = msg
async def __aenter__(self):
"""if self.msg.target.target_from == 'QQ|Group':
if self.msg.session.sender in last_send_typing_time:
if datetime.datetime.now().timestamp() - last_send_typing_time[self.msg.session.sender] <= 3600:
return
last_send_typing_time[self.msg.session.sender] = datetime.datetime.now().timestamp()
await bot.send_group_msg(group_id=self.msg.session.target,
message=f'[CQ:poke,qq={self.msg.session.sender}]')"""
async def __aexit__(self, exc_type, exc_val, exc_tb):
pass
class FetchTarget(FetchTargetT):
name = client_name
@staticmethod
async def fetch_target(target_id, sender_id=None) -> Union[Bot.FetchedSession]:
match_target = re.match(r'^(QQ\|Group|QQ\|Guild|QQ)\|(.*)', target_id)
if match_target:
target_from = sender_from = match_target.group(1)
target_id = match_target.group(2)
if sender_id:
match_sender = re.match(r'^(QQ\|Tiny|QQ)\|(.*)', sender_id)
if match_sender:
sender_from = match_sender.group(1)
sender_id = match_sender.group(2)
else:
sender_id = target_id
return Bot.FetchedSession(target_from, target_id, sender_from, sender_id)
@staticmethod
async def fetch_target_list(target_list: list) -> List[Bot.FetchedSession]:
lst = []
group_list_raw = await bot.call_action('get_group_list')
group_list = []
for g in group_list_raw:
group_list.append(g['group_id'])
friend_list_raw = await bot.call_action('get_friend_list')
friend_list = []
guild_list_raw = await bot.call_action('get_guild_list')
guild_list = []
for g in guild_list_raw:
get_channel_list = await bot.call_action('get_guild_channel_list', guild_id=g['guild_id'])
for channel in get_channel_list:
if channel['channel_type'] == 1:
guild_list.append(f"{str(g['guild_id'])}|{str(channel['channel_id'])}")
for f in friend_list_raw:
friend_list.append(f)
for x in target_list:
fet = await FetchTarget.fetch_target(x)
if fet:
if fet.target.target_from == 'QQ|Group':
if fet.session.target not in group_list:
continue
if fet.target.target_from == 'QQ':
if fet.session.target not in friend_list:
continue
if fet.target.target_from == 'QQ|Guild':
if fet.session.target not in guild_list:
continue
lst.append(fet)
return lst
@staticmethod
async def post_message(module_name, message, user_list: List[Bot.FetchedSession] = None, i18n=False, **kwargs):
_tsk = []
blocked = False
async def post_(fetch_: Bot.FetchedSession):
nonlocal _tsk
nonlocal blocked
try:
if Temp.data['is_group_message_blocked'] and fetch_.target.target_from == 'QQ|Group':
Temp.data['waiting_for_send_group_message'].append({'fetch': fetch_, 'message': message,
'i18n': i18n, 'kwargs': kwargs})
else:
if i18n:
await fetch_.send_direct_message(fetch_.parent.locale.t(message, **kwargs))
else:
await fetch_.send_direct_message(message)
if _tsk:
_tsk = []
if enable_analytics:
BotDBUtil.Analytics(fetch_).add('', module_name, 'schedule')
await asyncio.sleep(0.5)
except SendMessageFailed as e:
if e.args[0] == 'send group message failed: blocked by server':
if len(_tsk) >= 3:
blocked = True
if not blocked:
_tsk.append({'fetch': fetch_, 'message': message, 'i18n': i18n, 'kwargs': kwargs})
else:
Temp.data['is_group_message_blocked'] = True
Temp.data['waiting_for_send_group_message'].append({'fetch': fetch_, 'message': message,
'i18n': i18n, 'kwargs': kwargs})
if _tsk:
for t in _tsk:
Temp.data['waiting_for_send_group_message'].append(t)
_tsk = []
for bu in base_superuser_list:
fetch_base_superuser = await FetchTarget.fetch_target(bu)
if fetch_base_superuser:
await fetch_base_superuser. \
send_direct_message(fetch_base_superuser.parent.locale.t("error.message.paused",
prefix=command_prefix[0]))
except Exception:
Logger.error(traceback.format_exc())
if user_list is not None:
for x in user_list:
await post_(x)
else:
get_target_id = BotDBUtil.TargetInfo.get_enabled_this(module_name, "QQ")
group_list_raw = await bot.call_action('get_group_list')
group_list = [g['group_id'] for g in group_list_raw]
friend_list_raw = await bot.call_action('get_friend_list')
friend_list = [f['user_id'] for f in friend_list_raw]
guild_list_raw = await bot.call_action('get_guild_list')
guild_list = []
for g in guild_list_raw:
try:
get_channel_list = await bot.call_action('get_guild_channel_list', guild_id=g['guild_id'],
no_cache=True)
for channel in get_channel_list:
if channel['channel_type'] == 1:
guild_list.append(f"{str(g['guild_id'])}|{str(channel['channel_id'])}")
except Exception:
traceback.print_exc()
continue
in_whitelist = []
else_ = []
for x in get_target_id:
fetch = await FetchTarget.fetch_target(x.targetId)
Logger.debug(fetch)
if fetch:
if fetch.target.target_from == 'QQ|Group':
if int(fetch.session.target) not in group_list:
continue
if fetch.target.target_from == 'QQ':
if int(fetch.session.target) not in friend_list:
continue
if fetch.target.target_from == 'QQ|Guild':
if fetch.session.target not in guild_list:
continue
if fetch.target.target_from in ['QQ', 'QQ|Guild']:
in_whitelist.append(post_(fetch))
else:
load_options: dict = json.loads(x.options)
if load_options.get('in_post_whitelist', False):
in_whitelist.append(post_(fetch))
else:
else_.append(post_(fetch))
async def post_in_whitelist(lst):
for l in lst:
await l
await asyncio.sleep(random.randint(1, 5))
if in_whitelist:
asyncio.create_task(post_in_whitelist(in_whitelist))
async def post_not_in_whitelist(lst):
for f in lst:
await f
await asyncio.sleep(random.randint(15, 30))
if else_:
asyncio.create_task(post_not_in_whitelist(else_))
Logger.info(f"The task of posting messages to whitelisted groups is complete. "
f"Posting message to {len(else_)} groups not in whitelist.")
Bot.MessageSession = MessageSession
Bot.FetchTarget = FetchTarget
Bot.client_name = client_name

15
bots/lagrange/utils.py Normal file
View file

@ -0,0 +1,15 @@
from bots.lagrange.client import bot
from aiocqhttp.exceptions import ActionFailed
from config import Config
def get_plain_msg(array_msg: list) -> str:
text = []
for msg in array_msg:
if msg['type'] == 'text':
text.append(msg['data']['text'])
return '\n'.join(text)
async def get_group_list():
return await bot.call_action('get_group_list')

View file

@ -11,6 +11,7 @@ from .message.internal import *
from .tasks import *
from .temp import *
from .utils import *
from ..logger import Logger
class Bot:
@ -26,11 +27,12 @@ class Bot:
disable_secret_check=False,
allow_split_image=True):
if isinstance(target, str):
target = Bot.FetchTarget.fetch_target(target)
target = await Bot.FetchTarget.fetch_target(target)
if not target:
raise ValueError("Target not found")
if isinstance(msg, list):
msg = MessageChain(msg)
Logger.info(target.__dict__)
await target.send_direct_message(msg, disable_secret_check, allow_split_image)
@staticmethod

View file

@ -14,7 +14,7 @@ from core.types.message import MessageChain as MessageChainT
class MessageChain(MessageChainT):
def __init__(self, elements: Union[str, List[Union[Plain, Image, Voice, Embed, Url]],
Tuple[Union[Plain, Image, Voice, Embed, Url]],
Plain, Image, Voice, Embed, Url]):
Plain, Image, Voice, Embed, Url] = None):
self.value = []
if isinstance(elements, ErrorMessage):
elements = str(elements)
@ -38,6 +38,19 @@ class MessageChain(MessageChainT):
self.value += match_kecode(e.text)
else:
self.value.append(e)
elif isinstance(e, dict):
if e['type'] in ['plain', 'text']:
self.value.append(Plain(e['data']['text']))
elif e['type'] == 'image':
self.value.append(Image(e['data']['path']))
elif e['type'] == 'voice':
self.value.append(Voice(e['data']['path']))
elif e['type'] == 'embed':
self.value.append(
Embed(e['data']['title'], e['data']['description'], e['data']['url'],
e['data']['timestamp'],
e['data']['color'], Image(e['data']['image']), Image(e['data']['thumbnail']),
e['data']['author'], e['data']['footer'], e['data']['fields']))
elif isinstance(e, str):
if e != '':
self.value += match_kecode(e)
@ -45,6 +58,8 @@ class MessageChain(MessageChainT):
Logger.error(f'Unexpected message type: {elements}')
elif isinstance(elements, MessageChain):
self.value = elements.value
elif elements is None:
pass
else:
Logger.error(f'Unexpected message type: {elements}')
@ -99,19 +114,33 @@ class MessageChain(MessageChainT):
for x in self.value:
if isinstance(x, Embed) and not embed:
value += x.to_message_chain()
elif isinstance(x, ErrorMessage):
value.append(ErrorMessage(x.error_message, locale=locale))
elif isinstance(x, Plain):
if x.text != '':
value.append(x)
else:
Plain(ErrorMessage('{error.message.chain.plain.empty}', locale=locale))
value.append(Plain(ErrorMessage('{error.message.chain.plain.empty}', locale=locale)))
else:
value.append(x)
if not value:
value.append(Plain(ErrorMessage('{error.message.chain.empty}', locale=locale)))
return value
def to_list(self, locale="zh_cn", embed=True):
value = []
for x in self.value:
if isinstance(x, Embed) and not embed:
value += x.to_message_chain().to_list()
elif isinstance(x, Plain):
if x.text != '':
value.append(x.to_dict())
else:
value.append(Plain(ErrorMessage('{error.message.chain.plain.empty}', locale=locale)).to_dict())
else:
value.append(x.to_dict())
if not value:
value.append(Plain(ErrorMessage('{error.message.chain.empty}', locale=locale)).to_dict())
return value
def append(self, element):
self.value.append(element)
@ -121,6 +150,9 @@ class MessageChain(MessageChainT):
def insert(self, index, element):
self.value.insert(index, element)
def copy(self):
return MessageChain(self.value.copy())
def __str__(self):
return f'[{", ".join([x.__repr__() for x in self.value])}]'

View file

@ -1,3 +1,4 @@
import base64
import re
import uuid
from os.path import abspath
@ -28,6 +29,9 @@ class Plain(PlainT):
def __repr__(self):
return f'Plain(text="{self.text}")'
def to_dict(self):
return {'type': 'plain', 'data': {'text': self.text}}
class Url(UrlT):
mm = False
@ -69,6 +73,9 @@ class ErrorMessage(EMsg):
def __repr__(self):
return self.error_message
def to_dict(self):
return {'type': 'error', 'data': {'error': self.error_message}}
class Image(ImageT):
def __init__(self,
@ -100,12 +107,20 @@ class Image(ImageT):
image_cache.write(raw)
return img_path
async def get_base64(self):
file = await self.get()
with open(file, 'rb') as f:
return str(base64.b64encode(f.read()), "UTF-8")
def __str__(self):
return self.path
def __repr__(self):
return f'Image(path="{self.path}", headers={self.headers})'
def to_dict(self):
return {'type': 'image', 'data': {'path': self.path}}
class Voice(VoiceT):
def __init__(self,
@ -118,6 +133,9 @@ class Voice(VoiceT):
def __repr__(self):
return f'Voice(path={self.path})'
def to_dict(self):
return {'type': 'voice', 'data': {'path': self.path}}
class EmbedField(EmbedFieldT):
def __init__(self,
@ -134,6 +152,9 @@ class EmbedField(EmbedFieldT):
def __repr__(self):
return f'EmbedField(name="{self.name}", value="{self.value}", inline={self.inline})'
def to_dict(self):
return {'type': 'field', 'data': {'name': self.name, 'value': self.value, 'inline': self.inline}}
class Embed(EmbedT):
def __init__(self,
@ -192,5 +213,20 @@ class Embed(EmbedT):
f'thumbnail={self.thumbnail.__repr__()}, author="{self.author}", footer="{self.footer}", ' \
f'fields={self.fields})'
def to_dict(self):
return {
'type': 'embed',
'data': {
'title': self.title,
'description': self.description,
'url': self.url,
'timestamp': self.timestamp,
'color': self.color,
'image': self.image,
'thumbnail': self.thumbnail,
'author': self.author,
'footer': self.footer,
'fields': self.fields}}
__all__ = ["Plain", "Image", "Voice", "Embed", "EmbedField", "Url", "ErrorMessage"]

View file

@ -1,9 +1,10 @@
import asyncio
from datetime import datetime
import traceback
import ujson as json
from core.builtins import Bot
from core.builtins import Bot, Temp, MessageChain
from core.logger import Logger
from core.utils.info import get_all_clients_name
from core.utils.ip import append_ip, fetch_ip_info
@ -47,6 +48,10 @@ class JobQueue:
for target in get_all_clients_name():
await cls.add_job(target, 'secret_append_ip', ip_info, wait=False)
@classmethod
async def send_message(cls, target_client: str, target_id: str, message):
await cls.add_job(target_client, 'send_message', {'target_id': target_id, 'message': message})
def return_val(tsk, value: dict, status=True):
value = value.update({'status': status})
@ -63,17 +68,30 @@ async def check_job_queue():
for tsk in get_all:
Logger.debug(f'Received job queue task {tsk.taskid}, action: {tsk.action}')
args = json.loads(tsk.args)
Logger.debug(f'Args: {args}')
try:
if tsk.action == 'validate_permission':
fetch = await Bot.FetchTarget.fetch_target(args['target_id'], args['sender_id'])
if fetch:
return_val(tsk, {'value': await fetch.parent.check_permission()})
else:
return_val(tsk, {'value': False})
if tsk.action == 'trigger_hook':
await Bot.Hook.trigger(args['module_or_hook_name'], args['args'])
return_val(tsk, {})
if tsk.action == 'secret_append_ip':
append_ip(args)
return_val(tsk, {})
if tsk.action == 'send_message':
return_val(tsk, {})
fetch = await Bot.send_message(args['target_id'], MessageChain(args['message']))
Logger.debug(f'Send message to {args["target_id"]}')
if tsk.action == 'lagrange_keepalive':
Temp.data['lagrange_keepalive'] = datetime.now().timestamp()
return_val(tsk, {})
if tsk.action == 'lagrange_available_groups':
Temp.data['lagrange_available_groups'] = args
return_val(tsk, {})
except Exception as e:
return_val(tsk, {'traceback': traceback.format_exc()}, status=False)

View file

@ -10,7 +10,7 @@ class MessageChain:
def __init__(self, elements: Union[str, List[Union[Plain, Image, Voice, Embed, Url]],
Tuple[Union[Plain, Image, Voice, Embed, Url]],
Plain, Image, Voice, Embed, Url]):
Plain, Image, Voice, Embed, Url] = None):
"""
:param elements: 消息链元素
"""
@ -29,6 +29,18 @@ class MessageChain:
"""
raise NotImplementedError
def to_list(self) -> list:
"""
将消息链转换为列表
"""
raise NotImplementedError
def from_list(self, lst: list):
"""
从列表构造消息链
"""
raise NotImplementedError
def append(self, element):
"""
添加一个消息链元素到末尾