├── src └── graia │ └── application │ ├── message │ ├── parser │ │ ├── __init__.py │ │ ├── pattern.py │ │ ├── signature.py │ │ ├── pack.py │ │ └── literature.py │ ├── __init__.py │ └── elements │ │ ├── external.py │ │ └── __init__.py │ ├── test │ ├── __init__.py │ └── request_tracing.py │ ├── friend.py │ ├── entities.py │ ├── event │ ├── dispatcher.py │ ├── network.py │ ├── __init__.py │ ├── lifecycle.py │ └── messages.py │ ├── logger.py │ ├── context.py │ ├── exceptions.py │ ├── entry.py │ ├── group.py │ ├── session.py │ ├── interrupts.py │ └── utilles.py ├── setup.py ├── .vscode ├── settings.json └── launch.json ├── .pre-commit-config.yaml ├── .github ├── ISSUE_TEMPLATE │ └── ----.md └── workflows │ └── black-lint.yml ├── pyproject.toml ├── CONTRIBUTING.md ├── .gitignore ├── README.md └── docs └── graia ├── index.html ├── broadcast ├── typing.html ├── interfaces │ └── index.html ├── builtin │ ├── index.html │ └── event.html ├── entities │ ├── decorator.html │ ├── index.html │ ├── namespace.html │ └── listener.html └── priority.html └── application ├── test └── index.html ├── message ├── parser │ └── index.html └── index.html ├── entry.html ├── friend.html ├── context.html ├── event └── dispatcher.html └── entities.html /src/graia/application/message/parser/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/graia/application/test/__init__.py: -------------------------------------------------------------------------------- 1 | """这里提供用于 Graia 测试使用的快捷工具, 不同的工具在不同的包里面. 2 | 3 | """ 4 | -------------------------------------------------------------------------------- /src/graia/application/message/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class BotMessage(BaseModel): 5 | messageId: int 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | # Github 咋还不支持 pyproject.toml 读依赖啊... 4 | 5 | setup( 6 | name='graia-application-mirai', 7 | ) -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "c:\\Users\\Elaina\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\\graia-application-mirai-s-A4g-JQ-py3.8\\Scripts\\python.exe", 3 | "python.linting.enabled": true 4 | } -------------------------------------------------------------------------------- /src/graia/application/friend.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Friend(BaseModel): 5 | "描述 Tencent QQ 中的可发起对话对象 '好友(friend)' 的能被获取到的信息." 6 | 7 | id: int 8 | nickname: str 9 | remark: str 10 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 20.8b1 # Replace by any tag/version: https://github.com/psf/black/tags 4 | hooks: 5 | - id: black 6 | language_version: python3 # Should be a command that runs python3.6+ -------------------------------------------------------------------------------- /src/graia/application/entities.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pydantic import BaseModel 3 | 4 | 5 | class UploadMethods(Enum): 6 | """用于向 `uploadImage` 或 `uploadVoice` 方法描述图片的上传类型""" 7 | 8 | Friend = "friend" 9 | Group = "group" 10 | Temp = "temp" 11 | 12 | 13 | class MiraiConfig(BaseModel): 14 | """`/config` 接口的序列化实体类""" 15 | 16 | cacheSize: int 17 | enableWebsocket: bool 18 | -------------------------------------------------------------------------------- /src/graia/application/event/dispatcher.py: -------------------------------------------------------------------------------- 1 | from graia.broadcast.entities.dispatcher import BaseDispatcher 2 | from graia.broadcast.interfaces.dispatcher import DispatcherInterface 3 | from graia.application.message.chain import MessageChain 4 | 5 | 6 | class MessageChainCatcher(BaseDispatcher): 7 | @staticmethod 8 | async def catch(interface: "DispatcherInterface"): 9 | if interface.annotation is MessageChain: 10 | return interface.event.messageChain 11 | -------------------------------------------------------------------------------- /src/graia/application/message/parser/pattern.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Optional 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass(init=True, eq=True, repr=True) 6 | class ParamPattern: 7 | longs: List[str] 8 | short: Optional[str] = None 9 | default: Any = None 10 | help_message: Optional[str] = None 11 | 12 | 13 | @dataclass(init=True, eq=True, repr=True) 14 | class SwitchParameter(ParamPattern): 15 | default: bool = False 16 | auto_reverse: bool = False 17 | 18 | 19 | @dataclass(init=True, eq=True, repr=True) 20 | class BoxParameter(ParamPattern): 21 | "可以被指定传入消息的参数, 但只有一个." 22 | -------------------------------------------------------------------------------- /src/graia/application/event/network.py: -------------------------------------------------------------------------------- 1 | from graia.broadcast import BaseDispatcher 2 | from graia.broadcast.entities.event import Dispatchable 3 | from . import ApplicationDispatcher, EmptyDispatcher 4 | 5 | 6 | class SessionRefreshed(Dispatchable): 7 | "网络异常: 检测到无效的 Session 并自动刷新, 事件发布时已经刷新成为有效的 Session." 8 | 9 | Dispatcher = EmptyDispatcher 10 | 11 | 12 | class SessionRefreshFailed(Dispatchable): 13 | "网络异常: 检测到无效的 Session 并尝试自动刷新失败." 14 | 15 | Dispatcher = EmptyDispatcher 16 | 17 | 18 | class RemoteException(Dispatchable): 19 | "网络异常: 无头客户端处发生错误, 你应该检查其输出的错误日志." 20 | 21 | Dispatcher = EmptyDispatcher 22 | 23 | 24 | class InvaildRequest(Dispatchable): 25 | "网络异常: 意料之外地, 发出了不被无头客户端接收的 HTTP 请求, 你应该通过相应渠道向我们汇报此问题" 26 | 27 | Dispatcher = EmptyDispatcher 28 | -------------------------------------------------------------------------------- /src/graia/application/message/elements/external.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from . import ExternalElement 3 | 4 | 5 | class Image(ExternalElement): 6 | type: str = "Image" 7 | imageId: Optional[str] = None 8 | url: Optional[str] = None 9 | 10 | def asSerializationString(self) -> str: 11 | return f"[mirai:image:{self.imageId}]" 12 | 13 | 14 | class FlashImage(Image, ExternalElement): 15 | type = "FlashImage" 16 | 17 | def asSerializationString(self) -> str: 18 | return f"[mirai:flash:{self.imageId}]" 19 | 20 | 21 | class Voice(ExternalElement): 22 | type = "Voice" 23 | voiceId: Optional[str] = None 24 | url: Optional[str] = None 25 | 26 | def asSerializationString(self) -> str: 27 | return f"[mirai:voice:{self.voiceId}]" 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题汇报 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **描述你遇到的问题:** 11 | 12 | 13 | **复现步骤:** 14 | 该 BUG 会在进行以下操作后出现: 15 | 1. ... 16 | 2. ... 17 | 18 | **发生错误的代码** 19 | 20 | 21 | **控制台日志输出截图:** 22 | 23 | 24 | 25 | **运行环境:** 26 | - 操作系统: 27 | - `mirai-core` 版本: 28 | - `mirai-api-http` 版本: 29 | - `graia-application-mirai` 版本: 30 | - 是否出现在机器人长期运行后: 31 | 32 | **额外信息:** 33 | 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "graia-application-mirai" 3 | version = "0.20.1" 4 | description = "" 5 | authors = ["GreyElaina "] 6 | license = "AGPL-3.0" 7 | packages = [ 8 | { include = "graia", from = "src" } 9 | ] 10 | 11 | [[tool.poetry.source]] 12 | name = "tuna-tsinghua" 13 | default = false 14 | url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" 15 | 16 | [tool.poetry.dependencies] 17 | python = "^3.6" 18 | pydantic = "^1.8.2" 19 | graia-broadcast = "^0.11.0" 20 | aiohttp = "^3.6.2" 21 | yarl = "^1.4.2" 22 | contextvars = {version = "^2.4", python = "<3.7"} 23 | regex = "^2020.7.14" 24 | 25 | [tool.poetry.dev-dependencies] 26 | devtools = "^0.5.1" 27 | objgraph = "^3.4.1" 28 | black = "^20.8b1" 29 | 30 | [build-system] 31 | requires = ["poetry>=0.12"] 32 | build-backend = "poetry.masonry.api" 33 | -------------------------------------------------------------------------------- /src/graia/application/message/elements/__init__.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Any 3 | from pydantic import BaseModel 4 | 5 | 6 | class Element(BaseModel): 7 | def __hash__(self): 8 | return hash((type(self),) + tuple(self.__dict__.values())) 9 | 10 | 11 | class InternalElement(Element, abc.ABC): 12 | def toExternal(self) -> "ExternalElement": 13 | """可以为异步方法""" 14 | pass 15 | 16 | @abc.abstractclassmethod 17 | def fromExternal(cls, external_element) -> "InternalElement": 18 | """可以为异步方法""" 19 | pass 20 | 21 | def asDisplay(self) -> str: 22 | return "" 23 | 24 | def asSerializationString(self) -> str: 25 | return "" 26 | 27 | 28 | class ExternalElement(Element): 29 | pass 30 | 31 | 32 | class ShadowElement(Element): 33 | pass 34 | 35 | 36 | def isShadowElement(any_instance: Any) -> bool: 37 | """检查实例是否为 Shadow Element 38 | 39 | Args: 40 | any_instance (Any): 欲检查的实例 41 | 42 | Returns: 43 | bool: 是否为 Shadow Element 44 | """ 45 | return isinstance(any_instance, ShadowElement) 46 | -------------------------------------------------------------------------------- /src/graia/application/logger.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import logging 3 | 4 | 5 | class AbstractLogger(ABC): 6 | @abstractmethod 7 | def info(self, msg): 8 | pass 9 | 10 | @abstractmethod 11 | def error(self, msg): 12 | pass 13 | 14 | @abstractmethod 15 | def debug(self, msg): 16 | pass 17 | 18 | @abstractmethod 19 | def warn(self, msg): 20 | pass 21 | 22 | @abstractmethod 23 | def exception(self, msg): 24 | pass 25 | 26 | 27 | class LoggingLogger(AbstractLogger): 28 | def __init__(self, **kwargs) -> None: 29 | logging.basicConfig( 30 | format="[%(asctime)s][%(levelname)s]: %(message)s", 31 | level=logging.INFO if not kwargs.get("debug") else logging.DEBUG, 32 | ) 33 | 34 | def info(self, msg): 35 | return logging.info(msg) 36 | 37 | def error(self, msg): 38 | return logging.error(msg) 39 | 40 | def debug(self, msg): 41 | return logging.debug(msg) 42 | 43 | def warn(self, msg): 44 | return logging.warn(msg) 45 | 46 | def exception(self, msg): 47 | return logging.exception(msg) 48 | -------------------------------------------------------------------------------- /src/graia/application/event/__init__.py: -------------------------------------------------------------------------------- 1 | from graia.broadcast.entities.dispatcher import BaseDispatcher 2 | from pydantic import BaseModel, validator 3 | from graia.broadcast import Dispatchable 4 | from graia.application.context import application 5 | from graia.application.exceptions import InvalidEventTypeDefinition 6 | 7 | 8 | class MiraiEvent(Dispatchable, BaseModel): 9 | type: str 10 | 11 | @validator("type", allow_reuse=True) 12 | def type_limit(cls, v): 13 | if cls.type != v: 14 | raise InvalidEventTypeDefinition( 15 | "{0}'s type must be '{1}', not '{2}'".format(cls.__name__, cls.type, v) 16 | ) 17 | return v 18 | 19 | class Config: 20 | extra = "ignore" 21 | 22 | class Dispatcher: 23 | pass 24 | 25 | 26 | class ApplicationDispatcher(BaseDispatcher): 27 | @staticmethod 28 | async def catch(interface): 29 | if getattr(interface.annotation, "__name__", None) == "GraiaMiraiApplication": 30 | return application.get() 31 | 32 | 33 | class EmptyDispatcher(BaseDispatcher): 34 | mixin = [ApplicationDispatcher] 35 | 36 | @staticmethod 37 | async def catch(interface): 38 | pass 39 | -------------------------------------------------------------------------------- /src/graia/application/context.py: -------------------------------------------------------------------------------- 1 | from contextvars import ContextVar 2 | from contextlib import contextmanager 3 | 4 | from graia.application.entities import UploadMethods 5 | 6 | application = ContextVar("application") 7 | event = ContextVar("event") 8 | event_loop = ContextVar("event_loop") 9 | broadcast = ContextVar("broadcast") 10 | 11 | # for image 12 | # sendGroupMessage 等发送message的指令将set该上下文条目. 13 | image_method = ContextVar("image_method") 14 | 15 | 16 | @contextmanager 17 | def enter_message_send_context(method: UploadMethods): 18 | t = image_method.set(method) 19 | yield 20 | image_method.reset(t) 21 | 22 | 23 | @contextmanager 24 | def enter_context(app=None, event_i=None): 25 | t1 = None 26 | t2 = None 27 | t3 = None 28 | t4 = None 29 | 30 | if app: 31 | t1 = application.set(app) 32 | t3 = event_loop.set(app.broadcast.loop) 33 | t4 = broadcast.set(app.broadcast) 34 | if event_i: 35 | t2 = event.set(event_i) 36 | 37 | yield 38 | try: 39 | if t1: 40 | application.reset(t1) 41 | 42 | if all([t2, t3, t4]): 43 | event.reset(t2) 44 | event_loop.reset(t3) 45 | broadcast.reset(t4) 46 | except ValueError: # 在测试 Scheduler 时发现的问题...辣鸡 Python! 47 | pass 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing for Graia Application 2 | 3 | **在此为所有向 `Graia Project` 作出贡献, 为社区添砖加瓦的每位开发者/使用者表示衷心的感谢, 你们的支持就是我们开发的动力.** 4 | 5 | `Graia Project` 下所有项目都欢迎任何环抱开源精神的贡献者. 6 | 而本文档则提到了我们能想到的在贡献本项目时你应该/不应该做的事. 7 | 8 | 你可以通过以下方式向本项目 `Graia Application` 作出贡献: 9 | 10 | - 协助寻找 BUG 11 | - 协助修复已发现的/潜在的 BUG 12 | - 为本项目开发新特性/功能(请先通过 Github Issue 向我们提出建议 ~~然后我们可能会推出官方的方案~~ ) 13 | - 添加非必要的功能支持 14 | - 修改异常的代码行为 15 | - 在 Github Issue 里写关于某个文档尚未提到的特性的使用方法探索 16 | - 帮助撰写 [GraiaDocument](https://github.com/GreyElaina/GraiaDocument) 17 | 18 | 以下是我能想到的注意事项: 19 | 20 | - 能用 `is` 就用 `is`, 但注意引用地址, Python 的小整数池, `str` 的 immutable 等问题(通常情况下我们会协助和指导你进行修改) 21 | - 尽量的使用已有的库(例如使用 `regex` 代替标准库中的 `re`, 以获取更好的性能) 22 | - 尽量别引入新的库 23 | - 不严格要求代码风格, 但请别写的太烂. 24 | - 如果需要测试文件, 可以通过创建 `src/test.py`, 这个文件已经被标记为无需被 git 跟踪 25 | - 最好是所有声明的变量都加上类型注解, 不加也行, 但最好. 26 | - 使用 `poetry` 管理环境 27 | - 别修改 `setup.py`, 使用 `poetry` 28 | - 修改一项东西的名称时, 务必三思(包括但不限于类名, 方法名, 函数名, 参数名等) 29 | - 如果涉及到修改有关 `mirai-api-http` 交互的部分, 请先测试下, 并在 PR 里标出你所使用的版本 30 | - 看不懂的东西请别改... 31 | - `docstring` 都是用的 Google Style, 当然, 如果能帮忙改成 jsdoc 样式的也行, 那样就可以直接生成了~~坐等PR~~ 32 | - 类名使用 `PascalCase`, 单词首字母全大写; 方法名用 `camelCase`, 除第一个单词外的所有单词首字母大写; 文档里提到某种东西 33 | 请别使用缩写, 用 `` ` `` 框起来, 然后如果有 `kebab-case` 的名称优先用; 可以用 markdown. 34 | - 需要添加一个实用函数请在 `graia.application.utilles` 这个模块下面加 -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Graia: Event Model Test", 9 | "type": "python", 10 | "request": "launch", 11 | "module": "graia.application.event.mirai", 12 | "cwd": "${workspaceFolder}/src", 13 | "justMyCode": false 14 | }, 15 | { 16 | "name": "Graia: Literature Test", 17 | "type": "python", 18 | "request": "launch", 19 | "module": "graia.application.message.parser.literature", 20 | "cwd": "${workspaceFolder}/src", 21 | "justMyCode": false 22 | }, 23 | { 24 | "name": "Graia: Test for shlex", 25 | "type": "python", 26 | "request": "launch", 27 | "module": "shlex", 28 | "cwd": "${workspaceFolder}/src", 29 | "justMyCode": false 30 | }, 31 | { 32 | "name": "Python: test", 33 | "type": "python", 34 | "request": "launch", 35 | "program": "./src/test.py", 36 | "console": "integratedTerminal", 37 | "justMyCode": false 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /src/graia/application/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidEventTypeDefinition(Exception): 2 | "不合法的事件类型定义." 3 | pass 4 | 5 | 6 | class InvaildVerifyKey(Exception): 7 | "无效的 VerifyKey 或其配置." 8 | pass 9 | 10 | 11 | class AccountNotFound(Exception): 12 | "未能使用所配置的账号激活 sessionKey, 请检查 connect_info 配置." 13 | pass 14 | 15 | 16 | class InvaildSession(Exception): 17 | "无效的 sessionKey, 请重新获取." 18 | pass 19 | 20 | 21 | class UnauthorizedSession(Exception): 22 | "尚未验证/绑定的 session." 23 | pass 24 | 25 | 26 | class UnknownTarget(Exception): 27 | "对象位置未知, 不存在或不可及." 28 | pass 29 | 30 | 31 | # FileNotFoundError 32 | 33 | # PermissionError 34 | 35 | 36 | class AccountMuted(Exception): 37 | "账号在对象所在聊天区域被封禁." 38 | pass 39 | 40 | 41 | class TooLongMessage(Exception): 42 | "消息过长, 尝试分段发送或报告问题." 43 | pass 44 | 45 | 46 | class InvaildArgument(Exception): 47 | "操作参数不合法, 请报告问题." 48 | pass 49 | 50 | 51 | class NotSupportedVersion(Exception): 52 | "该版本不支持本接口." 53 | pass 54 | 55 | 56 | class DeprecatedImpl(Exception): 57 | "该接口已弃用." 58 | pass 59 | 60 | 61 | class EntangledSuperposition(Exception): 62 | "你传入的一个 List[InternalElement] 中包含了一个没有重写 toExternal 的消息元素" 63 | pass 64 | 65 | 66 | class MissingNecessaryOne(Exception): 67 | "应在所提到的参数之中至少传入/使用一个" 68 | pass 69 | 70 | 71 | class ConflictItem(Exception): 72 | "项冲突/其中一项被重复定义" 73 | pass 74 | -------------------------------------------------------------------------------- /.github/workflows/black-lint.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | black-lint: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v2 27 | 28 | # Runs a single command using the runners shell 29 | - name: Python Blacken 30 | # You may pin to the exact commit or the version. 31 | # uses: piotrpawlaczek/python-blacken@00da49d5262ea8408aa006873de629fef9fc9dd1 32 | uses: piotrpawlaczek/python-blacken@v20.8b1 33 | with: 34 | # File or directory to run black on. 35 | path: "./src/graia" 36 | # The number of characters allowed per line. 37 | line-length: 120 38 | -------------------------------------------------------------------------------- /src/graia/application/message/parser/signature.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Any, Callable, Optional as O 3 | from pydantic import BaseModel, validator 4 | from graia.application.message.chain import MessageChain 5 | import regex 6 | 7 | 8 | class NormalMatch(BaseModel, abc.ABC): 9 | @abc.abstractmethod 10 | def operator(self) -> str: 11 | pass 12 | 13 | 14 | class PatternReceiver(BaseModel): 15 | name: str 16 | isGreed: bool = False 17 | 18 | def __init__(self, name: str, isGreed: bool = False) -> None: 19 | super().__init__(name=name, isGreed=isGreed) 20 | 21 | @validator("name", allow_reuse=True) 22 | def name_checker(cls, v): 23 | if not regex.match("^[a-zA-Z_][a-zA-Z0-9_]*$", v): 24 | raise ValueError("invaild name") 25 | return v 26 | 27 | def __hash__(self) -> int: 28 | return super(object, self).__hash__() 29 | 30 | 31 | class FullMatch(NormalMatch): 32 | pattern: str 33 | 34 | def __init__(self, pattern) -> None: 35 | super().__init__(pattern=pattern) 36 | 37 | def operator(self): 38 | return regex.escape(self.pattern) 39 | 40 | 41 | class RegexMatch(NormalMatch): 42 | pattern: str 43 | 44 | def __init__(self, pattern) -> None: 45 | super().__init__(pattern=pattern) 46 | 47 | def operator(self): 48 | return self.pattern 49 | 50 | 51 | class RequireParam(PatternReceiver): 52 | checker: O[Callable[[MessageChain], bool]] = None 53 | translator: O[Callable[[MessageChain], Any]] = None 54 | 55 | 56 | class OptionalParam(PatternReceiver): 57 | checker: O[Callable[[MessageChain], bool]] = None 58 | translator: O[Callable[[MessageChain], Any]] = None 59 | -------------------------------------------------------------------------------- /src/graia/application/event/lifecycle.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import typing 3 | from graia.broadcast.entities.event import Dispatchable 4 | from graia.broadcast.entities.dispatcher import BaseDispatcher 5 | from graia.broadcast.interfaces.dispatcher import DispatcherInterface 6 | 7 | if typing.TYPE_CHECKING: 8 | from graia.application import GraiaMiraiApplication 9 | 10 | class ApplicationLaunched(Dispatchable): 11 | app: "GraiaMiraiApplication" 12 | 13 | def __init__(self, app) -> None: 14 | self.app = app 15 | 16 | class Dispatcher(BaseDispatcher): 17 | @staticmethod 18 | async def catch(interface: "DispatcherInterface"): 19 | from .. import GraiaMiraiApplication 20 | 21 | if interface.annotation is GraiaMiraiApplication: 22 | return interface.event.app 23 | 24 | 25 | class ApplicationLaunchedBlocking(Dispatchable): 26 | app: "GraiaMiraiApplication" 27 | 28 | def __init__(self, app) -> None: 29 | self.app = app 30 | 31 | class Dispatcher(BaseDispatcher): 32 | @staticmethod 33 | async def catch(interface: "DispatcherInterface"): 34 | from .. import GraiaMiraiApplication 35 | 36 | if interface.annotation is GraiaMiraiApplication: 37 | return interface.event.app 38 | 39 | 40 | class ApplicationShutdowned(Dispatchable): 41 | app: "GraiaMiraiApplication" 42 | 43 | def __init__(self, app) -> None: 44 | self.app = app 45 | 46 | class Dispatcher(BaseDispatcher): 47 | @staticmethod 48 | async def catch(interface: "DispatcherInterface"): 49 | from .. import GraiaMiraiApplication 50 | 51 | if interface.annotation is GraiaMiraiApplication: 52 | return interface.event.app 53 | -------------------------------------------------------------------------------- /src/graia/application/message/parser/pack.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from typing import List, Sequence, Union 3 | from .signature import FullMatch, PatternReceiver 4 | 5 | 6 | class Arguments: 7 | content: List[PatternReceiver] 8 | 9 | @property 10 | def isGreed(self) -> bool: 11 | return any([i.isGreed for i in self.content]) 12 | 13 | def __init__(self, content: List[PatternReceiver]) -> None: 14 | self.content = content 15 | 16 | def __repr__(self) -> str: 17 | return "".format(str(self.content)) 18 | 19 | 20 | def merge_signature_chain_fullmatch( 21 | chain: Sequence[Union[FullMatch, PatternReceiver]] 22 | ) -> Sequence[Union[Arguments, FullMatch]]: 23 | result = [] 24 | 25 | temp_l1 = [] 26 | 27 | for i in chain: 28 | if isinstance(i, FullMatch): 29 | temp_l1.append(i.pattern) 30 | else: 31 | if temp_l1: 32 | result.append(FullMatch("".join(temp_l1))) 33 | temp_l1.clear() 34 | result.append(i) 35 | else: 36 | if temp_l1: 37 | result.append(FullMatch("".join(temp_l1))) 38 | temp_l1.clear() 39 | 40 | return type(chain)(result) 41 | 42 | 43 | def merge_signature_chain( 44 | chain: Sequence[Union[FullMatch, PatternReceiver]] 45 | ) -> Sequence[Union[Arguments, FullMatch]]: 46 | chain = merge_signature_chain_fullmatch(chain) 47 | result = [] 48 | temp_l1 = [] 49 | 50 | for i in chain: 51 | if isinstance(i, PatternReceiver): 52 | temp_l1.append(i) 53 | else: 54 | if temp_l1: 55 | result.append(Arguments(temp_l1)) 56 | temp_l1 = [] 57 | result.append(i) 58 | else: 59 | if temp_l1: 60 | result.append(Arguments(temp_l1)) 61 | temp_l1 = [] 62 | 63 | return type(chain)(result) 64 | -------------------------------------------------------------------------------- /src/graia/application/entry.py: -------------------------------------------------------------------------------- 1 | """这个模块用于为开发者提供一站式的导入体验.""" 2 | 3 | from . import GraiaMiraiApplication 4 | from .logger import AbstractLogger, LoggingLogger 5 | from .event.dispatcher import MessageChainCatcher 6 | from .event.lifecycle import ApplicationLaunched, ApplicationShutdowned 7 | from .event.messages import FriendMessage, GroupMessage, TempMessage 8 | from .event.mirai import ( 9 | BotOnlineEvent, 10 | BotOfflineEventActive, 11 | BotOfflineEventForce, 12 | BotOfflineEventDropped, 13 | BotReloginEvent, 14 | BotGroupPermissionChangeEvent, 15 | BotMuteEvent, 16 | BotUnmuteEvent, 17 | BotJoinGroupEvent, 18 | GroupRecallEvent, 19 | FriendRecallEvent, 20 | GroupNameChangeEvent, 21 | GroupEntranceAnnouncementChangeEvent, 22 | GroupMuteAllEvent, 23 | GroupAllowAnonymousChatEvent, 24 | GroupAllowConfessTalkEvent, 25 | GroupAllowMemberInviteEvent, 26 | MemberJoinEvent, 27 | MemberLeaveEventKick, 28 | MemberLeaveEventQuit, 29 | MemberCardChangeEvent, 30 | MemberSpecialTitleChangeEvent, 31 | MemberPermissionChangeEvent, 32 | MemberMuteEvent, 33 | MemberUnmuteEvent, 34 | NewFriendRequestEvent, 35 | MemberJoinRequestEvent, 36 | BotInvitedJoinGroupRequestEvent, 37 | ) 38 | from .message.elements.internal import ( 39 | Plain, 40 | Source, 41 | Quote, 42 | At, 43 | AtAll, 44 | Face, 45 | Image, 46 | FlashImage, 47 | Xml, 48 | Json, 49 | App, 50 | Poke, 51 | PokeMethods, 52 | ) 53 | from .message.chain import MessageChain 54 | from .friend import Friend 55 | from .group import MemberPerm, Group, Member, MemberInfo, GroupConfig 56 | from .session import Session 57 | from .exceptions import ( 58 | InvalidEventTypeDefinition, 59 | InvaildVerifyKey, 60 | AccountNotFound, 61 | InvaildSession, 62 | UnauthorizedSession, 63 | UnknownTarget, 64 | AccountMuted, 65 | TooLongMessage, 66 | InvaildArgument, 67 | NotSupportedVersion, 68 | DeprecatedImpl, 69 | EntangledSuperposition, 70 | MissingNecessaryOne, 71 | ) 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | src/test.py 131 | src/test-mem.py -------------------------------------------------------------------------------- /src/graia/application/group.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | from pydantic import BaseModel 4 | from pydantic.fields import Field 5 | 6 | 7 | class MemberPerm(Enum): 8 | "描述群成员在群组中所具备的权限" 9 | 10 | Member = "MEMBER" # 普通成员 11 | Administrator = "ADMINISTRATOR" # 管理员 12 | Owner = "OWNER" # 群主 13 | 14 | 15 | class Group(BaseModel): 16 | "描述 Tencent QQ 中的可发起聊天区域 '群组(group)' 的能被获取到的信息." 17 | 18 | id: int 19 | name: str 20 | accountPerm: MemberPerm = Field(..., alias="permission") 21 | 22 | 23 | class Member(BaseModel): 24 | "描述用户在群组中所具备的有关状态, 包括所在群组, 群中昵称, 所具备的权限, 唯一ID." 25 | 26 | id: int 27 | name: str = Field(..., alias="memberName") 28 | permission: MemberPerm 29 | group: Group 30 | 31 | 32 | class GroupConfig(BaseModel): 33 | "描述群组各项功能的设置." 34 | 35 | name: Optional[str] = None 36 | announcement: Optional[str] = None 37 | confessTalk: Optional[bool] = None 38 | allowMemberInvite: Optional[bool] = None 39 | autoApprove: Optional[bool] = None 40 | anonymousChat: Optional[bool] = None 41 | 42 | # 调用 json 方法时记得加 exclude_none=True. 43 | 44 | class Config: 45 | allow_mutation = True 46 | 47 | 48 | class MemberInfo(BaseModel): 49 | "描述群组成员的可修改状态, 修改需要管理员/群主权限." 50 | 51 | name: Optional[str] = None 52 | specialTitle: Optional[str] = None 53 | 54 | # 调用 json 方法时记得加 exclude_none=True. 55 | 56 | class Config: 57 | allow_mutation = True 58 | 59 | class FileList(BaseModel): 60 | "描述群组文件的有关状态" 61 | 62 | name: Optional[str] = None 63 | id: Optional[str] = None 64 | "文件路径" 65 | path: Optional[str] = None 66 | "是否为文件" 67 | is_file: Optional[bool] = Field(..., alias="isFile") 68 | 69 | # 我搞不懂为什么写这个的人全部设的 Optional 70 | 71 | class FileInfo(BaseModel): 72 | "群组文件详细信息" 73 | 74 | name: Optional[str] = None 75 | path: Optional[str] = None 76 | id: Optional[str] = None 77 | "文件长度" 78 | length: Optional[int] 79 | "下载次数" 80 | download_times: Optional[int] = Field(..., alias="downloadTimes") 81 | "上传者QQ" 82 | uploader_id: Optional[int] = Field(..., alias="uploaderId") 83 | "上传时间" 84 | upload_time: Optional[int] = Field(..., alias="uploadTime") 85 | "最后修改时间" 86 | last_modify_time: Optional[int] = Field(..., alias="lastModifyTime") 87 | "文件下载链接" 88 | download_url: Optional[str] = Field(..., alias="downloadUrl") 89 | "文件 sha1 值" 90 | sha1: Optional[str] = None 91 | "文件 md5 值" 92 | md5: Optional[str] = None 93 | -------------------------------------------------------------------------------- /src/graia/application/test/request_tracing.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from typing import Optional 3 | from aiohttp import TraceConfig 4 | from aiohttp.client import ClientSession 5 | from aiohttp.tracing import TraceRequestEndParams, TraceRequestStartParams 6 | from ..logger import AbstractLogger 7 | import random 8 | import string 9 | 10 | 11 | class HttpRequestTracing: 12 | logger: AbstractLogger 13 | 14 | def __init__(self, logger: AbstractLogger) -> None: 15 | self.logger = logger 16 | 17 | async def on_request_start( 18 | self, session: ClientSession, trace_config_ctx, params: TraceRequestStartParams 19 | ): 20 | req_id = "".join(random.choices(string.ascii_letters, k=12)) 21 | trace_config_ctx.req_id = req_id 22 | if params.method == "GET": 23 | self.logger.debug(f"Request[{req_id}:GET]: {params.url}") 24 | elif params.method == "POST": 25 | self.logger.debug( 26 | f"Request[{req_id}:POST]: {params.url} using [{trace_config_ctx.trace_request_ctx[1]['json'] if trace_config_ctx.trace_request_ctx else None}]" 27 | ) 28 | 29 | async def on_request_end( 30 | self, session: ClientSession, trace_config_ctx, params: TraceRequestEndParams 31 | ): 32 | if params.method == "GET": 33 | self.logger.debug(f"Response[{trace_config_ctx.req_id}:GET]: {params.url}") 34 | elif params.method == "POST": 35 | self.logger.debug( 36 | f"Response[{trace_config_ctx.req_id}:POST]: {params.url} using [{trace_config_ctx.trace_request_ctx[1]['json'] if trace_config_ctx.trace_request_ctx else None}], responsed [{await params.response.text()}]" 37 | ) 38 | 39 | def build_session( 40 | self, apply_for: Optional[ClientSession] = None, *args, **kwargs 41 | ) -> ClientSession: 42 | trace_config = TraceConfig() 43 | trace_config.on_request_start.append(self.on_request_start) 44 | trace_config.on_request_end.append(self.on_request_end) 45 | session = apply_for or ClientSession( 46 | trace_configs=[trace_config], *args, **kwargs 47 | ) 48 | if apply_for is not None: 49 | session.trace_configs.append(trace_config) 50 | trace_config.freeze() 51 | 52 | def hook_method(method): 53 | def hooked_wrapper(*args, **kwargs): 54 | return method(*args, **{**kwargs, "trace_request_ctx": (args, kwargs)}) 55 | 56 | return hooked_wrapper 57 | 58 | session.get = hook_method(session.get) 59 | session.post = hook_method(session.post) 60 | return session 61 | -------------------------------------------------------------------------------- /src/graia/application/session.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from pydantic import BaseModel, AnyHttpUrl 3 | from typing import Optional, Tuple 4 | import os 5 | import yarl 6 | 7 | @dataclass 8 | class Session: 9 | """用于描述与上游接口会话, 并存储会话状态的实体类. 10 | 11 | Attributes: 12 | host (AnyHttpUrl): `mirai-api-http` 服务所在的根接口地址 13 | account (int): 应用所使用账号的整数 ID, 虽然如果启用 `singleMode` 时不需要, 但仍然建议填写. 14 | verifyKey (str): 在 `mirai-api-http` 配置流程中定义, 需为相同的值以通过安全验证, 需在 mirai-api-http 配置里启用 `enableVerify`. 15 | websocket (bool): 是否使用 Websocket 方式获取事件, 若为 `False` 则使用 HTTP 短轮询方式获取事件, 性能较低. 16 | sessionKey (str, optional): 会话标识, 即会话中用于进行操作的唯一认证凭证, 需要经过 `activeSession` 后才可用. 17 | current_version (Tuple[int, int, int], optional): 上游服务的版本, 暂时没有自动获取. 18 | """ 19 | 20 | host: AnyHttpUrl 21 | account: int = None 22 | verifyKey: str = None 23 | sessionKey: str = None 24 | current_version: Tuple[int, int, int] = None 25 | websocket: bool = True 26 | 27 | @classmethod 28 | def fromEnv( 29 | cls, 30 | host: Optional[AnyHttpUrl] = None, 31 | verifyKey: Optional[str] = None, 32 | account: Optional[int] = None, 33 | websocket: Optional[bool] = True, 34 | ) -> "Session": 35 | """从环境变量和所给出参数中实例化 Session, 所有参数都必须给出, 无论通过环境变量还是实际给出的值; 36 | 实例化时给出的参数优先级高于环境变量. 37 | 38 | 环境变量对应列表: 39 | - `GRAIA_HOST`: 与参数 `host` 同义; 40 | - `GRAIA_VERIFYKEY`: 与参数 `verifyKey` 同义; 41 | - `GRAIA_ACCOUNT_ID`: 与参数 `account` 同义; 42 | - `GRAIA_WEBSOCKET_ENABLED`: 与参数 `websocket` 同义, 规则: 43 | - `"true"` 或 `"1"` 得 `True`, 即启用 Websocket 方式连接; 44 | - `"false"` 或 `"0"` 得 `False`, 即禁用 Websocket 方式连接. 45 | 46 | Args: 47 | host (AnyHttpUrl, optional): `mirai-api-http` 服务所在的根接口地址 48 | verifyKey (str, optional): 在 `mirai-api-http` 配置流程中定义, 需为相同的值以通过安全验证. 49 | account (int, optional): 应用所使用账号的整数 ID. 50 | websocket (bool, optional): 是否使用 Websocket 方式获取事件, 若为 `False` 则使用 HTTP 短轮询方式获取事件, 性能较低, 默认为 `True`. 51 | 52 | Returns: 53 | Session: 从给出的参数和所处环境变量生成的 `Session` 实例. 54 | """ 55 | return cls( 56 | **{ 57 | "host": host or os.getenv("GRAIA_HOST"), 58 | "verifyKey": verifyKey or os.getenv("GRAIA_VERIFYKEY"), 59 | "account": account or os.getenv("GRAIA_ACCOUNT_ID"), 60 | "websocket": websocket 61 | or ({"false": False, "true": True, "1": True, "0": False}).get( 62 | os.getenv("GRAIA_WEBSOCKET_ENABLED").lower(), websocket 63 | ), 64 | } 65 | ) 66 | 67 | class Config: 68 | allow_mutation = True 69 | -------------------------------------------------------------------------------- /src/graia/application/event/messages.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from datetime import datetime 3 | from pydantic import validator, Field 4 | from pydantic.main import BaseModel 5 | 6 | from graia.application.event.dispatcher import MessageChainCatcher 7 | from graia.application.message.chain import MessageChain 8 | from graia.broadcast.entities.dispatcher import BaseDispatcher 9 | from graia.broadcast.interfaces.dispatcher import DispatcherInterface 10 | from graia.application.group import Member, Group 11 | from graia.application.friend import Friend 12 | from graia.application.message.elements.internal import Source 13 | from . import ApplicationDispatcher, MiraiEvent 14 | 15 | 16 | class SourceElementDispatcher(BaseDispatcher): 17 | @staticmethod 18 | async def catch(interface: DispatcherInterface): 19 | if interface.annotation is Source: 20 | return interface.event.messageChain.getFirst(Source) 21 | 22 | 23 | class FriendMessage(MiraiEvent): 24 | type: str = "FriendMessage" 25 | messageChain: MessageChain 26 | sender: Friend 27 | 28 | class Dispatcher(BaseDispatcher): 29 | mixin = [MessageChainCatcher, ApplicationDispatcher, SourceElementDispatcher] 30 | 31 | @staticmethod 32 | async def catch(interface: DispatcherInterface): 33 | if interface.annotation is Friend: 34 | return interface.event.sender 35 | 36 | 37 | class GroupMessage(MiraiEvent): 38 | type: str = "GroupMessage" 39 | messageChain: MessageChain 40 | sender: Member 41 | 42 | class Dispatcher(BaseDispatcher): 43 | mixin = [MessageChainCatcher, ApplicationDispatcher, SourceElementDispatcher] 44 | 45 | @staticmethod 46 | async def catch(interface: DispatcherInterface): 47 | if interface.annotation is Group: 48 | return interface.event.sender.group 49 | elif interface.annotation is Member: 50 | return interface.event.sender 51 | 52 | 53 | class TempMessage(MiraiEvent): 54 | type: str = "TempMessage" 55 | messageChain: MessageChain 56 | sender: Member 57 | 58 | @classmethod 59 | def parse_obj(cls, obj): 60 | return super().parse_obj(obj) 61 | 62 | class Dispatcher(BaseDispatcher): 63 | mixin = [MessageChainCatcher, ApplicationDispatcher, SourceElementDispatcher] 64 | 65 | @staticmethod 66 | async def catch(interface: DispatcherInterface): 67 | if interface.annotation is Group: 68 | return interface.event.sender.group 69 | elif interface.annotation is Member: 70 | return interface.event.sender 71 | 72 | 73 | class ForwardContentMessage(BaseModel): 74 | senderId: int 75 | senderName: str 76 | 77 | messageChain: MessageChain 78 | 79 | time: datetime 80 | 81 | 82 | class Forward(MiraiEvent): 83 | type: str = "Forward" 84 | 85 | "表示该条转发消息的标题, 通常为 `群聊的聊天记录`" 86 | title: str 87 | 88 | "显示在消息列表中的预览文本, 调用 asDisplay 方法返回该值" 89 | brief: str 90 | 91 | "似乎没有什么用, 这个东西找不到在哪里显示" 92 | source: str 93 | 94 | "描述, 通常都像: `查看 x 条转发消息` 这样" 95 | summary: str 96 | 97 | content: List[ForwardContentMessage] = Field(..., alias="nodeList") 98 | 99 | def asDisplay(self): 100 | return self.brief 101 | 102 | class Dispatcher(BaseDispatcher): 103 | mixin = [MessageChainCatcher, ApplicationDispatcher] 104 | 105 | @staticmethod 106 | async def catch(interface: DispatcherInterface): 107 | pass 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 此仓库已废弃, 请使用替代品 [Ariadne](https://github.com/GraiaProject/Ariadne); 本项目被标记为 `v4`, 相对的有 `v4+`(Ariadne) 与 `v5`(WIP, 尚未完工), 目前我推荐使用 Ariadne. 2 | 3 | # Graia Application for mirai-api-http 4 | 5 | 当前最新版本: ![PyPI](https://img.shields.io/pypi/v/graia-application-mirai) 6 | 所需求的最低 CPython 版本: ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/graia-application-mirai) 7 | 已确认可以在其上运行的 Python 实现: ![PyPI - Implementation](https://img.shields.io/pypi/implementation/graia-application-mirai) 8 | 9 | ### 开始使用 10 | 11 | 文档地址: https://graia-document.vercel.app/ 12 | API 文档地址(使用 `pdoc` 生成): https://graiaproject.github.io/Application/graia/application/index.html 13 | 14 | Tencent QQ 交流群: [邀请链接](https://jq.qq.com/?_wv=1027&k=VXp6plBD) 15 | Discussion: https://github.com/GraiaProject/Application/discussions 16 | 17 | #### 从 Pypi 安装 18 | ``` bash 19 | pip install graia-application-mirai 20 | # 或使用 poetry 21 | poetry add graia-application-mirai 22 | ``` 23 | 24 | #### 从 Github 安装 25 | ``` bash 26 | pip install poetry 27 | git clone https://github.com/GraiaProject/Application graia-app 28 | cd graia-app 29 | poetry install 30 | ``` 31 | 32 | ### 作出贡献 33 | `Graia Framework` 欢迎一切形式上的贡献(包括但不限于 `Issues`, `Pull Requests`, `Good Idea` 等) 34 | 我们希望能有更多优秀的开发者加入到对项目的贡献上来. 你的 `Star` 是对我们最大的支持和鼓励. 35 | 36 | 我们在[这里](https://github.com/GraiaProject/Application/blob/master/CONTRIBUTING.md)写了你在贡献本项目及 37 | `Graia Project` 时所可能需要注意的事项. 38 | 39 | 因为历史原因, 我们的文档, 即 [Graia Document](https://github.com/GreyElaina/GraiaDocument) 目前急需改进和完善, 40 | 如果有意愿, 欢迎提起 Pull Request. 41 | 42 | 若你在使用的过程中遇到了问题, 欢迎[提出聪明的问题](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md), 也请不要[使用糟糕的方式提问](https://github.com/tangx/Stop-Ask-Questions-The-Stupid-Ways), 我们希望有人能让这个项目变得更好. 43 | 44 | 若在使用时发现了本项目的问题, 先检查文档中是否有提及这一情况, 45 | 若没有, 你可以在我们的[问题追踪器](https://github.com/GraiaProject/Application/issues)处提出问题, 46 | 我们会尽快解决你发现的问题. 47 | 48 | 你也可以通过 Discussion/QQ 群等方式获取帮助,现在我们更推荐使用 [Discussion](https://github.com/GraiaProject/Application/discussions). 49 | 50 | **若使用中发现了并非本项目导致的问题, 请先向其他项目汇报问题, 当然, 记得通知我.** 51 | 52 | ### 鸣谢&相关项目 53 | > 这些项目也很棒, 去他们的项目页看看, 点个 `Star` 以鼓励他们的开发工作, 毕竟没有他们也没有 `Graia Framework`. 54 | 55 | 特别感谢 [`mamoe`](https://github.com/mamoe) 给我们带来这些精彩的项目: 56 | - [`mirai`](https://github.com/mamoe/mirai): 即 `mirai-core`, 一个高性能, 高可扩展性的 QQ 协议库 57 | - [`mirai-console`](https://github.com/mamoe/mirai-console): 一个基于 `mirai` 开发的插件式可扩展开发平台 58 | - [`mirai-api-http`](https://github.com/project-mirai/mirai-api-http): 为本项目提供与 `mirai` 交互方式的 `mirai-console` 插件 59 | 60 | `Graia Application` 基于以下独立 `Graia Project` 项目实现: 61 | - [`Broadcast Control`](https://github.com/GraiaProject/BroadcastControl): 扩展性强大, 模块间低耦合, 高灵活性的事件系统支持 62 | 63 | `Graia Application` 同样还关联了其他 `Graia Project` 项目: 64 | - [`Components`](https://github.com/GraiaProject/Components): 简单的消息链元素选择器 65 | - [`Template`](https://github.com/GraiaProject/Template): 消息模板 66 | - [`Saya`](https://github.com/GraiaProject/Saya) 为该项目提供了间接但简洁的模块管理系统. [文档](https://graia-document.vercel.app/docs/saya/saya-index) 67 | - 关于 `Saya`: 这是一个全新的系统, 包含的潜力不亚于 `Application`, 并且实现了更方便的面向模块的 API, 但如果你需要应用到 `Application` 上, 则仍需要先学习相关的内容. 68 | 69 | 若有相关需求, 我们也强烈建议配合以下独立 `Graia Project` 项目使用: 70 | - [`Scheduler`](https://github.com/GraiaProject/Scheduler): 简洁的基于 `asyncio` 的定时任务实现. 71 | 72 | 作为学习目的, 主要维护者 `GreyElaina` 以个人名义重新以 `AGPL-3.0` 开源了 `python-mirai`, 即 `Graia Application` 的前身, 希望能为社区的发展助力: 73 | - [`python-mirai`](https://github.com/GreyElaina/python-mirai): 接口简洁, 支持 `mirai-api-http` 约 `v1.6.x` 版本. 一切的开始. 74 | 75 | 也感谢所有基于本项目开发的各位开发者, 请积极向上游项目反馈问题. 76 | 77 | ### 许可证 78 | 我们使用 [`GNU AGPLv3`](https://choosealicense.com/licenses/agpl-3.0/) 作为本项目的开源许可证. 79 | -------------------------------------------------------------------------------- /src/graia/application/interrupts.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, Union 2 | from graia.broadcast.exceptions import ExecutionStop 3 | from graia.broadcast.interrupt.waiter import Waiter 4 | from graia.application.group import Group, Member 5 | from graia.application.message.elements.internal import Quote, Source 6 | from graia.application.message import BotMessage 7 | from graia.application.event.messages import FriendMessage, GroupMessage, TempMessage 8 | from graia.application.friend import Friend 9 | 10 | 11 | def GroupMessageInterrupt( 12 | special_group: Optional[Union[Group, int]] = None, 13 | special_member: Optional[Union[Member, int]] = None, 14 | quote_access: Optional[Union[BotMessage, Source]] = None, 15 | custom_judgement: Optional[Callable[[GroupMessage], bool]] = None, 16 | block_propagation: bool = False, 17 | ): 18 | @Waiter.create_using_function([GroupMessage], block_propagation=block_propagation) 19 | def GroupMessageInterruptWaiter(event: GroupMessage): 20 | if special_group: 21 | if event.sender.group.id != ( 22 | special_group.id if isinstance(special_group, Group) else special_group 23 | ): 24 | raise ExecutionStop() 25 | if special_member: 26 | if event.sender.id != ( 27 | special_member.id 28 | if isinstance(special_member, Member) 29 | else special_member 30 | ): 31 | raise ExecutionStop() 32 | if quote_access: 33 | quotes = event.messageChain.get(Quote) 34 | if not quotes: 35 | raise ExecutionStop() 36 | quote: Quote = quotes[0] 37 | if isinstance(quote_access, BotMessage): 38 | if quote.id != quote_access.messageId: 39 | raise ExecutionStop() 40 | elif isinstance(quote_access, Source): 41 | if quote.id != quote_access.id: 42 | raise ExecutionStop() 43 | if custom_judgement: 44 | if not custom_judgement(event): 45 | raise ExecutionStop() 46 | return event 47 | 48 | return GroupMessageInterruptWaiter 49 | 50 | 51 | def FriendMessageInterrupt( 52 | special_friend: Optional[Union[Friend, int]] = None, 53 | quote_access: Optional[Union[BotMessage, Source]] = None, 54 | custom_judgement: Optional[Callable[[FriendMessage], bool]] = None, 55 | block_propagation: bool = False, 56 | ): 57 | @Waiter.create_using_function([FriendMessage], block_propagation=block_propagation) 58 | def FriendMessageInterruptWaiter(event: FriendMessage): 59 | if special_friend: 60 | if event.sender.id != ( 61 | special_friend.id 62 | if isinstance(special_friend, Friend) 63 | else special_friend 64 | ): 65 | raise ExecutionStop() 66 | if quote_access: 67 | quotes = event.messageChain.get(Quote) 68 | if not quotes: 69 | raise ExecutionStop() 70 | quote: Quote = quotes[0] 71 | if isinstance(quote_access, BotMessage): 72 | if quote.id != quote_access.messageId: 73 | raise ExecutionStop() 74 | elif isinstance(quote_access, Source): 75 | if quote.id != quote_access.id: 76 | raise ExecutionStop() 77 | if custom_judgement: 78 | if not custom_judgement(event): 79 | raise ExecutionStop() 80 | return event 81 | 82 | return FriendMessageInterruptWaiter 83 | 84 | 85 | def TempMessageInterrupt( 86 | special_group: Optional[Union[Group, int]] = None, 87 | special_member: Optional[Union[Member, int]] = None, 88 | quote_access: Optional[Union[BotMessage, Source]] = None, 89 | custom_judgement: Optional[Callable[[TempMessage], bool]] = None, 90 | block_propagation: bool = False, 91 | ): 92 | @Waiter.create_using_function([TempMessage], block_propagation=block_propagation) 93 | def TempMessageInterruptWaiter(event: TempMessage): 94 | if special_group: 95 | if event.sender.group.id != ( 96 | special_group.id if isinstance(special_group, Group) else special_group 97 | ): 98 | raise ExecutionStop() 99 | if special_member: 100 | if event.sender.id != ( 101 | special_member.id 102 | if isinstance(special_member, Member) 103 | else special_member 104 | ): 105 | raise ExecutionStop() 106 | if quote_access: 107 | quotes = event.messageChain.get(Quote) 108 | if not quotes: 109 | raise ExecutionStop() 110 | quote: Quote = quotes[0] 111 | if isinstance(quote_access, BotMessage): 112 | if quote.id != quote_access.messageId: 113 | raise ExecutionStop() 114 | elif isinstance(quote_access, Source): 115 | if quote.id != quote_access.id: 116 | raise ExecutionStop() 117 | if custom_judgement: 118 | if not custom_judgement(event): 119 | raise ExecutionStop() 120 | return event 121 | 122 | return TempMessageInterruptWaiter 123 | -------------------------------------------------------------------------------- /docs/graia/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Namespace graia

