# 源与适配器

## 添加源、适配器

此前的章节中提到过源与适配器。实际上一个 bot 实例允许你添加任意协议，任意数量的源与适配器。

```python
from melobot import Bot

bot = Bot(...)
# 添加一个输入源
bot.add_input(...)
# 添加一个输出源
bot.add_output(...)
# 添加一个输入输出源
bot.add_io(...)
# 添加一个适配器
bot.add_adapter(...)
```

> 假设使用 in[A], out[A], io[A], @[A] 分别表示 A 协议的输入源、输出源、输入输出源和适配器
>
> 以下的 + 并无顺序之分，只是应用在同一 bot 实例上的意思

典型的组合方式：

| 组合方式 | 效果 |
| --- | --- |
| in[A] + @[A] | A 协议输入 √，A 协议输出 ×（因为有些协议不一定有输出源） |
| out[A] + @[A] | A 协议输入 ×，A 协议输出 √（因为有些协议不一定有输入源） |
| in[A] + out[A] + @[A] | A 协议输入 √，A 协议输出 √ |
| io[A] + @[A] | A 协议输入 √，A 协议输出 √ |

不太合理的组合：

| 组合方式 | 效果 |
| --- | --- |
| in[A] | A 协议输入 ×，A 协议输出 ×（没有适配器，协议无法正常工作） |
| out[A] | A 协议输入 ×，A 协议输出 ×（没有适配器，协议无法正常工作） |
| @[A] | A 协议输入 ×，A 协议输出 ×（没有源，协议无法正常工作） |
| @[A] + @[A] | A 协议输入 ×，A 协议输出 ×（同一协议的适配器不能添加第二次，且没有源） |

```{admonition} 提示
:class: tip
对 bot 实例来说，同时存在同一协议的源与适配器才能正常工作。
```

同协议多路输入、多路输出组合：

| 组合方式 | 效果 |
| --- | --- |
| in[A] + in[A] + ... + out[A] + out[A] + ... + @[A] | 产生协议 A 的多路输入和输出（例如 OneBot 协议的多个账号输入和输出） |

```{admonition} 提示
:class: tip
多路输入和输出，如何选择、分配和管理，后续的章节中会介绍。
```

多协议组合：

| 组合方式 | 效果 |
| --- | --- |
| in[A] + @[A] + out[B] + @[B] | A 协议输入 √，A 协议输出 ×；B 协议输入 ×，B 协议输出 √ |

多协议且多路的组合：

| 组合方式 | 效果 |
| --- | --- |
| io[A] + in[A] + @[A] + io[B] + io[B] + @[B] | A 协议 2 路输入，1 路输出；B 协议 2 路输入，2 路输出 |

## 添加协议栈

协议栈是一组**同协议**的源与适配器的包装，用于简化分步添加源、适配器的繁琐过程。

例如 OneBot 协议的协议栈对象，允许你直接添加源对象，内部自动添加适配器：

```python
from melobot.protocols.onebot.v11 import OneBotV11Protocol, ForwardWebSocketIO, \
    ReverseWebSocketIO

# 添加指定数量的源
protocol = OneBotV11Protocol(
    ForwardWebSocketIO(...), 
    ReverseWebSocketIO(...), 
    ...
)
# 随后把协议栈对象提供给 bot 实例
# 协议栈对象会自动完成适配器添加
bot.add_protocol(protocol)
```

其他协议的协议栈对象，可能会有更抽象的包装行为：例如简化掉源对象创建，让用户专注于配置参数本身。

## 获取适配器对象

很自然的，可以在适配器创建时获取：

```python
from melobot.protocols.onebot.v11 import Adapter
# 但一般只会在主脚本创建适配器
adapter = Adapter(...)
# 因为随后要提供给 bot 实例
bot.add_adapter(adapter)
```

在插件中，通过 bot 获取适配器：

```python
from melobot import get_bot

bot = get_bot()
adapter = bot.get_adapter(...)
```

或者通过依赖注入获取：

```python
from melobot.protocols.onebot.v11 import Adapter

@on_xxx(...)
async def _(adapter: Adapter) -> None: ...
```

## 获取源对象

很自然的，可以在源对象创建时获取：

```python
from melobot.protocols.onebot.v11 import ForwardWebSocketIO
# 但一般只会在主脚本创建源
src = ForwardWebSocketIO(...)
# 因为随后要提供给 bot 实例
bot.add_io(src)
```

在插件中，通过适配器获取源：

```python
src = adapter.get_isrc(...)
src = adapter.get_osrc(...)
```

此外，还可以在事件处理过程中，获取事件的来源适配器和来源输入源：

```python
@on_xxx(...)
async def _(event: Event) -> None:
    info = event.get_origin_info()
    adapter, src = info.adapter, info.in_src
```

## 协议标识

所有源对象和适配器对象，都拥有 `protocol` 属性，为协议字符串。表示了所适用的协议类型。

```python
from melobot.protocols.onebot.v11 import ForwardWebSocketIO, Adapter

src = ForwardWebSocketIO(...)
adapter = Adapter(...)
assert src.protocol == "OneBot-v11@Meloland"
assert adapter.protocol == "OneBot-v11@Meloland"
```

一般协议字符串的风格为：`{PROTOCOL_NAME}-v{PROTOCOL_VERSION}@{PROTOCOL_SUPPORT_AUTHOR}`。

## 生命周期钩子

所有源对象和适配器对象都是可 hook 的。使用 {meth}`~.HookMixin.on` 方法可以绑定一个 hook 函数：

```python
# hook 函数同步异步均可
async def f1() -> None: ...
def f2() -> None: ...
```

使用 {class}`.SourceLifeSpan` 为源对象绑定 hook 函数：

```python
# 直接使用
from melobot.io import SourceLifeSpan
src.on(SourceLifeSpan.STARTED)(f1)

# 或取得装饰器
on_close = src.on(SourceLifeSpan.CLOSE)
on_close(f2)

# 或直接使用装饰器语法
@src.on(SourceLifeSpan.STOPPED)
async def f3() -> None: ...
```

使用  {class}`.AdapterLifeSpan` 为适配器对象绑定 hook 函数：

```python
# 直接使用
from melobot.adapter import AdapterLifeSpan
adapter.on(AdapterLifeSpan.STARTED)(f1)

# 或取得装饰器
on_started = adapter.on(AdapterLifeSpan.CLOSE)
on_started(f2)

# 或直接使用装饰器语法
@adapter.on(AdapterLifeSpan.STOPPED)
async def f3() -> None: ...
```

其他非通用属性与特性，请参考各个源、适配器的 API 文档。

## 总结

本篇主要说明了 melobot 源与适配器的使用方法和特性。

下一篇将重点说明：适配器层其他组件。
