Lots of updates

This commit is contained in:
Richard Chien 2018-07-01 11:01:24 +08:00
parent 1835a4c33d
commit a13128f356
9 changed files with 162 additions and 90 deletions

View File

@ -74,7 +74,7 @@ def load_builtin_plugins():
load_plugins(plugin_dir, 'none.plugins') load_plugins(plugin_dir, 'none.plugins')
from .command import on_command, CommandSession from .command import on_command, CommandSession, CommandGroup
from .notice_request import ( from .notice_request import (
on_notice, NoticeSession, on_notice, NoticeSession,
on_request, RequestSession, on_request, RequestSession,

View File

@ -5,13 +5,13 @@ from typing import (
Tuple, Union, Callable, Iterable, Dict, Any, Optional, Sequence Tuple, Union, Callable, Iterable, Dict, Any, Optional, Sequence
) )
from aiocqhttp import CQHttp, Error as CQHttpError from aiocqhttp import CQHttp
from aiocqhttp.message import Message from aiocqhttp.message import Message
from . import permissions as perm from . import permission as perm
from ..helpers import context_source from .helpers import context_source
from ..expression import render from .expression import render
from ..session import BaseSession from .session import BaseSession
# Key: str (one segment of command name) # Key: str (one segment of command name)
# Value: subtree or a leaf Command object # Value: subtree or a leaf Command object
@ -37,17 +37,20 @@ class Command:
self.only_to_me = only_to_me self.only_to_me = only_to_me
self.args_parser_func = None self.args_parser_func = None
async def run(self, session, *, permission: Optional[int] = None) -> bool: async def run(self, session, check_perm: bool = True) -> bool:
""" """
Run the command in a given session. Run the command in a given session.
:param session: CommandSession object :param session: CommandSession object
:param permission: the permission the caller owns :param check_perm: should check permission before running
:return: the command is finished :return: the command is finished
""" """
if permission is None: if check_perm:
permission = await _calculate_permission(session.bot, session.ctx) has_perm = await perm.check_permission(
if self.func and permission & self.permission: session.bot, session.ctx, self.permission)
else:
has_perm = True
if self.func and has_perm:
if self.args_parser_func: if self.args_parser_func:
await self.args_parser_func(session) await self.args_parser_func(session)
await self.func(session) await self.func(session)
@ -55,48 +58,6 @@ class Command:
return False return False
async def _calculate_permission(bot: CQHttp, ctx: Dict[str, Any]) -> int:
"""
Calculate the permission OWNED by the current context.
This is different from the permission REQUIRED by a command.
The result of this function should be made a bit-and with
the permission required by some command to check whether
the context is allowed to call the command.
:param bot: CQHttp instance
:param ctx: message context
:return: the calculated permission value
"""
permission = 0
if ctx['user_id'] in bot.config.SUPERUSERS:
permission |= perm.IS_SUPERUSER
if ctx['message_type'] == 'private':
if ctx['sub_type'] == 'friend':
permission |= perm.IS_PRIVATE_FRIEND
elif ctx['sub_type'] == 'group':
permission |= perm.IS_PRIVATE_GROUP
elif ctx['sub_type'] == 'discuss':
permission |= perm.IS_PRIVATE_DISCUSS
elif ctx['sub_type'] == 'other':
permission |= perm.IS_PRIVATE_OTHER
elif ctx['message_type'] == 'group':
permission |= perm.IS_GROUP_MEMBER
if not ctx['anonymous']:
try:
member_info = await bot.get_group_member_info(**ctx)
if member_info:
if member_info['role'] == 'owner':
permission |= perm.IS_GROUP_OWNER
elif member_info['role'] == 'admin':
permission |= perm.IS_GROUP_ADMIN
except CQHttpError:
pass
elif ctx['message_type'] == 'discuss':
permission |= perm.IS_DISCUSS
return permission
def on_command(name: Union[str, Tuple[str]], *, def on_command(name: Union[str, Tuple[str]], *,
aliases: Iterable = (), aliases: Iterable = (),
permission: int = perm.EVERYBODY, permission: int = perm.EVERYBODY,
@ -325,10 +286,13 @@ async def handle_command(bot: CQHttp, ctx: Dict[str, Any]) -> bool:
""" """
src = context_source(ctx) src = context_source(ctx)
session = None session = None
check_perm = True
if _sessions.get(src): if _sessions.get(src):
session = _sessions[src] session = _sessions[src]
if session and session.is_valid: if session and session.is_valid:
session.refresh(ctx, current_arg=str(ctx['message'])) session.refresh(ctx, current_arg=str(ctx['message']))
# there is no need to check permission for existing session
check_perm = False
else: else:
# the session is expired, remove it # the session is expired, remove it
del _sessions[src] del _sessions[src]
@ -340,7 +304,7 @@ async def handle_command(bot: CQHttp, ctx: Dict[str, Any]) -> bool:
_sessions[src] = session _sessions[src] = session
try: try:
res = await session.cmd.run(session) res = await session.cmd.run(session, check_perm=check_perm)
# the command is finished, remove the session # the command is finished, remove the session
del _sessions[src] del _sessions[src]
return res return res

View File

@ -1,25 +0,0 @@
PRIVATE_FRIEND = 0x0001
PRIVATE_GROUP = 0x0002
PRIVATE_DISCUSS = 0x0004
PRIVATE_OTHER = 0x0008
PRIVATE = 0x000F
DISCUSS = 0x00F0
GROUP_MEMBER = 0x0100
GROUP_ADMIN = 0x0200
GROUP_OWNER = 0x0400
GROUP = 0x0F00
SUPERUSER = 0xF000
EVERYBODY = 0xFFFF
IS_NOBODY = 0x0000
IS_PRIVATE_FRIEND = PRIVATE_FRIEND
IS_PRIVATE_GROUP = PRIVATE_GROUP
IS_PRIVATE_DISCUSS = PRIVATE_DISCUSS
IS_PRIVATE_OTHER = PRIVATE_OTHER
IS_PRIVATE = PRIVATE
IS_DISCUSS = DISCUSS
IS_GROUP_MEMBER = GROUP_MEMBER
IS_GROUP_ADMIN = GROUP_MEMBER | GROUP_ADMIN
IS_GROUP_OWNER = GROUP_ADMIN | GROUP_OWNER
IS_GROUP = GROUP
IS_SUPERUSER = 0xFFFF

View File

@ -4,8 +4,8 @@ from aiocqhttp import CQHttp
from aiocqhttp.message import MessageSegment from aiocqhttp.message import MessageSegment
from .command import handle_command from .command import handle_command
from .natural_language import handle_natural_language
from .log import logger from .log import logger
from .helpers import send
async def handle_message(bot: CQHttp, ctx: Dict[str, Any]) -> None: async def handle_message(bot: CQHttp, ctx: Dict[str, Any]) -> None:
@ -23,9 +23,10 @@ async def handle_message(bot: CQHttp, ctx: Dict[str, Any]) -> None:
handled = await handle_command(bot, ctx) handled = await handle_command(bot, ctx)
if handled: if handled:
logger.debug('Message is handled as a command') logger.debug('Message is handled as command')
return return
elif ctx['to_me']:
await send(bot, ctx, '你在说什么我看不懂诶')
# TODO: NLP handled = await handle_natural_language(bot, ctx)
if handled:
logger.debug('Message is handled as natural language')
return

22
none/natural_language.py Normal file
View File

@ -0,0 +1,22 @@
from collections import namedtuple
from typing import Dict, Any
from aiocqhttp import CQHttp
_nl_processors = set()
class NLProcessor:
__slots__ = ('func', 'permission', 'only_to_me', 'keywords',
'precondition_func')
NLPResult = namedtuple('NLPResult', (
'confidence',
'cmd_name',
'cmd_args',
))
async def handle_natural_language(bot: CQHttp, ctx: Dict[str, Any]) -> None:
pass

91
none/permission.py Normal file
View File

@ -0,0 +1,91 @@
from collections import namedtuple
from typing import Dict, Any
from aiocache import cached
from aiocqhttp import CQHttp, Error as CQHttpError
PRIVATE_FRIEND = 0x0001
PRIVATE_GROUP = 0x0002
PRIVATE_DISCUSS = 0x0004
PRIVATE_OTHER = 0x0008
PRIVATE = 0x000F
DISCUSS = 0x00F0
GROUP_MEMBER = 0x0100
GROUP_ADMIN = 0x0200
GROUP_OWNER = 0x0400
GROUP = 0x0F00
SUPERUSER = 0xF000
EVERYBODY = 0xFFFF
IS_NOBODY = 0x0000
IS_PRIVATE_FRIEND = PRIVATE_FRIEND
IS_PRIVATE_GROUP = PRIVATE_GROUP
IS_PRIVATE_DISCUSS = PRIVATE_DISCUSS
IS_PRIVATE_OTHER = PRIVATE_OTHER
IS_PRIVATE = PRIVATE
IS_DISCUSS = DISCUSS
IS_GROUP_MEMBER = GROUP_MEMBER
IS_GROUP_ADMIN = GROUP_MEMBER | GROUP_ADMIN
IS_GROUP_OWNER = GROUP_ADMIN | GROUP_OWNER
IS_GROUP = GROUP
IS_SUPERUSER = 0xFFFF
_min_context_fields = (
'message_type',
'sub_type',
'user_id',
'discuss_id',
'group_id',
'anonymous',
)
_MinContext = namedtuple('MinContext', _min_context_fields)
async def check_permission(bot: CQHttp, ctx: Dict[str, Any],
permission_required: int) -> bool:
min_ctx_kwargs = {}
for field in _min_context_fields:
if field in ctx:
min_ctx_kwargs[field] = ctx[field]
else:
min_ctx_kwargs[field] = None
min_ctx = _MinContext(**min_ctx_kwargs)
return await _check(bot, min_ctx, permission_required)
@cached(ttl=2 * 60) # cache the result for 1 minute
async def _check(bot: CQHttp, min_ctx: _MinContext,
permission_required: int) -> bool:
permission = 0
if min_ctx.user_id in bot.config.SUPERUSERS:
permission |= IS_SUPERUSER
if min_ctx.message_type == 'private':
if min_ctx.sub_type == 'friend':
permission |= IS_PRIVATE_FRIEND
elif min_ctx.sub_type == 'group':
permission |= IS_PRIVATE_GROUP
elif min_ctx.sub_type == 'discuss':
permission |= IS_PRIVATE_DISCUSS
elif min_ctx.sub_type == 'other':
permission |= IS_PRIVATE_OTHER
elif min_ctx.message_type == 'group':
permission |= IS_GROUP_MEMBER
if not min_ctx.anonymous:
try:
member_info = await bot.get_group_member_info(
group_id=min_ctx.group_id,
user_id=min_ctx.user_id,
no_cache=True
)
if member_info:
if member_info['role'] == 'owner':
permission |= IS_GROUP_OWNER
elif member_info['role'] == 'admin':
permission |= IS_GROUP_ADMIN
except CQHttpError:
pass
elif min_ctx.message_type == 'discuss':
permission |= IS_DISCUSS
return bool(permission & permission_required)

View File

@ -1,15 +1,14 @@
from aiocqhttp.message import unescape from aiocqhttp.message import unescape
from none import on_command, CommandSession from none import on_command, CommandSession, permission as perm
from none.command import permissions as perm
@on_command('echo', only_to_me=False) @on_command('echo', only_to_me=False)
async def echo(session: CommandSession): async def echo(session: CommandSession):
await session.send(session.args.get('text') or session.current_arg) await session.send(session.args.get('message') or session.current_arg)
@on_command('say', permission=perm.SUPERUSER, only_to_me=False) @on_command('say', permission=perm.SUPERUSER, only_to_me=False)
async def _(session: CommandSession): async def _(session: CommandSession):
await session.send( await session.send(
unescape(session.args.get('text') or session.current_arg)) unescape(session.args.get('message') or session.current_arg))

View File

@ -1,4 +1,4 @@
from none.command import CommandSession, CommandGroup from none import CommandSession, CommandGroup
from . import expressions as expr from . import expressions as expr
@ -17,6 +17,26 @@ async def _(session: CommandSession):
session.args[session.current_key] = session.current_arg_text.strip() session.args[session.current_key] = session.current_arg_text.strip()
# @on_natural_language(keywords={'天气', '雨', '雪', '晴', '阴', '多云', '冰雹'},
# only_to_me=False)
# async def weather_nlp(session: NaturalLanguageSession):
# return NLPResult(89.5, ('weather', 'weather'), {'city': '南京'})
#
#
# @weather_nlp.condition
# async def _(session: NaturalLanguageSession):
# keywords = {'天气', '雨', '雪', '晴', '阴', '多云', '冰雹'}
# for kw in keywords:
# if kw in session.text:
# keyword_hit = True
# break
# else:
# keyword_hit = False
# if session.ctx['to_me'] and keyword_hit:
# return True
# return False
@w.command('suggestion', aliases=('生活指数', '生活建议', '生活提示')) @w.command('suggestion', aliases=('生活指数', '生活建议', '生活提示'))
async def suggestion(session: CommandSession): async def suggestion(session: CommandSession):
await session.send('suggestion') await session.send('suggestion')

View File

@ -14,7 +14,7 @@ setup(
description='A QQ bot framework', description='A QQ bot framework',
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
install_requires=['aiocqhttp>=0.3'], install_requires=['aiocqhttp>=0.3', 'aiocache'],
python_requires='>=3.6', python_requires='>=3.6',
platforms='any', platforms='any',
classifiers=( classifiers=(