23 |
24 |
25 |
26 |
27 |

Sub-modules

28 |
29 |
graia.application
30 |
31 |
32 |
33 |
graia.broadcast
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 60 |
61 | 64 | 65 | -------------------------------------------------------------------------------- /docs/graia/broadcast/typing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.typing API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.broadcast.typing

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from typing import Any, Callable, TYPE_CHECKING, Type, Union
30 | 
31 | if TYPE_CHECKING:
32 |     from graia.broadcast.interfaces.dispatcher import DispatcherInterface
33 | 
34 | T_Dispatcher = Union[
35 |     Type["BaseDispatcher"], "BaseDispatcher", Callable[["DispatcherInterface"], Any]
36 | ]
37 | 
38 | T_Dispatcher_Callable = Callable[["DispatcherInterface"], Any]
39 | 
40 | from graia.broadcast.entities.dispatcher import BaseDispatcher
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 65 |
66 | 69 | 70 | -------------------------------------------------------------------------------- /docs/graia/application/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.test API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.application.test

23 |
24 |
25 |

这里提供用于 Graia 测试使用的快捷工具, 不同的工具在不同的包里面.

26 |
27 | 28 | Expand source code 29 | 30 |
"""这里提供用于 Graia 测试使用的快捷工具, 不同的工具在不同的包里面.
31 | 
32 | """
33 |
34 |
35 |
36 |

