Repository: nonebot/nonebot2

Issue:
Bug: MessageTemplate.format 将消息段错误拼接为文本
**描述问题：**

`MessageTemplate.format` 将非文本类型消息段错误拼接为文本类型

**如何复现？**

```python
>>> from nonebot.adapters.onebot.v11 import Message, MessageSegment
>>> Message.template("{}{}").format(MessageSegment.image("file:///"), "hello")
[MessageSegment(type='text', data={'text': '[CQ:image,file=file:///,cache=true,proxy=true]'}), MessageSegment(type='text', data={'text': 'hello'})]
```

**期望的结果**

```python
>>> from nonebot.adapters.onebot.v11 import Message, MessageSegment
>>> Message.template("{}{}").format(MessageSegment.image("file:///"), "hello")
[MessageSegment(type='image', data={'file': 'file:///', 'type': None, 'cache': 'true', 'proxy': 'true', 'timeout': None}), MessageSegment(type='text', data={'text': 'hello'})]
```

**环境信息：**

 - OS: Windows 10
 - Python Version: 3.9.6
 - Nonebot Version: 2.0.0-beta2

**截图或日志**

![before](https://user-images.githubusercontent.com/68982190/165677394-d5679b8c-6ab8-4eb9-a796-c5e597273031.png)

**备注**

我自己写了一段修复代码（不保证稳定性）

[`template.py`](https://github.com/nonebot/nonebot2/blob/master/nonebot/internal/adapter/template.py)将原来的

```python
formatted_text = self.format_field(obj, str(format_control))
results.append(formatted_text)
```
替换为
```python
from .message import MessageSegment
if isinstance(obj, MessageSegment):
    results.append(obj)
else:
    formatted_text = self.format_field(obj, str(format_control))
    results.append(formatted_text)
```

修复后效果如下

![fix](https://user-images.githubusercontent.com/68982190/165677717-e7cff460-d2d4-47ff-907d-a8a9fc0a6a84.png)
Bug: MessageTemplate.format 将消息段错误拼接为文本
**描述问题：**

`MessageTemplate.format` 将非文本类型消息段错误拼接为文本类型

**如何复现？**

```python
>>> from nonebot.adapters.onebot.v11 import Message, MessageSegment
>>> Message.template("{}{}").format(MessageSegment.image("file:///"), "hello")
[MessageSegment(type='text', data={'text': '[CQ:image,file=file:///,cache=true,proxy=true]'}), MessageSegment(type='text', data={'text': 'hello'})]
```

**期望的结果**

```python
>>> from nonebot.adapters.onebot.v11 import Message, MessageSegment
>>> Message.template("{}{}").format(MessageSegment.image("file:///"), "hello")
[MessageSegment(type='image', data={'file': 'file:///', 'type': None, 'cache': 'true', 'proxy': 'true', 'timeout': None}), MessageSegment(type='text', data={'text': 'hello'})]
```

**环境信息：**

 - OS: Windows 10
 - Python Version: 3.9.6
 - Nonebot Version: 2.0.0-beta2

**截图或日志**

![before](https://user-images.githubusercontent.com/68982190/165677394-d5679b8c-6ab8-4eb9-a796-c5e597273031.png)

**备注**

我自己写了一段修复代码（不保证稳定性）

[`template.py`](https://github.com/nonebot/nonebot2/blob/master/nonebot/internal/adapter/template.py)将原来的

```python
formatted_text = self.format_field(obj, str(format_control))
results.append(formatted_text)
```
替换为
```python
from .message import MessageSegment
if isinstance(obj, MessageSegment):
    results.append(obj)
else:
    formatted_text = self.format_field(obj, str(format_control))
    results.append(formatted_text)
```

修复后效果如下

![fix](https://user-images.githubusercontent.com/68982190/165677717-e7cff460-d2d4-47ff-907d-a8a9fc0a6a84.png)


Discussion:
https://github.com/nonebot/nonebot2/blob/3870f0084d4c7a856211d07d99d535a19c071e0c/nonebot/internal/adapter/template.py#L174-L178

应该是这里，onebot adapter 那边没指定 formatter，直接转 str 进来了。
@mnixry 🤗 
https://github.com/nonebot/nonebot2/blob/3870f0084d4c7a856211d07d99d535a19c071e0c/nonebot/internal/adapter/template.py#L174-L178

应该是这里，onebot adapter 那边没指定 formatter，直接转 str 进来了。
@mnixry 🤗 

Resolving patch:
diff --git a/nonebot/internal/adapter/template.py b/nonebot/internal/adapter/template.py
--- a/nonebot/internal/adapter/template.py
+++ b/nonebot/internal/adapter/template.py
@@ -49,7 +49,9 @@ def __init__(
     ) -> None:
         ...
 
-    def __init__(self, template, factory=str) -> None:
+    def __init__(  # type:ignore
+        self, template, factory=str
+    ) -> None:  # TODO: fix type hint here
         self.template: TF = template
         self.factory: Type[TF] = factory
         self.format_specs: Dict[str, FormatSpecFunc] = {}
@@ -72,25 +74,37 @@ def format_map(self, mapping: Mapping[str, Any]) -> TF:
         return self._format([], mapping)
 
     def _format(self, args: Sequence[Any], kwargs: Mapping[str, Any]) -> TF:
-        msg = self.factory()
+        full_message = self.factory()
+        used_args, arg_index = set(), 0
+
         if isinstance(self.template, str):
-            msg += self.vformat(self.template, args, kwargs)
+            msg, arg_index = self._vformat(
+                self.template, args, kwargs, used_args, arg_index
+            )
+            full_message += msg
         elif isinstance(self.template, self.factory):
             template = cast("Message[MessageSegment]", self.template)
             for seg in template:
-                msg += self.vformat(str(seg), args, kwargs) if seg.is_text() else seg
+                if not seg.is_text():
+                    full_message += seg
+                else:
+                    msg, arg_index = self._vformat(
+                        str(seg), args, kwargs, used_args, arg_index
+                    )
+                    full_message += msg
         else:
             raise TypeError("template must be a string or instance of Message!")
 
-        return msg  # type:ignore
+        self.check_unused_args(list(used_args), args, kwargs)
+        return cast(TF, full_message)
 
     def vformat(
-        self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
+        self,
+        format_string: str,
+        args: Sequence[Any],
+        kwargs: Mapping[str, Any],
     ) -> TF:
-        used_args = set()
-        result, _ = self._vformat(format_string, args, kwargs, used_args, 2)
-        self.check_unused_args(list(used_args), args, kwargs)
-        return result
+        raise NotImplementedError("`vformat` has merged into `_format`")
 
     def _vformat(
         self,
@@ -98,12 +112,8 @@ def _vformat(
         args: Sequence[Any],
         kwargs: Mapping[str, Any],
         used_args: Set[Union[int, str]],
-        recursion_depth: int,
         auto_arg_index: int = 0,
     ) -> Tuple[TF, int]:
-        if recursion_depth < 0:
-            raise ValueError("Max string recursion exceeded")
-
         results: List[Any] = [self.factory()]
 
         for (literal_text, field_name, format_spec, conversion) in self.parse(
@@ -143,23 +153,13 @@ def _vformat(
                 obj, arg_used = self.get_field(field_name, args, kwargs)
                 used_args.add(arg_used)
 
-                assert format_spec is not None
-
                 # do any conversion on the resulting object
                 obj = self.convert_field(obj, conversion) if conversion else obj
 
-                # expand the format spec, if needed
-                format_control, auto_arg_index = self._vformat(
-                    format_spec,
-                    args,
-                    kwargs,
-                    used_args,
-                    recursion_depth - 1,
-                    auto_arg_index,
-                )
-
                 # format the object and append to the result
-                formatted_text = self.format_field(obj, str(format_control))
+                formatted_text = (
+                    self.format_field(obj, format_spec) if format_spec else obj
+                )
                 results.append(formatted_text)
 
         return functools.reduce(self._add, results), auto_arg_index


Test patch:
diff --git a/tests/test_adapters/test_template.py b/tests/test_adapters/test_template.py
--- a/tests/test_adapters/test_template.py
+++ b/tests/test_adapters/test_template.py
@@ -32,6 +32,26 @@ def custom(input: str) -> str:
     assert str(formatted) == "custom-custom!text[fake:image]"
 
 
+def test_rich_template_message():
+    Message = make_fake_message()
+    MS = Message.get_segment_class()
+
+    pic1, pic2, pic3 = (
+        MS.image("file:///pic1.jpg"),
+        MS.image("file:///pic2.jpg"),
+        MS.image("file:///pic3.jpg"),
+    )
+
+    template = Message.template("{}{}" + pic2 + "{}")
+
+    result = template.format(pic1, "[fake:image]", pic3)
+
+    assert result["image"] == Message([pic1, pic2, pic3])
+    assert str(result) == (
+        "[fake:image]" + escape_text("[fake:image]") + "[fake:image]" + "[fake:image]"
+    )
+
+
 def test_message_injection():
     Message = make_fake_message()