基本完成QQ指令部分

This commit is contained in:
San Liy 2023-12-20 20:47:07 +08:00
parent dfd095e783
commit 568cd50c3c
8 changed files with 2796 additions and 52 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@
/config.toml /config.toml
/temp.json /temp.json
/db.*

2639
cmdparser.py Normal file

File diff suppressed because it is too large Load Diff

41
cmds.py Normal file
View File

@ -0,0 +1,41 @@
import secrets
from models import User
from sio_model import SioDecorator, Message, SioRequest
def cmds(app, data):
# 此处会检查注册的指令是否重复
sio_decorator = SioDecorator(app, data)
@sio_decorator.cmd('ping')
async def ping(sqt: SioRequest):
msg = Message(content='pong', room_id=sqt.room_id)
await sqt.app.ctx.sio.emit('sendMessage', msg.to_json())
@sio_decorator.cmd('gitea')
async def gitea(sqt: SioRequest):
parser = sqt.parser
parser.add_argument('-vd', '--validate', help='绑定QQ与gitea',
action="store_true")
args = parser.parse_args(sqt.args)
if args.validate:
state = secrets.token_urlsafe(16)
user = await User.filter(id=sqt.sender_id).get_or_none()
if user:
user.name = sqt.sender_name
user.state = state
await user.save()
else:
user = User(id=sqt.sender_id, name=sqt.sender_name, state=state)
await user.save()
url = (f'{app.ctx.sio_config.validate_host}/login/oauth/authorize?'
f'client_id={app.ctx.sio_config.client_id}&'
f'redirect_uri={app.ctx.sio_config.localhost}/redirect&'
f'response_type=code&state={state}')
msg = Message(content=f'click: \n{url}', room_id=sqt.room_id)
await sqt.app.ctx.sio.emit('sendMessage', msg.to_json())
if len(sqt.args) == 0:
parser.parse_args(["-h"])
return sio_decorator

View File

@ -1,15 +0,0 @@
from typing import Dict, Any, Optional
from model import Message
def command_convert(data: Dict[str, Any]) -> Optional[Message]:
match (data.get('message').get('content')):
case '/bot': # 测试存活
return Message(content='icalingua bot test', room_id=data['roomId'])
case ['/gitea validate', '-v']:
return Message()
case _:
return None

View File

@ -3,14 +3,14 @@ from tortoise.models import Model
class User(Model): class User(Model):
id = fields.IntField(pk=True) id = fields.BigIntField(pk=True, autoincrement=False)
name = fields.CharField(max_length=60) name = fields.CharField(max_length=60)
gusers = fields.ForeignKeyField('models.GiteaUser') state = fields.CharField(max_length=60)
state = fields.CharField(max_length=20)
class GiteaUser(Model): class GiteaUser(Model):
id = fields.IntField(pk=True) id = fields.BigIntField(pk=True, autoincrement=False)
qid = fields.ForeignKeyField('models.GiteaUser')
name = fields.CharField(max_length=20) name = fields.CharField(max_length=20)
ac_tk = fields.CharField(max_length=256) ac_tk = fields.CharField(max_length=256)
rfs_tk = fields.CharField(max_length=256) rfs_tk = fields.CharField(max_length=256)

13
orm.py
View File

@ -1,13 +0,0 @@
from tortoise import Model, fields
class User(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=60)
state = fields.CharField(max_length=20)
class UserGroup(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=60)
user = fields.ForeignKeyField('orm.User')

View File