Sub-modules

37 |
38 |
graia.application.test.request_tracing
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 69 |
70 | 73 | 74 | -------------------------------------------------------------------------------- /docs/graia/broadcast/interfaces/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.interfaces API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 46 | 65 |
66 | 69 | 70 | -------------------------------------------------------------------------------- /src/graia/application/utilles.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from typing import Any, Callable, ContextManager, Iterable, List, Union, TypeVar 3 | from graia.broadcast.entities.dispatcher import BaseDispatcher 4 | from graia.broadcast.interfaces.dispatcher import DispatcherInterface 5 | 6 | from .exceptions import ( 7 | AccountMuted, 8 | AccountNotFound, 9 | InvaildArgument, 10 | InvaildVerifyKey, 11 | InvaildSession, 12 | NotSupportedVersion, 13 | TooLongMessage, 14 | UnauthorizedSession, 15 | UnknownTarget, 16 | ) 17 | from .context import enter_context 18 | import inspect 19 | 20 | _T = TypeVar("_T") 21 | 22 | 23 | def applicationContextManager(func: Callable): 24 | @functools.wraps(func) 25 | async def wrapper(self, *args, **kwargs): 26 | with enter_context(app=self): 27 | return await func(self, *args, **kwargs) 28 | 29 | return wrapper 30 | 31 | 32 | def requireAuthenticated(func: Callable): 33 | @functools.wraps(func) 34 | def wrapper(self, *args, **kwargs): 35 | if not self.connect_info.sessionKey: 36 | raise InvaildSession("you must authenticate before this.") 37 | return func(self, *args, **kwargs) 38 | 39 | wrapper.__annotations__ = func.__annotations__ 40 | return wrapper 41 | 42 | 43 | def SinceVersion(*version: int): 44 | def wrapper(func): 45 | @functools.wraps(func) 46 | def inside_wrapper(self, *args, **kwargs): 47 | if ( 48 | self.connect_info.current_version 49 | and self.connect_info.current_version < version 50 | ): 51 | raise NotSupportedVersion( 52 | "the current version does not support this feature: {0}".format( 53 | self.connect_info.current_version 54 | ) 55 | ) 56 | return func(self, *args, **kwargs) 57 | 58 | return inside_wrapper 59 | 60 | return wrapper 61 | 62 | 63 | def DeprecatedSince(*version: int, action: str = "warn"): 64 | if action not in ["warn", "error", "ignore"]: 65 | raise TypeError("action must be in" + str(["warn", "error", "ignore"])) 66 | 67 | def wrapper(func): 68 | @functools.wraps(func) 69 | def inside_wrapper(self, *args, **kwargs): 70 | if ( 71 | self.connect_info.current_version 72 | and self.connect_info.current_version > version 73 | ): 74 | if action == "error": 75 | raise NotSupportedVersion( 76 | "the current version deprecated this feature: {0}".format( 77 | self.connect_info.current_version 78 | ) 79 | ) 80 | elif action == "warn": 81 | import warnings 82 | 83 | warnings.warn( 84 | "'{0}' has been deprecated since {1}, use other methods to realize your business as soon as possible!".format( 85 | func.__qualname__, version 86 | ) 87 | ) 88 | return func(*args, **kwargs) 89 | 90 | return inside_wrapper 91 | 92 | return wrapper 93 | 94 | 95 | code_exceptions_mapping = { 96 | 1: InvaildVerifyKey, 97 | 2: AccountNotFound, 98 | 3: InvaildSession, 99 | 4: UnauthorizedSession, 100 | 5: UnknownTarget, 101 | 6: FileNotFoundError, 102 | 10: PermissionError, 103 | 20: AccountMuted, 104 | 30: TooLongMessage, 105 | 400: InvaildArgument, 106 | } 107 | 108 | 109 | def raise_for_return_code(code: Union[dict, int]): 110 | if isinstance(code, dict): 111 | code = code.get("code") 112 | exception_code = code_exceptions_mapping.get(code) 113 | if exception_code: 114 | raise exception_code 115 | elif isinstance(code, int): 116 | exception_code = code_exceptions_mapping.get(code) 117 | if exception_code: 118 | raise exception_code 119 | 120 | 121 | def print_traceback_javay(): 122 | stacks = inspect.stack()[1:] 123 | for i in stacks: 124 | print(f" at [{i.filename}:{i.lineno}]") 125 | print("\n") 126 | 127 | 128 | class AppMiddlewareAsDispatcher(BaseDispatcher): 129 | always = True 130 | context: ContextManager 131 | 132 | def __init__(self, app) -> None: 133 | self.app = app 134 | 135 | def beforeExecution(self, interface: "DispatcherInterface"): 136 | self.context = enter_context(self.app, interface.event) 137 | self.context.__enter__() 138 | 139 | def afterExecution(self, interface: "DispatcherInterface", exception, tb): 140 | self.context.__exit__(exception.__class__ if exception else None, exception, tb) 141 | 142 | async def catch(self, interface: "DispatcherInterface"): 143 | from graia.application import GraiaMiraiApplication 144 | 145 | if interface.annotation is GraiaMiraiApplication: 146 | return self.app 147 | 148 | 149 | def context_enter_auto(context): 150 | def wrapper1(func): 151 | @functools.wraps(func) 152 | def wrapper2(*args, **kwargs): 153 | with context: 154 | return func(*args, **kwargs) 155 | 156 | return wrapper2 157 | 158 | return wrapper1 159 | 160 | 161 | def call_atonce(*args, **kwargs): 162 | def wrapper(callable_target): 163 | return callable_target(*args, **kwargs) 164 | 165 | return wrapper 166 | 167 | 168 | class InsertGenerator: 169 | base: Iterable[Any] 170 | insert_items: List[Any] 171 | 172 | def __init__(self, base_iterable: Iterable, pre_items: List[Any] = None) -> None: 173 | self.base = base_iterable 174 | self.insert_items = pre_items or [] 175 | 176 | def __iter__(self): 177 | for i in self.base: 178 | if self.insert_items: 179 | yield self.insert_items.pop() 180 | yield i 181 | else: 182 | if self.insert_items: 183 | yield from self.insert_items[::-1] 184 | 185 | 186 | class MultiUsageGenerator(InsertGenerator): 187 | continue_count: int 188 | 189 | def __init__(self, base_iterable: Iterable, pre_items: List[Any] = None) -> None: 190 | super().__init__(base_iterable, pre_items=pre_items) 191 | self.continue_count = 0 192 | 193 | def __iter__(self): 194 | for i in super().__iter__(): 195 | if self.continue_count > 0: 196 | self.continue_count -= 1 197 | continue 198 | yield i 199 | 200 | 201 | class AutoUnpackTuple: 202 | def __init__(self, base_iterable: Iterable, pre_items: List[Any] = None) -> None: 203 | self.base = base_iterable 204 | 205 | def __iter__(self): 206 | for i in self.base: 207 | if isinstance(i, tuple): 208 | yield from i 209 | continue 210 | yield i 211 | 212 | 213 | def yes_or_no(value: bool) -> str: 214 | return "yes" if value else "no" 215 | -------------------------------------------------------------------------------- /docs/graia/broadcast/builtin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.builtin API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 50 | 70 |
71 | 74 | 75 | -------------------------------------------------------------------------------- /docs/graia/application/message/parser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.message.parser API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 58 | 80 |
81 | 84 | 85 | -------------------------------------------------------------------------------- /docs/graia/broadcast/entities/decorator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.entities.decorator API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.broadcast.entities.decorator

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from typing import Any, Callable
 30 | 
 31 | 
 32 | class Decorator:
 33 |     target: Callable[[Any], Any]
 34 |     pre: bool = False
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |

Classes

45 |
46 |
47 | class Decorator 48 |
49 |
50 |
51 |
52 | 53 | Expand source code 54 | 55 |
class Decorator:
 56 |     target: Callable[[Any], Any]
 57 |     pre: bool = False
58 |
59 |

Subclasses

60 | 64 |

Class variables

65 |
66 |
var pre : bool
67 |
68 |
69 |
70 |
var target : Callable[[Any], Any]
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | 103 |
104 | 107 | 108 | -------------------------------------------------------------------------------- /docs/graia/application/entry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.entry API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.application.entry

23 |
24 |
25 |

这个模块用于为开发者提供一站式的导入体验.

26 |
27 | 28 | Expand source code 29 | 30 |
"""这个模块用于为开发者提供一站式的导入体验."""
 31 | 
 32 | from . import GraiaMiraiApplication
 33 | from .logger import AbstractLogger, LoggingLogger
 34 | from .event.dispatcher import MessageChainCatcher
 35 | from .event.lifecycle import ApplicationLaunched, ApplicationShutdowned
 36 | from .event.messages import FriendMessage, GroupMessage, TempMessage
 37 | from .event.mirai import (
 38 |     BotOnlineEvent,
 39 |     BotOfflineEventActive,
 40 |     BotOfflineEventForce,
 41 |     BotOfflineEventDropped,
 42 |     BotReloginEvent,
 43 |     BotGroupPermissionChangeEvent,
 44 |     BotMuteEvent,
 45 |     BotUnmuteEvent,
 46 |     BotJoinGroupEvent,
 47 |     GroupRecallEvent,
 48 |     FriendRecallEvent,
 49 |     GroupNameChangeEvent,
 50 |     GroupEntranceAnnouncementChangeEvent,
 51 |     GroupMuteAllEvent,
 52 |     GroupAllowAnonymousChatEvent,
 53 |     GroupAllowConfessTalkEvent,
 54 |     GroupAllowMemberInviteEvent,
 55 |     MemberJoinEvent,
 56 |     MemberLeaveEventKick,
 57 |     MemberLeaveEventQuit,
 58 |     MemberCardChangeEvent,
 59 |     MemberSpecialTitleChangeEvent,
 60 |     MemberPermissionChangeEvent,
 61 |     MemberMuteEvent,
 62 |     MemberUnmuteEvent,
 63 |     NewFriendRequestEvent,
 64 |     MemberJoinRequestEvent,
 65 |     BotInvitedJoinGroupRequestEvent,
 66 | )
 67 | from .message.elements.internal import (
 68 |     Plain,
 69 |     Source,
 70 |     Quote,
 71 |     At,
 72 |     AtAll,
 73 |     Face,
 74 |     Image,
 75 |     FlashImage,
 76 |     Xml,
 77 |     Json,
 78 |     App,
 79 |     Poke,
 80 |     PokeMethods,
 81 | )
 82 | from .message.chain import MessageChain
 83 | from .friend import Friend
 84 | from .group import MemberPerm, Group, Member, MemberInfo, GroupConfig
 85 | from .session import Session
 86 | from .exceptions import (
 87 |     InvalidEventTypeDefinition,
 88 |     InvaildAuthkey,
 89 |     AccountNotFound,
 90 |     InvaildSession,
 91 |     UnauthorizedSession,
 92 |     UnknownTarget,
 93 |     AccountMuted,
 94 |     TooLongMessage,
 95 |     InvaildArgument,
 96 |     NotSupportedVersion,
 97 |     DeprecatedImpl,
 98 |     EntangledSuperposition,
 99 |     MissingNecessaryOne,
100 | )
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | 125 |
126 | 129 | 130 | -------------------------------------------------------------------------------- /docs/graia/application/friend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.friend API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.application.friend

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from pydantic import BaseModel
 30 | 
 31 | 
 32 | class Friend(BaseModel):
 33 |     "描述 Tencent QQ 中的可发起对话对象 '好友(friend)' 的能被获取到的信息."
 34 | 
 35 |     id: int
 36 |     nickname: str
 37 |     remark: str
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |

Classes

48 |
49 |
50 | class Friend 51 | (**data: Any) 52 |
53 |
54 |

描述 Tencent QQ 中的可发起对话对象 '好友(friend)' 的能被获取到的信息.

55 |

Create a new model by parsing and validating input data from keyword arguments.

56 |

Raises ValidationError if the input data cannot be parsed to form a valid model.

57 |
58 | 59 | Expand source code 60 | 61 |
class Friend(BaseModel):
 62 |     "描述 Tencent QQ 中的可发起对话对象 '好友(friend)' 的能被获取到的信息."
 63 | 
 64 |     id: int
 65 |     nickname: str
 66 |     remark: str
67 |
68 |

Ancestors

69 |
    70 |
  • pydantic.main.BaseModel
  • 71 |
  • pydantic.utils.Representation
  • 72 |
73 |

Class variables

74 |
75 |
var id : int
76 |
77 |
78 |
79 |
var nickname : str
80 |
81 |
82 |
83 |
var remark : str
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | 117 |
118 | 121 | 122 | -------------------------------------------------------------------------------- /docs/graia/broadcast/priority.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.priority API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.broadcast.priority

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from enum import IntEnum
 30 | 
 31 | 
 32 | class Priority(IntEnum):
 33 |     Special = -1
 34 |     Default = 16
 35 |     BuiltinListener = 8
 36 |     Logger = -2
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |

Classes

47 |
48 |
49 | class Priority 50 | (value, names=None, *, module=None, qualname=None, type=None, start=1) 51 |
52 |
53 |

An enumeration.

54 |
55 | 56 | Expand source code 57 | 58 |
class Priority(IntEnum):
 59 |     Special = -1
 60 |     Default = 16
 61 |     BuiltinListener = 8
 62 |     Logger = -2
63 |
64 |

Ancestors

65 |
    66 |
  • enum.IntEnum
  • 67 |
  • builtins.int
  • 68 |
  • enum.Enum
  • 69 |
70 |

Class variables

71 |
72 |
var BuiltinListener
73 |
74 |
75 |
76 |
var Default
77 |
78 |
79 |
80 |
var Logger
81 |
82 |
83 |
84 |
var Special
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | 119 |
120 | 123 | 124 | -------------------------------------------------------------------------------- /docs/graia/application/message/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.message API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.application.message

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from pydantic import BaseModel
 30 | 
 31 | 
 32 | class BotMessage(BaseModel):
 33 |     messageId: int
34 |
35 |
36 |
37 |

Sub-modules

38 |
39 |
graia.application.message.chain
40 |
41 |
42 |
43 |
graia.application.message.elements
44 |
45 |
46 |
47 |
graia.application.message.parser
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |

Classes

59 |
60 |
61 | class BotMessage 62 | (**data: Any) 63 |
64 |
65 |

Create a new model by parsing and validating input data from keyword arguments.

66 |

