mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-02-20 17:46:51 +08:00
完成适配器化改造,所有上报数据统一、接口调用等都改成通过不同消息源的适配器来完成,插件和消息源的耦合
This commit is contained in:
parent
ecd446f057
commit
3508db348d
37
app.py
37
app.py
@ -1,31 +1,29 @@
|
||||
import os
|
||||
import importlib
|
||||
|
||||
from flask import Flask, request
|
||||
|
||||
from little_shit import SkipException, get_filters_dir
|
||||
from little_shit import SkipException, load_plugins
|
||||
from filter import apply_filters
|
||||
from msg_src_adapter import get_adapter
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/qq/', methods=['POST'])
|
||||
def _handle_qq_message():
|
||||
@app.route('/<string:via>/<string:login_id>', methods=['POST'], strict_slashes=False)
|
||||
def _handle_via_account(via: str, login_id: str):
|
||||
ctx_msg = request.json
|
||||
ctx_msg['via'] = 'qq'
|
||||
return _main(ctx_msg)
|
||||
|
||||
|
||||
@app.route('/wx/', methods=['POST'])
|
||||
def _handle_wx_message():
|
||||
ctx_msg = request.json
|
||||
ctx_msg['via'] = 'wx'
|
||||
ctx_msg['via'] = via
|
||||
ctx_msg['login_id'] = login_id
|
||||
return _main(ctx_msg)
|
||||
|
||||
|
||||
def _main(ctx_msg: dict):
|
||||
try:
|
||||
if ctx_msg.get('post_type') != 'receive_message':
|
||||
adapter = get_adapter(ctx_msg.get('via'), ctx_msg.get('login_id'))
|
||||
if not adapter:
|
||||
raise SkipException
|
||||
ctx_msg = adapter.unitize_context(ctx_msg)
|
||||
if ctx_msg.get('post_type') != 'message':
|
||||
raise SkipException
|
||||
if not apply_filters(ctx_msg):
|
||||
raise SkipException
|
||||
@ -36,16 +34,7 @@ def _main(ctx_msg: dict):
|
||||
return '', 204
|
||||
|
||||
|
||||
def _load_filters():
|
||||
filter_mod_files = filter(
|
||||
lambda filename: filename.endswith('.py') and not filename.startswith('_'),
|
||||
os.listdir(get_filters_dir())
|
||||
)
|
||||
command_mods = [os.path.splitext(file)[0] for file in filter_mod_files]
|
||||
for mod_name in command_mods:
|
||||
importlib.import_module('filters.' + mod_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_load_filters()
|
||||
load_plugins('msg_src_adapters')
|
||||
load_plugins('filters')
|
||||
app.run(host=os.environ.get('HOST', '0.0.0.0'), port=os.environ.get('PORT', '8080'))
|
||||
|
67
command.py
67
command.py
@ -1,9 +1,8 @@
|
||||
import functools
|
||||
import re
|
||||
import os
|
||||
|
||||
from apiclient import client as api
|
||||
from little_shit import SkipException, get_command_name_separators, get_command_args_separators
|
||||
from little_shit import get_command_name_separators, get_command_args_separators
|
||||
from msg_src_adapter import get_adapter_by_ctx
|
||||
|
||||
_command_name_seps = get_command_name_separators()
|
||||
_command_args_seps = get_command_args_separators()
|
||||
@ -117,13 +116,15 @@ class CommandRegistry:
|
||||
if command_name in self.command_map:
|
||||
func = self.command_map[command_name]
|
||||
if not self._check_scope(func, ctx_msg):
|
||||
msg_type = ctx_msg.get('type')
|
||||
if msg_type == 'group_message':
|
||||
msg_type = ctx_msg.get('msg_type')
|
||||
if msg_type == 'group':
|
||||
msg_type_str = '群组消息'
|
||||
elif msg_type == 'discuss_message':
|
||||
elif msg_type == 'discuss':
|
||||
msg_type_str = '讨论组消息'
|
||||
else:
|
||||
elif msg_type == 'private':
|
||||
msg_type_str = '私聊消息'
|
||||
else:
|
||||
msg_type_str = '未知来源消息'
|
||||
raise CommandScopeError(msg_type_str)
|
||||
if not self._check_permission(func, ctx_msg):
|
||||
raise CommandPermissionError
|
||||
@ -140,13 +141,13 @@ class CommandRegistry:
|
||||
"""
|
||||
allowed_msg_type = set()
|
||||
if func.allow_group:
|
||||
allowed_msg_type.add('group_message')
|
||||
allowed_msg_type.add('group')
|
||||
if func.allow_discuss:
|
||||
allowed_msg_type.add('discuss_message')
|
||||
allowed_msg_type.add('discuss')
|
||||
if func.allow_private:
|
||||
allowed_msg_type.add('friend_message')
|
||||
allowed_msg_type.add('private')
|
||||
|
||||
if ctx_msg.get('type') in allowed_msg_type:
|
||||
if ctx_msg.get('msg_type') in allowed_msg_type:
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -160,39 +161,23 @@ class CommandRegistry:
|
||||
:param ctx_msg: context message
|
||||
:return: permitted or not
|
||||
"""
|
||||
adapter = get_adapter_by_ctx(ctx_msg)
|
||||
if adapter.is_sender_superuser(ctx_msg):
|
||||
return True # Superuser is the BIG BOSS
|
||||
|
||||
def check(b):
|
||||
if not b:
|
||||
raise SkipException
|
||||
if func.superuser_only:
|
||||
return False
|
||||
|
||||
try:
|
||||
if func.superuser_only:
|
||||
raise SkipException
|
||||
if ctx_msg.get('type') == 'group_message' and ctx_msg.get('via') == 'qq':
|
||||
allowed_roles = {'owner', 'admin', 'member'}
|
||||
if func.group_admin_only:
|
||||
allowed_roles.intersection_update({'owner', 'admin'})
|
||||
if func.group_owner_only:
|
||||
allowed_roles.intersection_update({'owner'})
|
||||
groups = list(filter(
|
||||
lambda g: str(g.get('id')) == ctx_msg.get('group_id'),
|
||||
api.get_group_info(ctx_msg).json()
|
||||
))
|
||||
if len(groups) <= 0 or 'member' not in groups[0]:
|
||||
# This is strange, not likely happens
|
||||
raise SkipException
|
||||
if ctx_msg.get('msg_type') == 'group':
|
||||
# TODO: 在酷 Q 测试一下
|
||||
allowed_roles = {'owner', 'admin', 'member'}
|
||||
if func.group_admin_only:
|
||||
allowed_roles.intersection_update({'owner', 'admin'})
|
||||
if func.group_owner_only:
|
||||
allowed_roles.intersection_update({'owner'})
|
||||
|
||||
members = list(filter(
|
||||
lambda m: str(m.get('id')) == ctx_msg.get('sender_id'),
|
||||
groups[0].get('member')
|
||||
))
|
||||
if len(members) <= 0 or members[0].get('role') not in allowed_roles:
|
||||
# This is strange, not likely happens
|
||||
raise SkipException
|
||||
except SkipException:
|
||||
if ctx_msg.get('via') == 'qq' and ctx_msg.get('sender_uid') != os.environ.get('QQ_SUPER_USER'):
|
||||
return False
|
||||
elif ctx_msg.get('via') == 'wx' and ctx_msg.get('sender_account') != os.environ.get('WX_SUPER_USER'):
|
||||
role = adapter.get_sender_group_role(ctx_msg)
|
||||
if role not in allowed_roles:
|
||||
return False
|
||||
|
||||
# Still alive, let go
|
||||
|
@ -4,7 +4,6 @@ import requests
|
||||
from command import CommandRegistry
|
||||
from commands import core
|
||||
from little_shit import get_source
|
||||
from apiclient import client as api
|
||||
|
||||
__registry__ = cr = CommandRegistry()
|
||||
|
||||
@ -33,15 +32,15 @@ def tuling123(args_text, ctx_msg, internal=False):
|
||||
reply = '腊鸡图灵机器人出问题了,先不管他,过会儿再玩他'
|
||||
core.echo(reply, ctx_msg)
|
||||
|
||||
|
||||
@cr.register('xiaoice', '小冰')
|
||||
def xiaoice(args_text, ctx_msg, internal=False):
|
||||
resp = api.wx_consult(account='xiaoice-ms', content=args_text)
|
||||
if resp:
|
||||
json = resp.json()
|
||||
if json and json.get('reply'):
|
||||
reply = json['reply']
|
||||
core.echo(reply, ctx_msg, internal)
|
||||
return reply
|
||||
core.echo('小冰没有回复,请稍后再试', ctx_msg, internal)
|
||||
return None
|
||||
# TODO: 加入微信消息源之后修改
|
||||
# @cr.register('xiaoice', '小冰')
|
||||
# def xiaoice(args_text, ctx_msg, internal=False):
|
||||
# resp = api.wx_consult(account='xiaoice-ms', content=args_text)
|
||||
# if resp:
|
||||
# json = resp.json()
|
||||
# if json and json.get('reply'):
|
||||
# reply = json['reply']
|
||||
# core.echo(reply, ctx_msg, internal)
|
||||
# return reply
|
||||
# core.echo('小冰没有回复,请稍后再试', ctx_msg, internal)
|
||||
# return None
|
||||
|
@ -1,5 +1,5 @@
|
||||
from command import CommandRegistry
|
||||
from apiclient import client as api
|
||||
from msg_src_adapter import get_adapter_by_ctx
|
||||
|
||||
__registry__ = cr = CommandRegistry()
|
||||
|
||||
@ -9,7 +9,10 @@ def echo(args_text, ctx_msg, internal=False):
|
||||
if internal:
|
||||
return None
|
||||
else:
|
||||
return api.send_message(args_text, ctx_msg)
|
||||
return get_adapter_by_ctx(ctx_msg).send_message(
|
||||
target=ctx_msg,
|
||||
content=args_text
|
||||
)
|
||||
|
||||
|
||||
@cr.register('help', '帮助', '用法', '使用帮助', '使用指南', '使用说明', '使用方法', '怎么用')
|
||||
|
@ -1,15 +1,12 @@
|
||||
import os
|
||||
import importlib
|
||||
|
||||
from command import CommandRegistry
|
||||
from commands import core
|
||||
from nl_processor import parse_potential_commands
|
||||
from little_shit import get_nl_processors_dir, get_fallback_command_after_nl_processors
|
||||
from little_shit import load_plugins, get_fallback_command_after_nl_processors
|
||||
from command import hub as cmdhub
|
||||
|
||||
|
||||
def _init():
|
||||
_load_processors()
|
||||
load_plugins('nl_processors')
|
||||
|
||||
|
||||
__registry__ = cr = CommandRegistry(init_func=_init)
|
||||
@ -41,13 +38,3 @@ def process(args_text, ctx_msg):
|
||||
core.echo('暂时无法理解你的意思。\n'
|
||||
'由于自然语言识别还非常不完善,建议使用命令来精确控制我。\n'
|
||||
'如需帮助请发送「使用帮助」。', ctx_msg)
|
||||
|
||||
|
||||
def _load_processors():
|
||||
processor_mod_files = filter(
|
||||
lambda filename: filename.endswith('.py') and not filename.startswith('_'),
|
||||
os.listdir(get_nl_processors_dir())
|
||||
)
|
||||
command_mods = [os.path.splitext(file)[0] for file in processor_mod_files]
|
||||
for mod_name in command_mods:
|
||||
importlib.import_module('nl_processors.' + mod_name)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
from functools import reduce, wraps
|
||||
from functools import wraps
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
|
@ -26,22 +26,21 @@ def test(_, ctx_msg):
|
||||
|
||||
@cr.register('block')
|
||||
@cr.restrict(full_command_only=True, superuser_only=True)
|
||||
@split_arguments(maxsplit=2)
|
||||
@split_arguments(maxsplit=1)
|
||||
def block(_, ctx_msg, argv=None):
|
||||
def _send_error_msg():
|
||||
core.echo('参数不正确。\n\n正确使用方法:\nsudo.block wx|qq <account-to-block>', ctx_msg)
|
||||
core.echo('参数不正确。\n\n正确使用方法:\nsudo.block <account-to-block>', ctx_msg)
|
||||
|
||||
if len(argv) != 2:
|
||||
if len(argv) != 1:
|
||||
_send_error_msg()
|
||||
return
|
||||
|
||||
via, account = argv
|
||||
account = argv[0]
|
||||
# Get a target using a fake context message
|
||||
target = get_target({
|
||||
'via': via,
|
||||
'type': 'friend_message',
|
||||
'sender_uid': account,
|
||||
'sender_account': account
|
||||
'via': 'default',
|
||||
'msg_type': 'private',
|
||||
'sender_id': account
|
||||
})
|
||||
|
||||
if not target:
|
||||
@ -65,31 +64,28 @@ def block_list(_, ctx_msg, internal=False):
|
||||
if internal:
|
||||
return blocked_targets
|
||||
if blocked_targets:
|
||||
# `t[1:]` to reply user account, without target prefix 'p'.
|
||||
# This is a shit code, and should be changed later sometime.
|
||||
core.echo('已屏蔽的用户:\n' + ', '.join([t[1:] for t in blocked_targets]), ctx_msg)
|
||||
core.echo('已屏蔽的用户:\n' + ', '.join(blocked_targets), ctx_msg)
|
||||
else:
|
||||
core.echo('还没有屏蔽过用户', ctx_msg)
|
||||
|
||||
|
||||
@cr.register('unblock')
|
||||
@cr.restrict(full_command_only=True, superuser_only=True)
|
||||
@split_arguments(maxsplit=2)
|
||||
@split_arguments(maxsplit=1)
|
||||
def unblock(_, ctx_msg, argv=None):
|
||||
def _send_error_msg():
|
||||
core.echo('参数不正确。\n\n正确使用方法:\nsudo.unblock wx|qq <account-to-unblock>', ctx_msg)
|
||||
core.echo('参数不正确。\n\n正确使用方法:\nsudo.unblock <account-to-unblock>', ctx_msg)
|
||||
|
||||
if len(argv) != 2:
|
||||
if len(argv) != 1:
|
||||
_send_error_msg()
|
||||
return
|
||||
|
||||
via, account = argv
|
||||
account = argv[0]
|
||||
# Get a target using a fake context message
|
||||
target = get_target({
|
||||
'via': via,
|
||||
'type': 'friend_message',
|
||||
'sender_uid': account,
|
||||
'sender_account': account
|
||||
'via': 'default',
|
||||
'msg_type': 'private',
|
||||
'sender_id': account
|
||||
})
|
||||
|
||||
if not target:
|
||||
|
15
config.py
15
config.py
@ -5,4 +5,19 @@ config = {
|
||||
'command_name_separators': ('->', '::', '/'), # Regex
|
||||
'command_args_start_flags': (',', ':', ',', ', ', ':', ': '), # Regex
|
||||
'command_args_separators': (',', ','), # Regex
|
||||
|
||||
'message_sources': [
|
||||
{
|
||||
'via': 'mojo_weixin',
|
||||
'login_id': 'rcdevtest',
|
||||
'superuser_id': 'richard_chien_0',
|
||||
'api_url': 'http://127.0.0.1:5001/openwx',
|
||||
},
|
||||
{
|
||||
'via': 'mojo_webqq',
|
||||
'login_id': '3281334718',
|
||||
'superuser_id': '1002647525',
|
||||
'api_url': 'http://127.0.0.1:5000/openqq',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import re
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
import interactive
|
||||
from filter import as_filter
|
||||
@ -14,20 +13,6 @@ _command_start_flags = get_command_start_flags()
|
||||
_command_args_start_flags = get_command_args_start_flags()
|
||||
|
||||
|
||||
def _load_commands():
|
||||
command_mod_files = filter(
|
||||
lambda filename: filename.endswith('.py') and not filename.startswith('_'),
|
||||
os.listdir(get_commands_dir())
|
||||
)
|
||||
command_mods = [os.path.splitext(file)[0] for file in command_mod_files]
|
||||
for mod_name in command_mods:
|
||||
cmd_mod = importlib.import_module('commands.' + mod_name)
|
||||
try:
|
||||
cmdhub.add_registry(mod_name, cmd_mod.__registry__)
|
||||
except AttributeError:
|
||||
print('Failed to load command module "' + mod_name + '.py".', file=sys.stderr)
|
||||
|
||||
|
||||
@as_filter(priority=0)
|
||||
def _dispatch_command(ctx_msg):
|
||||
try:
|
||||
@ -77,4 +62,12 @@ def _dispatch_command(ctx_msg):
|
||||
core.echo('这个命令不支持' + se.msg_type + '哦~', ctx_msg)
|
||||
|
||||
|
||||
_load_commands()
|
||||
def _add_registry_mod_cb(mod):
|
||||
mod_name = mod.__name__.split('.')[1]
|
||||
try:
|
||||
cmdhub.add_registry(mod_name, mod.__registry__)
|
||||
except AttributeError:
|
||||
print('Failed to load command module "' + mod_name + '.py".', file=sys.stderr)
|
||||
|
||||
|
||||
load_plugins('commands', module_callback=_add_registry_mod_cb)
|
||||
|
@ -8,7 +8,7 @@ from filter import as_filter
|
||||
@as_filter(priority=100)
|
||||
def _filter(ctx_msg):
|
||||
msg_format = ctx_msg.get('format')
|
||||
if msg_format != 'text' and ctx_msg.get('type') != 'friend_message':
|
||||
if msg_format != 'text' and ctx_msg.get('msg_type') != 'private':
|
||||
return False
|
||||
if msg_format not in ('text', 'media'):
|
||||
return False
|
||||
|
@ -7,7 +7,10 @@ from filter import as_filter
|
||||
|
||||
@as_filter(priority=1000)
|
||||
def _log_message(ctx_msg):
|
||||
print(ctx_msg.get('sender', '')
|
||||
+ (('@' + ctx_msg.get('group')) if ctx_msg.get('type') == 'group_message' else '')
|
||||
+ (('@' + ctx_msg.get('discuss')) if ctx_msg.get('type') == 'discuss_message' else '')
|
||||
+ ': ' + ctx_msg.get('content'))
|
||||
log = ctx_msg.get('sender') or ctx_msg.get('sender_id') or '未知用户'
|
||||
if ctx_msg.get('msg_type') == 'group':
|
||||
log += '@' + ctx_msg.get('group') or ctx_msg.get('group_id') or '未知群组'
|
||||
if ctx_msg.get('msg_type') == 'discuss':
|
||||
log += '@' + ctx_msg.get('discuss') or ctx_msg.get('discuss_id') or '未知讨论组'
|
||||
log += ': ' + ctx_msg.get('content', '')
|
||||
print(log)
|
||||
|
@ -3,12 +3,16 @@ This filter intercepts messages not intended to the bot and removes the beginnin
|
||||
"""
|
||||
|
||||
from filter import as_filter
|
||||
from apiclient import client as api
|
||||
from msg_src_adapter import get_adapter_by_ctx
|
||||
|
||||
|
||||
@as_filter(priority=50)
|
||||
def _split_at_xiaokai(ctx_msg):
|
||||
if ctx_msg.get('type') == 'group_message' or ctx_msg.get('type') == 'discuss_message':
|
||||
if ctx_msg.get('is_at_me'):
|
||||
# Directly return because it has been confirmed by previous processes
|
||||
return True
|
||||
|
||||
if ctx_msg.get('msg_type') == 'group' or ctx_msg.get('msg_type') == 'discuss':
|
||||
text = ctx_msg.get('text', '')
|
||||
if text.startswith('@'):
|
||||
my_group_nick = ctx_msg.get('receiver')
|
||||
@ -16,10 +20,8 @@ def _split_at_xiaokai(ctx_msg):
|
||||
return False
|
||||
at_me = '@' + my_group_nick
|
||||
if not text.startswith(at_me):
|
||||
user_info = api.get_user_info(ctx_msg).json()
|
||||
if not user_info:
|
||||
return False
|
||||
my_nick = user_info.get('name')
|
||||
user_info = get_adapter_by_ctx(ctx_msg).get_login_info(ctx_msg)
|
||||
my_nick = user_info.get('nickname')
|
||||
if not my_nick:
|
||||
return False
|
||||
at_me = '@' + my_nick
|
||||
|
@ -1,25 +0,0 @@
|
||||
"""
|
||||
This filter unitize context messages from different platform.
|
||||
"""
|
||||
|
||||
from filter import as_filter
|
||||
|
||||
|
||||
@as_filter(priority=10000)
|
||||
def _unitize(ctx_msg):
|
||||
if 'group_uid' in ctx_msg:
|
||||
ctx_msg['group_uid'] = str(ctx_msg['group_uid'])
|
||||
if 'sender_uid' in ctx_msg:
|
||||
ctx_msg['sender_uid'] = str(ctx_msg['sender_uid'])
|
||||
if 'sender_id' in ctx_msg:
|
||||
ctx_msg['sender_id'] = str(ctx_msg['sender_id'])
|
||||
if 'discuss_id' in ctx_msg:
|
||||
ctx_msg['discuss_id'] = str(ctx_msg['discuss_id'])
|
||||
if 'group_id' in ctx_msg:
|
||||
ctx_msg['group_id'] = str(ctx_msg['group_id'])
|
||||
if 'id' in ctx_msg:
|
||||
ctx_msg['id'] = str(ctx_msg['id'])
|
||||
|
||||
if ctx_msg.get('via') == 'qq' and not ctx_msg.get('format'):
|
||||
# All QQ messages that can be received are text
|
||||
ctx_msg['format'] = 'text'
|
@ -1,11 +1,8 @@
|
||||
import importlib
|
||||
import os
|
||||
import hashlib
|
||||
import random
|
||||
import functools
|
||||
from datetime import datetime
|
||||
|
||||
from config import config
|
||||
from apiclient import client as api
|
||||
|
||||
|
||||
class SkipException(Exception):
|
||||
@ -21,16 +18,21 @@ def get_root_dir():
|
||||
return os.path.split(os.path.realpath(__file__))[0]
|
||||
|
||||
|
||||
def get_filters_dir():
|
||||
return _mkdir_if_not_exists_and_return_path(os.path.join(get_root_dir(), 'filters'))
|
||||
def get_plugin_dir(plugin_dir_name):
|
||||
return _mkdir_if_not_exists_and_return_path(os.path.join(get_root_dir(), plugin_dir_name))
|
||||
|
||||
|
||||
def get_commands_dir():
|
||||
return _mkdir_if_not_exists_and_return_path(os.path.join(get_root_dir(), 'commands'))
|
||||
|
||||
|
||||
def get_nl_processors_dir():
|
||||
return _mkdir_if_not_exists_and_return_path(os.path.join(get_root_dir(), 'nl_processors'))
|
||||
def load_plugins(plugin_dir_name, module_callback=None):
|
||||
plugin_dir = get_plugin_dir(plugin_dir_name)
|
||||
plugin_files = filter(
|
||||
lambda filename: filename.endswith('.py') and not filename.startswith('_'),
|
||||
os.listdir(plugin_dir)
|
||||
)
|
||||
plugins = [os.path.splitext(file)[0] for file in plugin_files]
|
||||
for mod_name in plugins:
|
||||
mod = importlib.import_module(plugin_dir_name + '.' + mod_name)
|
||||
if module_callback:
|
||||
module_callback(mod)
|
||||
|
||||
|
||||
def get_db_dir():
|
||||
@ -46,47 +48,13 @@ def get_tmp_dir():
|
||||
|
||||
|
||||
def get_source(ctx_msg):
|
||||
"""
|
||||
Source is used to distinguish the interactive sessions.
|
||||
Note: This value may change after restarting the bot.
|
||||
|
||||
:return: a 32 character unique string (md5) representing a source, or a random value if something strange happened
|
||||
"""
|
||||
source = None
|
||||
if ctx_msg.get('via') == 'qq':
|
||||
if ctx_msg.get('type') == 'group_message' and ctx_msg.get('group_uid') and ctx_msg.get('sender_uid'):
|
||||
source = 'g' + ctx_msg.get('group_uid') + 'p' + ctx_msg.get('sender_uid')
|
||||
elif ctx_msg.get('type') == 'discuss_message' and ctx_msg.get('discuss_id') and ctx_msg.get('sender_uid'):
|
||||
source = 'd' + ctx_msg.get('discuss_id') + 'p' + ctx_msg.get('sender_uid')
|
||||
elif ctx_msg.get('type') == 'friend_message' and ctx_msg.get('sender_uid'):
|
||||
source = 'p' + ctx_msg.get('sender_uid')
|
||||
elif ctx_msg.get('via') == 'wx':
|
||||
if ctx_msg.get('type') == 'group_message' and ctx_msg.get('group_id') and ctx_msg.get('sender_id'):
|
||||
source = 'g' + ctx_msg.get('group_id') + 'p' + ctx_msg.get('sender_id')
|
||||
elif ctx_msg.get('type') == 'friend_message' and ctx_msg.get('sender_id'):
|
||||
source = 'p' + ctx_msg.get('sender_id')
|
||||
if not source:
|
||||
source = str(int(datetime.now().timestamp())) + str(random.randint(100, 999))
|
||||
return hashlib.md5(source.encode('utf-8')).hexdigest()
|
||||
from msg_src_adapter import get_adapter_by_ctx
|
||||
return get_adapter_by_ctx(ctx_msg).get_source(ctx_msg)
|
||||
|
||||
|
||||
def get_target(ctx_msg):
|
||||
"""
|
||||
Target is used to distinguish the records in database.
|
||||
Note: This value will not change after restarting the bot.
|
||||
|
||||
:return: an unique string (account id with some flags) representing a target,
|
||||
or None if there is no persistent unique value
|
||||
"""
|
||||
if ctx_msg.get('via') == 'qq':
|
||||
if ctx_msg.get('type') == 'group_message' and ctx_msg.get('group_uid'):
|
||||
return 'g' + ctx_msg.get('group_uid')
|
||||
elif ctx_msg.get('type') == 'friend_message' and ctx_msg.get('sender_uid'):
|
||||
return 'p' + ctx_msg.get('sender_uid')
|
||||
elif ctx_msg.get('via') == 'wx':
|
||||
if ctx_msg.get('type') == 'friend_message' and ctx_msg.get('sender_account'):
|
||||
return 'p' + ctx_msg.get('sender_account')
|
||||
return None
|
||||
from msg_src_adapter import get_adapter_by_ctx
|
||||
return get_adapter_by_ctx(ctx_msg).get_target(ctx_msg)
|
||||
|
||||
|
||||
def check_target(func):
|
||||
@ -96,9 +64,11 @@ def check_target(func):
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(args_text, ctx_msg, *args, **kwargs):
|
||||
target = get_target(ctx_msg)
|
||||
from msg_src_adapter import get_adapter_by_ctx
|
||||
adapter = get_adapter_by_ctx(ctx_msg)
|
||||
target = adapter.get_target(ctx_msg)
|
||||
if not target:
|
||||
api.send_message('当前语境无法使用这个命令,请尝试发送私聊消息或稍后再试吧~', ctx_msg)
|
||||
adapter.send_message(ctx_msg, '当前语境无法使用这个命令,请尝试发送私聊消息或稍后再试吧~')
|
||||
return
|
||||
else:
|
||||
return func(args_text, ctx_msg, *args, **kwargs)
|
||||
@ -128,3 +98,7 @@ def get_fallback_command():
|
||||
|
||||
def get_fallback_command_after_nl_processors():
|
||||
return config.get('fallback_command_after_nl_processors')
|
||||
|
||||
|
||||
def get_message_sources():
|
||||
return config.get('message_sources', [])
|
||||
|
131
msg_src_adapter.py
Normal file
131
msg_src_adapter.py
Normal file
@ -0,0 +1,131 @@
|
||||
import hashlib
|
||||
import random
|
||||
from datetime import datetime
|
||||
|
||||
from little_shit import get_message_sources
|
||||
|
||||
_adapter_classes = {} # one message source, one adapter class
|
||||
_adapter_instances = {} # one login account, one adapter instance
|
||||
|
||||
|
||||
def as_adapter(via):
|
||||
def decorator(cls):
|
||||
_adapter_classes[via] = cls
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class Adapter(object):
|
||||
def __init__(self, config: dict):
|
||||
self.login_id = config.get('login_id')
|
||||
self.superuser_id = config.get('superuser_id')
|
||||
|
||||
def unitize_context(self, ctx_msg: dict):
|
||||
return None
|
||||
|
||||
def send_message(self, target: dict, content):
|
||||
if target is None or content is None:
|
||||
return
|
||||
|
||||
msg_type = target.get('msg_type', 'private')
|
||||
if msg_type == 'group' and hasattr(self, 'send_group_message'):
|
||||
self.send_group_message(target, content)
|
||||
elif msg_type == 'discuss' and hasattr(self, 'send_discuss_message'):
|
||||
self.send_discuss_message(target, content)
|
||||
elif msg_type == 'private' and hasattr(self, 'send_private_message'):
|
||||
if 'user_id' not in target and 'sender_id' in target:
|
||||
target['user_id'] = target['sender_id'] # compatible with ctx_msg
|
||||
elif 'user_tid' not in target and 'sender_tid' in target:
|
||||
target['user_tid'] = target.get('sender_tid') # compatible with ctx_msg
|
||||
|
||||
self.send_private_message(target, content)
|
||||
|
||||
if 'user_id' in target and 'sender_id' in target:
|
||||
del target['user_id']
|
||||
elif 'user_tid' in target and 'sender_tid' in target:
|
||||
del target['user_tid']
|
||||
|
||||
def get_login_info(self, ctx_msg: dict):
|
||||
return {'user_id': ctx_msg.get('login_id')}
|
||||
|
||||
def is_sender_superuser(self, ctx_msg: dict):
|
||||
return ctx_msg.get('sender_id') == self.superuser_id
|
||||
|
||||
def get_sender_group_role(self, ctx_msg: dict):
|
||||
return 'member'
|
||||
|
||||
@staticmethod
|
||||
def get_target(ctx_msg: dict):
|
||||
"""
|
||||
Target is used to distinguish the records in database.
|
||||
Note: This value will not change after restarting the bot.
|
||||
|
||||
:return: an unique string (account id with some flags) representing a target,
|
||||
or None if there is no persistent unique value
|
||||
"""
|
||||
if ctx_msg.get('msg_type') == 'group' and ctx_msg.get('group_id'):
|
||||
return 'g#' + ctx_msg.get('group_id')
|
||||
elif ctx_msg.get('msg_type') == 'discuss' and ctx_msg.get('discuss_id'):
|
||||
return 'd#' + ctx_msg.get('discuss_id')
|
||||
elif ctx_msg.get('msg_type') == 'private' and ctx_msg.get('sender_id'):
|
||||
return 'p#' + ctx_msg.get('sender_id')
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_source(ctx_msg: dict):
|
||||
"""
|
||||
Source is used to distinguish the interactive sessions.
|
||||
Note: This value may change after restarting the bot.
|
||||
|
||||
:return: a 32 character unique string (md5) representing a source, or a random value if something strange happened
|
||||
"""
|
||||
source = None
|
||||
if ctx_msg.get('msg_type') == 'group' and ctx_msg.get('group_tid') and ctx_msg.get('sender_tid'):
|
||||
source = 'g#' + ctx_msg.get('group_tid') + '#p#' + ctx_msg.get('sender_tid')
|
||||
elif ctx_msg.get('msg_type') == 'discuss' and ctx_msg.get('discuss_tid') and ctx_msg.get('sender_tid'):
|
||||
source = 'd#' + ctx_msg.get('discuss_tid') + '#p#' + ctx_msg.get('sender_tid')
|
||||
elif ctx_msg.get('msg_type') == 'private' and ctx_msg.get('sender_tid'):
|
||||
source = 'p#' + ctx_msg.get('sender_tid')
|
||||
if not source:
|
||||
source = str(int(datetime.now().timestamp())) + str(random.randint(100, 999))
|
||||
return hashlib.md5(source.encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
class ConfigurationError(KeyError):
|
||||
pass
|
||||
|
||||
|
||||
def get_adapter_by_ctx(ctx_msg: dict):
|
||||
if ctx_msg:
|
||||
via = ctx_msg.get('via')
|
||||
login_id = ctx_msg.get('login_id')
|
||||
return get_adapter(via, login_id)
|
||||
return None
|
||||
|
||||
|
||||
def get_adapter(via: str, login_id: str):
|
||||
if via == 'default':
|
||||
# For the situations where 'via' does not matter, e.g. when we just want 'get_target' (which is universal)
|
||||
if 'default' in _adapter_instances:
|
||||
return _adapter_instances['default']
|
||||
else:
|
||||
_adapter_instances['default'] = Adapter({})
|
||||
return _adapter_instances['default']
|
||||
|
||||
if not (via and login_id):
|
||||
return None
|
||||
|
||||
key = hashlib.md5(via.encode('utf-8') + login_id.encode('utf-8')).hexdigest()
|
||||
if key in _adapter_instances:
|
||||
return _adapter_instances[key]
|
||||
else:
|
||||
msg_src_list = list(filter(
|
||||
lambda msg_src: msg_src['via'] == via and msg_src['login_id'] == login_id,
|
||||
get_message_sources()
|
||||
))
|
||||
if len(msg_src_list):
|
||||
_adapter_instances[key] = _adapter_classes[via](msg_src_list[0])
|
||||
return _adapter_instances[key]
|
||||
else:
|
||||
return None
|
109
msg_src_adapters/mojo_webqq.py
Normal file
109
msg_src_adapters/mojo_webqq.py
Normal file
@ -0,0 +1,109 @@
|
||||
import requests
|
||||
|
||||
from msg_src_adapter import Adapter, as_adapter, ConfigurationError
|
||||
|
||||
|
||||
@as_adapter(via='mojo_webqq')
|
||||
class MojoWebqqAdapter(Adapter):
|
||||
def __init__(self, config: dict):
|
||||
super().__init__(config)
|
||||
if not config.get('api_url'):
|
||||
raise ConfigurationError
|
||||
self.api_url = config['api_url']
|
||||
|
||||
def unitize_context(self, ctx_msg: dict):
|
||||
new_ctx = {'raw_ctx': ctx_msg, 'post_type': ctx_msg['post_type'], 'via': ctx_msg['via'],
|
||||
'login_id': ctx_msg['login_id']}
|
||||
if new_ctx['post_type'] != 'receive_message':
|
||||
return new_ctx
|
||||
new_ctx['post_type'] = 'message' # Just handle 'receive_message', and make 'post_type' 'message'
|
||||
new_ctx['time'] = ctx_msg['time']
|
||||
new_ctx['msg_id'] = str(ctx_msg['id'])
|
||||
new_ctx['msg_type'] = ctx_msg['type'].split('_')[0]
|
||||
new_ctx['msg_type'] = 'private' if new_ctx['msg_type'] == 'friend' else new_ctx['msg_type']
|
||||
new_ctx['format'] = 'text'
|
||||
new_ctx['content'] = ctx_msg['content']
|
||||
|
||||
new_ctx['receiver'] = ctx_msg.get('receiver', '')
|
||||
new_ctx['receiver_name'] = (requests.get(self.api_url + '/get_user_info').json() or {}).get('name', '')
|
||||
new_ctx['receiver_id'] = str(ctx_msg.get('receiver_uid', ''))
|
||||
new_ctx['receiver_tid'] = str(ctx_msg.get('receiver_id', ''))
|
||||
|
||||
new_ctx['sender'] = ctx_msg.get('sender', '')
|
||||
friend = list(filter(
|
||||
lambda f: f.get('uid') == ctx_msg['sender_uid'],
|
||||
requests.get(self.api_url + '/get_friend_info').json() or []
|
||||
))
|
||||
new_ctx['sender_name'] = friend[0].get('name', '') if friend else ''
|
||||
new_ctx['sender_id'] = str(ctx_msg.get('sender_uid', ''))
|
||||
new_ctx['sender_tid'] = str(ctx_msg.get('sender_id', ''))
|
||||
|
||||
if new_ctx['msg_type'] == 'group':
|
||||
new_ctx['group'] = ctx_msg.get('group', '')
|
||||
new_ctx['group_id'] = str(ctx_msg.get('group_uid', ''))
|
||||
new_ctx['group_tid'] = str(ctx_msg.get('group_id', ''))
|
||||
|
||||
if new_ctx['msg_type'] == 'discuss':
|
||||
new_ctx['discuss'] = ctx_msg.get('discuss', '')
|
||||
new_ctx['discuss_tid'] = str(ctx_msg.get('discuss_id', ''))
|
||||
|
||||
return new_ctx
|
||||
|
||||
def get_login_info(self, ctx_msg: dict):
|
||||
json = requests.get(self.api_url + '/get_user_info').json()
|
||||
if json:
|
||||
json['user_tid'] = json.get('id')
|
||||
json['user_id'] = json.get('uid')
|
||||
json['nickname'] = json.get('name')
|
||||
return json
|
||||
|
||||
def _get_group_info(self):
|
||||
return requests.get(self.api_url + '/get_group_info').json()
|
||||
|
||||
def get_sender_group_role(self, ctx_msg: dict):
|
||||
groups = list(filter(
|
||||
lambda g: str(g.get('id')) == ctx_msg['raw_ctx'].get('group_id'),
|
||||
self._get_group_info() or []
|
||||
))
|
||||
if len(groups) <= 0 or 'member' not in groups[0]:
|
||||
# This is strange, not likely happens
|
||||
return 'member'
|
||||
members = list(filter(
|
||||
lambda m: str(m.get('id')) == ctx_msg['raw_ctx'].get('sender_id'),
|
||||
groups[0].get('member')
|
||||
))
|
||||
if len(members) <= 0:
|
||||
# This is strange, not likely happens
|
||||
return 'member'
|
||||
return members[0].get('role', 'member')
|
||||
|
||||
def send_private_message(self, target: dict, content: str):
|
||||
params = None
|
||||
if target.get('user_id'):
|
||||
params = {'uid': target.get('user_id')}
|
||||
elif target.get('user_tid'):
|
||||
params = {'id': target.get('user_tid')}
|
||||
|
||||
if params:
|
||||
params['content'] = content
|
||||
requests.get(self.api_url + '/send_friend_message', params=params)
|
||||
|
||||
def send_group_message(self, target: dict, content: str):
|
||||
params = None
|
||||
if target.get('group_id'):
|
||||
params = {'uid': target.get('group_id')}
|
||||
elif target.get('group_tid'):
|
||||
params = {'id': target.get('group_tid')}
|
||||
|
||||
if params:
|
||||
params['content'] = content
|
||||
requests.get(self.api_url + '/send_group_message', params=params)
|
||||
|
||||
def send_discuss_message(self, target: dict, content: str):
|
||||
params = None
|
||||
if target.get('discuss_tid'):
|
||||
params = {'id': target.get('discuss_tid')}
|
||||
|
||||
if params:
|
||||
params['content'] = content
|
||||
requests.get(self.api_url + '/send_discuss_message', params=params)
|
@ -19,12 +19,13 @@ def as_processor(keywords=None):
|
||||
|
||||
def parse_potential_commands(sentence):
|
||||
segmentation = list(jieba.posseg.cut(sentence=sentence))
|
||||
print('分词结果:', segmentation)
|
||||
print('分词结果:', ['[' + s.flag + ']' + s.word for s in segmentation])
|
||||
potential_commands = []
|
||||
for processor in _processors:
|
||||
processed = False
|
||||
for regex in processor[0]:
|
||||
for word, flag in segmentation:
|
||||
for s in segmentation:
|
||||
word, flag = s.word, s.flag
|
||||
if re.search(regex, word):
|
||||
result = processor[1](sentence, segmentation)
|
||||
if result:
|
||||
|
Loading…
x
Reference in New Issue
Block a user