@ -7,9 +7,10 @@ from nacl.signing import SigningKey
from sanic import Sanic, Request from sanic import Sanic, Request
from sanic.log import logger, Colors from sanic.log import logger, Colors
from sanic.response import text from sanic.response import text
from socketio import AsyncClient
from tortoise.contrib.sanic import register_tortoise from tortoise.contrib.sanic import register_tortoise
from command import command_convert import cmds
from gitea_model import WebHookIssueComment, WebHookIssue, GiteaEvent from gitea_model import WebHookIssueComment, WebHookIssue, GiteaEvent
from sio_model import Ctx, SioConfig, Message from sio_model import Ctx, SioConfig, Message
from unit import sio_log_format, int2str, cas_log_fmt from unit import sio_log_format, int2str, cas_log_fmt
@ -37,7 +38,6 @@ async def setup_before_start(_app):
# 使用casbin策略管理 # 使用casbin策略管理
adapter = TortoiseAdapter() adapter = TortoiseAdapter()
e = casbin.AsyncEnforcer('./casbin_data/model.conf', adapter) e = casbin.AsyncEnforcer('./casbin_data/model.conf', adapter)
# e = casbin.Enforcer('./casbin_data/model.conf', './casbin_data/casbin.csv')
_app.ctx.e = e _app.ctx.e = e
t1 = await _app.ctx.e.add_policy('admin', '*') t1 = await _app.ctx.e.add_policy('admin', '*')
@ -53,14 +53,13 @@ async def setup_before_start(_app):
for u in list(rm_user): for u in list(rm_user):
if await _app.ctx.e.delete_user(u): if await _app.ctx.e.delete_user(u):
logger.debug(f'Delete {Colors.PURPLE}{u}{Colors.YELLOW} for group admin') logger.debug(f'Delete {Colors.PURPLE}{u}{Colors.YELLOW} for group admin')
await _app.ctx.e.save_policy() await _app.ctx.e.save_policy()
# 初始化sio # 初始化sio
# _app.ctx.sio = AsyncClient() _app.ctx.sio = AsyncClient()
# start_sio_listener() start_sio_listener()
#
# await _app.ctx.sio.connect(_app.ctx.sio_config.host) await _app.ctx.sio.connect(_app.ctx.sio_config.host)
@app.post('/receive') @app.post('/receive')
@ -184,14 +183,24 @@ def start_sio_listener():
@app.ctx.sio.on('addMessage') @app.ctx.sio.on('addMessage')
async def add_message(data: Dict[str, Any]): async def add_message(data: Dict[str, Any]):
logger.debug(sio_log_format('add_message:', data)) sio_decorator = cmds.cmds(app, data)
await sio_decorator.route2cmd_and_run()
sender_id = data['message']['senderId'] # @app.ctx.sio.on('addMessage')
# @SioDecorator.cmd('gitea', app)
if sender_id != app.ctx.sio_config.self_id: # async def gitea(sqt: SioRequest):
msg = await command_convert(data, app) # parser = sqt.parser
if msg is not None: # parser.add_argument('-vd', help='绑定QQ与gitea')
await app.ctx.sio.emit('sendMessage', msg.to_json()) # args = parser.parse_args(sqt.args)
# if args.vd:
# state = secrets.token_urlsafe(16)
# user = User(id=sqt.sender_id, name=sqt.sender_name, state=state)
# await user.save()
# url = (f'{app.ctx.sio_config.validate_host}/login/oauth/authorize?'
# f'client_id={app.ctx.sio_config.client_id}&'
# f'redirect_uri={app.ctx.sio_config.localhost}/redirect&'
# f'response_type=code&state={state}')
# return Message(content=f'click: \n{url}')
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,17 +1,24 @@
import shlex
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, List, Union, Literal from functools import wraps
from typing import Optional, List, Union, Literal, Dict, Any
from lib_not_dr.types import Options from casbin import Enforcer
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, model_validator
from sanic import Sanic
from sanic.log import logger
from socketio import AsyncClient from socketio import AsyncClient
from cmdparser import ArgumentParser, PrintMessage
from unit import sio_log_format
class AtElement(Options):
class AtElement(BaseModel):
text: str text: str
id: Union[int, Literal['all']] = 'all' id: Union[int, Literal['all']] = 'all'
class ReplyMessage(Options): class ReplyMessage(BaseModel):
id: str id: str
username: str = '' username: str = ''
content: str = '' content: str = ''
@ -26,7 +33,7 @@ class ReplyMessage(Options):
} }
class Message(Options): class Message(BaseModel):
content: str content: str
room_id: Optional[int] = None room_id: Optional[int] = None
room: Optional[int] = None # room id 和 room 二选一 ( 实际上直接填 room id 就行了 ) room: Optional[int] = None # room id 和 room 二选一 ( 实际上直接填 room id 就行了 )
@ -69,7 +76,81 @@ class SioConfig(BaseModel):
db_url: str db_url: str
class CmdExists(Exception):
...
class SioDecorator:
def __init__(self, app: Sanic, data: Dict[str, Any]):
logger.debug(sio_log_format('add_message:', data))
self._content = data['message']['content']
self._cmd = shlex.split(self._content)
self.app = app
self.data = data
self.cmds = {}
def cmd(self, cmd_key: str):
def decorator(func):
self._cmds_append(cmd_key, func)
@wraps(func)
async def wrapper(*args, **kwargs): # args 无参数名的 kw 有参数名的
...
return wrapper
return decorator
def _cmds_append(self, cmd, func):
if cmd in self.cmds.keys():
raise CmdExists(
f"Command already registered: /{cmd}"
)
else:
self.cmds[f'/{cmd}'] = func
async def route2cmd_and_run(self):
func = self.cmds.get(self._cmd[0])
if func:
sender_id = self.data['message']['senderId']
sender_name = self.data['message']['username']
room_id = self.data['roomId']
if sender_id != self.app.ctx.sio_config.self_id:
parser = ArgumentParser(self._cmd[0])
sqt = SioRequest(app=self.app,
parser=parser,
args=self._cmd[1:],
key=self._cmd[0],
sender_id=sender_id,
sender_name=sender_name,
content=self._content,
room_id=room_id,
data=self.data)
try:
await func(sqt)
except PrintMessage as e:
msg = Message(content=str(e), room_id=room_id)
await self.app.ctx.sio.emit('sendMessage', msg.to_json())
@dataclass @dataclass
class Ctx: class Ctx:
sio: AsyncClient sio: AsyncClient
sio_config: SioConfig sio_config: SioConfig
e: Enforcer
sio_decorator: SioDecorator
@dataclass
class SioRequest:
app: Sanic = None
parser: Optional[ArgumentParser] = None
args: Optional[list] = None
key: Optional[str] = None
data: Optional[Dict[str, Any]] = None
sender_id: Optional[int] = None
sender_name: str = ''
content: str = ''
room_id: Optional[int] = None