Raises ValidationError if the input data cannot be parsed to form a valid model.

67 |
68 | 69 | Expand source code 70 | 71 |
class BotMessage(BaseModel):
 72 |     messageId: int
73 |
74 |

Ancestors

75 |
    76 |
  • pydantic.main.BaseModel
  • 77 |
  • pydantic.utils.Representation
  • 78 |
79 |

Class variables

80 |
81 |
var messageId : int
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | 120 |
121 | 124 | 125 | -------------------------------------------------------------------------------- /docs/graia/broadcast/entities/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.entities API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.broadcast.entities

23 |
24 |
25 |
26 |
27 |

Sub-modules

28 |
29 |
graia.broadcast.entities.context
30 |
31 |
32 |
33 |
graia.broadcast.entities.decorator
34 |
35 |
36 |
37 |
graia.broadcast.entities.dispatcher
38 |
39 |
40 |
41 |
graia.broadcast.entities.event
42 |
43 |
44 |
45 |
graia.broadcast.entities.exectarget
46 |
47 |
48 |
49 |
graia.broadcast.entities.listener
50 |
51 |
52 |
53 |
graia.broadcast.entities.namespace
54 |
55 |
56 |
57 |
graia.broadcast.entities.signatures
58 |
59 |
60 |
61 |
graia.broadcast.entities.track_log
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 100 |
101 | 104 | 105 | -------------------------------------------------------------------------------- /src/graia/application/message/parser/literature.py: -------------------------------------------------------------------------------- 1 | import re 2 | import shlex 3 | import getopt 4 | import itertools 5 | from typing import Dict, List, Tuple 6 | from graia.broadcast.entities.dispatcher import BaseDispatcher 7 | from graia.broadcast.entities.signatures import Force 8 | from graia.broadcast.exceptions import ExecutionStop 9 | 10 | from graia.broadcast.interfaces.dispatcher import DispatcherInterface 11 | from graia.broadcast.utilles import printer 12 | from graia.application.message.chain import MessageChain, MessageIndex 13 | from graia.application.message.elements import Element 14 | from graia.application.message.elements.internal import ( 15 | At, 16 | App, 17 | Json, 18 | Plain, 19 | Quote, 20 | Source, 21 | Xml, 22 | Voice, 23 | Poke, 24 | FlashImage, 25 | ) 26 | 27 | from graia.application.message.parser.pattern import ( 28 | BoxParameter, 29 | ParamPattern, 30 | SwitchParameter, 31 | ) 32 | 33 | BLOCKING_ELEMENTS = (Xml, Json, App, Poke, Voice, FlashImage) 34 | 35 | 36 | class Literature(BaseDispatcher): 37 | "旅途的浪漫" 38 | 39 | always = False 40 | prefixs: Tuple[str] # 匹配前缀 41 | arguments: Dict[str, ParamPattern] 42 | 43 | allow_quote: bool 44 | skip_one_at_in_quote: bool 45 | 46 | def __init__( 47 | self, 48 | *prefixs, 49 | arguments: Dict[str, ParamPattern] = None, 50 | allow_quote: bool = False, 51 | skip_one_at_in_quote: bool = False, 52 | ) -> None: 53 | self.prefixs = prefixs 54 | self.arguments = arguments or {} 55 | self.allow_quote = allow_quote 56 | self.skip_one_at_in_quote = skip_one_at_in_quote 57 | 58 | def trans_to_map(self, message_chain: MessageChain): 59 | string_result: List[str] = [] 60 | id_elem_map: Dict[int, Element] = {} 61 | 62 | for elem in message_chain.__root__: 63 | if isinstance(elem, Plain): 64 | string_result.append( 65 | re.sub( 66 | r"\$(?P\d+)", 67 | lambda match: f'\\${match.group("id")}', 68 | elem.text, 69 | ) 70 | ) 71 | else: 72 | index = len(id_elem_map) + 1 73 | string_result.append(f"${index}") 74 | id_elem_map[index] = elem 75 | 76 | return ("".join(string_result), id_elem_map) 77 | 78 | def gen_long_map(self): 79 | result = {} 80 | for param_name, arg in self.arguments.items(): 81 | for long in arg.longs: 82 | if long in result: 83 | raise ValueError("conflict item") 84 | result[long] = param_name 85 | return result 86 | 87 | def gen_short_map(self): 88 | result = {} 89 | for param_name, arg in self.arguments.items(): 90 | if arg.short in result: 91 | raise ValueError("conflict item") 92 | result[arg.short] = param_name 93 | return result 94 | 95 | def gen_long_map_with_bar(self): 96 | return {("--" + k): v for k, v in self.gen_long_map().items()} 97 | 98 | def gen_short_map_with_bar(self): 99 | return {("-" + k): v for k, v in self.gen_short_map().items() if k is not None} 100 | 101 | def parse_message(self, message_chain: MessageChain): 102 | string_result, id_elem_map = self.trans_to_map(message_chain) 103 | 104 | parsed_args, variables = getopt.getopt( 105 | shlex.split(string_result), 106 | "".join( 107 | [ 108 | arg.short if isinstance(arg, SwitchParameter) else (arg.short + ":") 109 | for arg in self.arguments.values() 110 | if arg.short 111 | ] 112 | ), 113 | [ 114 | long if isinstance(arg, SwitchParameter) else long + "=" 115 | for arg in self.arguments.values() 116 | for long in arg.longs 117 | ], 118 | ) 119 | map_with_bar = {**self.gen_long_map_with_bar(), **self.gen_short_map_with_bar()} 120 | parsed_args = { 121 | map_with_bar[k]: ( 122 | MessageChain.create( 123 | [ 124 | Plain(i) 125 | if not re.match("^\$\d+$", i) 126 | else id_elem_map[int(i[1:])] 127 | for i in re.split(r"((? len(chain_frames): 170 | return 171 | for index, current_prefix in enumerate(self.prefixs): 172 | current_frame = chain_frames[index] 173 | if ( 174 | not current_frame.__root__ 175 | or type(current_frame.__root__[0]) is not Plain 176 | ): 177 | return 178 | if current_frame.__root__[0].text != current_prefix: 179 | return 180 | 181 | chain_frames = chain_frames[len(self.prefixs) :] 182 | return MessageChain.create( 183 | list(itertools.chain(*[i.__root__ + [Plain(" ")] for i in chain_frames]))[ 184 | :-1 185 | ] 186 | ).asMerged() 187 | 188 | async def beforeDispatch(self, interface: DispatcherInterface): 189 | message_chain: MessageChain = ( 190 | await interface.lookup_param( 191 | "__literature_messagechain__", MessageChain, None 192 | ) 193 | ).exclude(Source) 194 | if set([i.__class__ for i in message_chain.__root__]).intersection( 195 | BLOCKING_ELEMENTS 196 | ): 197 | raise ExecutionStop() 198 | if self.allow_quote and message_chain.has(Quote): 199 | # 自动忽略自 Quote 后第一个 At 200 | message_chain = message_chain[(1, None):] 201 | if self.skip_one_at_in_quote and message_chain.__root__: 202 | if message_chain.__root__[0].__class__ is At: 203 | message_chain = message_chain[(1, 1):] 204 | noprefix = self.prefix_match(message_chain) 205 | if noprefix is None: 206 | raise ExecutionStop() 207 | 208 | interface.execution_contexts[-1].literature_detect_result = self.parse_message( 209 | noprefix 210 | ) 211 | 212 | async def catch(self, interface: DispatcherInterface): 213 | if interface.name == "__literature_messagechain__": 214 | return 215 | 216 | result = interface.execution_contexts[-1].literature_detect_result 217 | if result: 218 | match_result, variargs = result 219 | if interface.default == "__literature_variables__": 220 | return variargs 221 | 222 | arg_fetch_result = match_result.get(interface.name) 223 | if arg_fetch_result: 224 | 225 | match_value, raw_argument = arg_fetch_result 226 | if isinstance(raw_argument, SwitchParameter): 227 | return Force(match_value) 228 | elif interface.annotation is ParamPattern: 229 | return raw_argument 230 | elif match_value is not None: 231 | return match_value 232 | 233 | 234 | if __name__ == "__main__": 235 | from graia.application.message.elements.internal import AtAll, At 236 | 237 | mc = MessageChain.create( 238 | [ 239 | Plain('test n --f3 "1 2 tsthd thsd ydj re7u '), 240 | At(351453455), 241 | Plain(' " --f34 "arg arega er ae aghr ae rtyh'), 242 | # At(656735757), 243 | Plain(' "'), 244 | ] 245 | ) 246 | 247 | l = Literature( 248 | "test", 249 | "n", 250 | arguments={ 251 | "a": BoxParameter(["test_f1", "f23"], "f"), 252 | "b": SwitchParameter(["f34"], "d"), 253 | }, 254 | ) 255 | from devtools import debug 256 | 257 | # debug(l.prefix_match(mc)) 258 | debug(l.parse_message(l.prefix_match(mc))) 259 | print(mc.asDisplay()) 260 | -------------------------------------------------------------------------------- /docs/graia/application/context.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.context API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.application.context

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from contextvars import ContextVar
 30 | from contextlib import contextmanager
 31 | 
 32 | from graia.application.entities import UploadMethods
 33 | 
 34 | application = ContextVar("application")
 35 | event = ContextVar("event")
 36 | event_loop = ContextVar("event_loop")
 37 | broadcast = ContextVar("broadcast")
 38 | 
 39 | # for image
 40 | # sendGroupMessage 等发送message的指令将set该上下文条目.
 41 | image_method = ContextVar("image_method")
 42 | 
 43 | 
 44 | @contextmanager
 45 | def enter_message_send_context(method: UploadMethods):
 46 |     t = image_method.set(method)
 47 |     yield
 48 |     image_method.reset(t)
 49 | 
 50 | 
 51 | @contextmanager
 52 | def enter_context(app=None, event_i=None):
 53 |     t1 = None
 54 |     t2 = None
 55 |     t3 = None
 56 |     t4 = None
 57 | 
 58 |     if app:
 59 |         t1 = application.set(app)
 60 |         t3 = event_loop.set(app.broadcast.loop)
 61 |         t4 = broadcast.set(app.broadcast)
 62 |     if event_i:
 63 |         t2 = event.set(event_i)
 64 | 
 65 |     yield
 66 |     try:
 67 |         if t1:
 68 |             application.reset(t1)
 69 | 
 70 |         if all([t2, t3, t4]):
 71 |             event.reset(t2)
 72 |             event_loop.reset(t3)
 73 |             broadcast.reset(t4)
 74 |     except ValueError:  # 在测试 Scheduler 时发现的问题...辣鸡 Python!
 75 |         pass
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |

Functions

84 |
85 |
86 | def enter_context(app=None, event_i=None) 87 |
88 |
89 |
90 |
91 | 92 | Expand source code 93 | 94 |
@contextmanager
 95 | def enter_context(app=None, event_i=None):
 96 |     t1 = None
 97 |     t2 = None
 98 |     t3 = None
 99 |     t4 = None
100 | 
101 |     if app:
102 |         t1 = application.set(app)
103 |         t3 = event_loop.set(app.broadcast.loop)
104 |         t4 = broadcast.set(app.broadcast)
105 |     if event_i:
106 |         t2 = event.set(event_i)
107 | 
108 |     yield
109 |     try:
110 |         if t1:
111 |             application.reset(t1)
112 | 
113 |         if all([t2, t3, t4]):
114 |             event.reset(t2)
115 |             event_loop.reset(t3)
116 |             broadcast.reset(t4)
117 |     except ValueError:  # 在测试 Scheduler 时发现的问题...辣鸡 Python!
118 |         pass
119 |
120 |
121 |
122 | def enter_message_send_context(method: UploadMethods) 123 |
124 |
125 |
126 |
127 | 128 | Expand source code 129 | 130 |
@contextmanager
131 | def enter_message_send_context(method: UploadMethods):
132 |     t = image_method.set(method)
133 |     yield
134 |     image_method.reset(t)
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | 161 |
162 | 165 | 166 | -------------------------------------------------------------------------------- /docs/graia/broadcast/builtin/event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.builtin.event API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.broadcast.builtin.event

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from typing import TYPE_CHECKING
 30 | from ..entities.dispatcher import BaseDispatcher
 31 | from ..entities.event import BaseEvent
 32 | 
 33 | if TYPE_CHECKING:
 34 |     from graia.broadcast.interfaces.dispatcher import DispatcherInterface
 35 | 
 36 | 
 37 | class ExceptionThrowed(BaseEvent):
 38 |     exception: Exception
 39 |     event: BaseEvent
 40 | 
 41 |     class Dispatcher(BaseDispatcher):
 42 |         @staticmethod
 43 |         def catch(interface: "DispatcherInterface"):
 44 |             if interface.annotation == interface.event.exception.__class__:
 45 |                 return interface.event.exception
 46 |             if interface.name == "event":
 47 |                 return interface.event.event
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |

Classes

58 |
59 |
60 | class ExceptionThrowed 61 | (**data: Any) 62 |
63 |
64 |

Create a new model by parsing and validating input data from keyword arguments.

65 |

Raises ValidationError if the input data cannot be parsed to form a valid model.

66 |
67 | 68 | Expand source code 69 | 70 |
class ExceptionThrowed(BaseEvent):
 71 |     exception: Exception
 72 |     event: BaseEvent
 73 | 
 74 |     class Dispatcher(BaseDispatcher):
 75 |         @staticmethod
 76 |         def catch(interface: "DispatcherInterface"):
 77 |             if interface.annotation == interface.event.exception.__class__:
 78 |                 return interface.event.exception
 79 |             if interface.name == "event":
 80 |                 return interface.event.event
81 |
82 |

Ancestors

83 |
    84 |
  • BaseEvent
  • 85 |
  • pydantic.main.BaseModel
  • 86 |
  • pydantic.utils.Representation
  • 87 |
88 |

Class variables

89 |
90 |
var Dispatcher
91 |
92 |

所有非单函数型 Dispatcher 的基类, 用于为参数解析提供可扩展的支持.

93 |
94 |
var eventBaseEvent
95 |
96 |
97 |
98 |
var exception : Exception
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | 132 |
133 | 136 | 137 | -------------------------------------------------------------------------------- /docs/graia/application/event/dispatcher.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.event.dispatcher API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.application.event.dispatcher

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from graia.broadcast.entities.dispatcher import BaseDispatcher
 30 | from graia.broadcast.interfaces.dispatcher import DispatcherInterface
 31 | from graia.application.message.chain import MessageChain
 32 | 
 33 | 
 34 | class MessageChainCatcher(BaseDispatcher):
 35 |     @staticmethod
 36 |     async def catch(interface: "DispatcherInterface"):
 37 |         if interface.annotation is MessageChain:
 38 |             return interface.event.messageChain
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |

Classes

49 |
50 |
51 | class MessageChainCatcher 52 |
53 |
54 |

所有非单函数型 Dispatcher 的基类, 用于为参数解析提供可扩展的支持.

55 |
56 | 57 | Expand source code 58 | 59 |
class MessageChainCatcher(BaseDispatcher):
 60 |     @staticmethod
 61 |     async def catch(interface: "DispatcherInterface"):
 62 |         if interface.annotation is MessageChain:
 63 |             return interface.event.messageChain
64 |
65 |

Ancestors

66 | 69 |

Inherited members

70 | 85 |
86 |
87 |
88 |
89 | 109 |
110 | 113 | 114 | -------------------------------------------------------------------------------- /docs/graia/application/entities.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.application.entities API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.application.entities

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from enum import Enum
 30 | from pydantic import BaseModel
 31 | 
 32 | 
 33 | class UploadMethods(Enum):
 34 |     """用于向 `uploadImage` 或 `uploadVoice` 方法描述图片的上传类型"""
 35 | 
 36 |     Friend = "friend"
 37 |     Group = "group"
 38 |     Temp = "temp"
 39 | 
 40 | 
 41 | class MiraiConfig(BaseModel):
 42 |     """`/config` 接口的序列化实体类"""
 43 | 
 44 |     cacheSize: int
 45 |     enableWebsocket: bool
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |

Classes

56 |
57 |
58 | class MiraiConfig 59 | (**data: Any) 60 |
61 |
62 |

/config 接口的序列化实体类

63 |

Create a new model by parsing and validating input data from keyword arguments.

64 |

Raises ValidationError if the input data cannot be parsed to form a valid model.

65 |
66 | 67 | Expand source code 68 | 69 |
class MiraiConfig(BaseModel):
 70 |     """`/config` 接口的序列化实体类"""
 71 | 
 72 |     cacheSize: int
 73 |     enableWebsocket: bool
74 |
75 |

Ancestors

76 |
    77 |
  • pydantic.main.BaseModel
  • 78 |
  • pydantic.utils.Representation
  • 79 |
80 |

Class variables

81 |
82 |
var cacheSize : int
83 |
84 |
85 |
86 |
var enableWebsocket : bool
87 |
88 |
89 |
90 |
91 |
92 |
93 | class UploadMethods 94 | (value, names=None, *, module=None, qualname=None, type=None, start=1) 95 |
96 |
97 |

用于向 uploadImageuploadVoice 方法描述图片的上传类型

98 |
99 | 100 | Expand source code 101 | 102 |
class UploadMethods(Enum):
103 |     """用于向 `uploadImage` 或 `uploadVoice` 方法描述图片的上传类型"""
104 | 
105 |     Friend = "friend"
106 |     Group = "group"
107 |     Temp = "temp"
108 |
109 |

Ancestors

110 |
    111 |
  • enum.Enum
  • 112 |
113 |

Class variables

114 |
115 |
var Friend
116 |
117 |
118 |
119 |
var Group
120 |
121 |
122 |
123 |
var Temp
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | 164 |
165 | 168 | 169 | -------------------------------------------------------------------------------- /docs/graia/broadcast/entities/namespace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.entities.namespace API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.broadcast.entities.namespace

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from typing import List
 30 | 
 31 | from pydantic import BaseModel  # pylint: disable=no-name-in-module
 32 | 
 33 | from .dispatcher import BaseDispatcher
 34 | 
 35 | 
 36 | class Namespace(BaseModel):
 37 |     name: str
 38 |     injected_dispatchers: List[BaseDispatcher] = []
 39 | 
 40 |     priority: int = 0
 41 |     default: bool = False
 42 | 
 43 |     hide: bool = False
 44 |     disabled: bool = False
 45 | 
 46 |     class Config:
 47 |         arbitrary_types_allowed = True
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |

Classes

58 |
59 |
60 | class Namespace 61 | (**data: Any) 62 |
63 |
64 |

Create a new model by parsing and validating input data from keyword arguments.

65 |

Raises ValidationError if the input data cannot be parsed to form a valid model.

66 |
67 | 68 | Expand source code 69 | 70 |
class Namespace(BaseModel):
 71 |     name: str
 72 |     injected_dispatchers: List[BaseDispatcher] = []
 73 | 
 74 |     priority: int = 0
 75 |     default: bool = False
 76 | 
 77 |     hide: bool = False
 78 |     disabled: bool = False
 79 | 
 80 |     class Config:
 81 |         arbitrary_types_allowed = True
82 |
83 |

Ancestors

84 |
    85 |
  • pydantic.main.BaseModel
  • 86 |
  • pydantic.utils.Representation
  • 87 |
88 |

Class variables

89 |
90 |
var Config
91 |
92 |
93 |
94 |
var default : bool
95 |
96 |
97 |
98 |
var disabled : bool
99 |
100 |
101 |
102 |
var hide : bool
103 |
104 |
105 |
106 |
var injected_dispatchers : List[BaseDispatcher]
107 |
108 |
109 |
110 |
var name : str
111 |
112 |
113 |
114 |
var priority : int
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | 152 |
153 | 156 | 157 | -------------------------------------------------------------------------------- /docs/graia/broadcast/entities/listener.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | graia.broadcast.entities.listener API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module graia.broadcast.entities.listener

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from typing import Callable, List, Type
 30 | 
 31 | from .dispatcher import BaseDispatcher
 32 | from .event import BaseEvent
 33 | from .namespace import Namespace
 34 | from .decorator import Decorator
 35 | 
 36 | from .exectarget import ExecTarget
 37 | 
 38 | 
 39 | class Listener(ExecTarget):
 40 |     def __init__(
 41 |         self,
 42 |         callable: Callable,
 43 |         namespace: Namespace,
 44 |         listening_events: List[Type[BaseEvent]],
 45 |         inline_dispatchers: List[BaseDispatcher] = None,
 46 |         headless_decorators: List[Decorator] = None,
 47 |         priority: int = 16,
 48 |         enable_internal_access: bool = False,
 49 |     ) -> None:
 50 |         super().__init__(
 51 |             callable, inline_dispatchers, headless_decorators, enable_internal_access
 52 |         )
 53 |         self.namespace = namespace
 54 |         self.listening_events = listening_events
 55 |         self.priority = priority
 56 | 
 57 |     namespace: Namespace
 58 |     listening_events: List[Type[BaseEvent]]
 59 |     priority: int = 16
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Classes

70 |
71 |
72 | class Listener 73 | (callable: Callable, namespace: Namespace, listening_events: List[Type[BaseEvent]], inline_dispatchers: List[BaseDispatcher] = None, headless_decorators: List[Decorator] = None, priority: int = 16, enable_internal_access: bool = False) 74 |
75 |
76 |
77 |
78 | 79 | Expand source code 80 | 81 |
class Listener(ExecTarget):
 82 |     def __init__(
 83 |         self,
 84 |         callable: Callable,
 85 |         namespace: Namespace,
 86 |         listening_events: List[Type[BaseEvent]],
 87 |         inline_dispatchers: List[BaseDispatcher] = None,
 88 |         headless_decorators: List[Decorator] = None,
 89 |         priority: int = 16,
 90 |         enable_internal_access: bool = False,
 91 |     ) -> None:
 92 |         super().__init__(
 93 |             callable, inline_dispatchers, headless_decorators, enable_internal_access
 94 |         )
 95 |         self.namespace = namespace
 96 |         self.listening_events = listening_events
 97 |         self.priority = priority
 98 | 
 99 |     namespace: Namespace
100 |     listening_events: List[Type[BaseEvent]]
101 |     priority: int = 16
102 |
103 |

Ancestors

104 | 107 |

Class variables

108 |
109 |
var listening_events : List[Type[BaseEvent]]
110 |
111 |
112 |
113 |
var namespaceNamespace
114 |
115 |
116 |
117 |
var priority : int
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | 151 |
152 | 155 | 156 | --------------------------------------------------------------------------------