Skip to content

Commit

Permalink
feat: add support for defining card messages with XML (#9)
Browse files Browse the repository at this point in the history
* feat: add card message XSD definition

* feat: add card message XML deserializer

* fix: handle multiline text correctly

* chore: include XSD in nuget package

* doc: add XML namespace docs

* fix: XSD element documentation

* fix: set project IsPackable to false

* feat: add sync overload and try methods

* feat: add sample

* fix: type error in schema

* fix: sample error

* doc: add documentation for xml card message

* fix: should use XML 1.0 as XmlReader does not support XML 1.1

* Update samples/Kook.Net.Samples.CardMarkup/Cards/big-card.xml

* Apply suggestions from code review

* fix: card markup doc typo

---------

Co-authored-by: Ge <[email protected]>
  • Loading branch information
AlisaAkiron and gehongyan authored Mar 19, 2024
1 parent 9fd2547 commit 76aa2f6
Show file tree
Hide file tree
Showing 52 changed files with 2,393 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Kook.Net.sln
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kook.Net.Samples.Docker", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kook.Net.Samples.Audio", "samples\Kook.Net.Samples.Audio\Kook.Net.Samples.Audio.csproj", "{B212D46B-458B-4F63-B35F-8EA07640AE34}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kook.Net.CardMarkup", "src\Kook.Net.CardMarkup\Kook.Net.CardMarkup.csproj", "{FFC53AF8-57CA-4541-98D2-2A3E22F21C9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kook.Net.Samples.CardMarkup", "samples\Kook.Net.Samples.CardMarkup\Kook.Net.Samples.CardMarkup.csproj", "{8695B8CA-5556-4772-B748-1DB2B92800BD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -111,6 +115,14 @@ Global
{B212D46B-458B-4F63-B35F-8EA07640AE34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B212D46B-458B-4F63-B35F-8EA07640AE34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B212D46B-458B-4F63-B35F-8EA07640AE34}.Release|Any CPU.Build.0 = Release|Any CPU
{FFC53AF8-57CA-4541-98D2-2A3E22F21C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFC53AF8-57CA-4541-98D2-2A3E22F21C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFC53AF8-57CA-4541-98D2-2A3E22F21C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFC53AF8-57CA-4541-98D2-2A3E22F21C9D}.Release|Any CPU.Build.0 = Release|Any CPU
{8695B8CA-5556-4772-B748-1DB2B92800BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8695B8CA-5556-4772-B748-1DB2B92800BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8695B8CA-5556-4772-B748-1DB2B92800BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8695B8CA-5556-4772-B748-1DB2B92800BD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57F76D67-0E55-4609-A61C-3A4C3073CA0E} = {58B166AB-82DD-472F-AC70-9318DA4F881E}
Expand All @@ -126,5 +138,7 @@ Global
{DCD692BE-9D91-4DE1-8603-CD8923C59B6A} = {8DE556F9-829D-48D3-A41C-FA57207CAE72}
{B8A50094-A959-4807-AB36-6256A1EEEE61} = {8DE556F9-829D-48D3-A41C-FA57207CAE72}
{B212D46B-458B-4F63-B35F-8EA07640AE34} = {8DE556F9-829D-48D3-A41C-FA57207CAE72}
{FFC53AF8-57CA-4541-98D2-2A3E22F21C9D} = {B51E68C1-B6FC-49A3-B04D-57BB0C19B192}
{8695B8CA-5556-4772-B748-1DB2B92800BD} = {8DE556F9-829D-48D3-A41C-FA57207CAE72}
EndGlobalSection
EndGlobal
6 changes: 6 additions & 0 deletions docs/demos/demos.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ title: 示例项目

该示例项目演示了如何使用 Kook.Net 中的文本命令框架,来构建一个易于扩展与维护的基于文本的命令交互 Bot。

## XML 卡片消息

### [Kook.Net.Samples.CardMarkup](https://github.com/gehongyan/Kook.Net/tree/master/samples/Kook.Net.Samples.CardMarkup)

该示例项目显示了如何使用 Kook.Net 中的 XML 卡片消息功能,使用 XML 标记语言来构建卡片消息,以及配合使用 Liquid 模版引擎,使用 `Fluid.Core` 库来在运行时通过模版构建卡片消息。

## 语音

### [Kook.Net.Samples.Audio](https://github.com/gehongyan/Kook.Net/tree/dev/samples/Kook.Net.Samples.Audio)
Expand Down
8 changes: 7 additions & 1 deletion docs/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
"**/obj/**",
"**.meta"
]
},
{
"files": [
"card-message.xsd"
],
"src": "../spec"
}
],
"dest": "_site",
Expand Down Expand Up @@ -97,4 +103,4 @@
"baseUrl": "https://www.kooknet.dev"
}
}
}
}
44 changes: 44 additions & 0 deletions docs/guides/card_markup/card.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
uid: Guides.CardMarkup.Card
title: 卡片
---

# 卡片

## 属性

| 属性 | 类型| 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| theme | string | null | | 卡片主题,可选值为 `primary` `success` `danger` `warning` `info` `secondary` `none` |
| size | string | null | | 卡片尺寸,可选值为 `small` `large` |
| color | string | null | | 卡片颜色,十六进制 RGB 色彩,以 `#` 开头 |

## 元素

每一个卡片需要包含一个 `<modules>` 元素,`<modules>` 元素包含卡片的[组件](modules.md)

```xml
<card>
<modules>
<!-- 卡片组件 -->
</modules>
</card>
```

## 示例 1

使用默认主题、尺寸、颜色的卡片,并包含一个 [标题模块](modules.md#标题-header)

[!code-xml[Card 01](samples/card/sample-01.xml)]

## 示例 2

使用 `warning` 主题、`small` 尺寸、`#aaaaaa` 颜色的卡片,并包含一个 [标题模块](modules.md#标题-header)、一个 [图片组模块](modules.md#图片组-images)

[!code-xml[Card 02](samples/card/sample-02.xml)]

## 示例 3

KOOK 消息编辑器中的投票消息模版。

[!code-xml[Card 03](samples/card/sample-03.xml)]
46 changes: 46 additions & 0 deletions docs/guides/card_markup/elements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
uid: Guides.CardMarkup.Elements
title: 元素
---

# 元素

[KOOK 开发者文档 - 卡片消息 - 元素](https://developer.kookapp.cn/doc/cardmessage#%E5%85%83%E7%B4%A0)

## 普通文本 `plain`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| emoji | bool | true | | 如果为 true,会把 emoji 的 shortcut 转为 emoji |

[!code-xml[Plain](samples/definitions/element-plain.xml)]

## KMarkdown `kmarkdown`

[!code-xml[KMarkdown](samples/definitions/element-kmarkdown.xml)]

## 图片 `image`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| src | anyURI | null || 图片 URL,必须以 `https://` 开头 |
| alt | string | null | | 图片的替代文本 |
| size | string | null | | 图片的尺寸,可选值为 `small` `large` |
| circle | bool | false | | 是否显示为圆形 |

[!code-xml[Image](samples/definitions/element-image.xml)]

## 按钮 `button`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| theme | string | null | | 按钮主题,可选值为 `primary` `success` `danger` `warning` `info` `secondary` |
| value | string | null | | 按钮需要传递的 value |
| click | string | null | | 按钮事件类型,可选值为 `link` `return-val` |

| 元素 | 数量 | 说明 |
| --- | --- | --- |
| [plain](#普通文本-plain) | 1,与 kmarkdown 互斥 | 按钮文本 |
| [kmarkdown](#kmarkdown-kmarkdown) | 1,与 plain 互斥 | 按钮文本 |

[!code-xml[Button](samples/definitions/element-button.xml)]
70 changes: 70 additions & 0 deletions docs/guides/card_markup/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
uid: Guides.CardMarkup.Intro
title: XML 卡片消息入门
---

# XML 卡片消息

[Kook.CardMarkup](xref:Kook.CardMarkup) 命名空间提供了将使用 XML 标记语言定义的卡片消息反序列化为 [ICard](xref:Kook.ICard) 对象的方法。

## 入门

下面的示例中,我们创建一个简单的,由标题、分割线和 9 张图片组成的一个卡片消息。

### XML 标记

创建一个 XML 文件,定义卡片消息的内容:

[!code-xml[Sample Card](samples/intro/sample-card.xml)]

#### XML 声明

文件第一行为 XML 声明,指定 XML 版本和字符编码:

> [!WARNING]
> 字符编码必须是 `UTF-8`
> XML 版本必须是 `1.0`
[!code-xml[Sample Card - XML Declaration](samples/intro/sample-card.xml#L1)]

#### 卡片消息

XML 根元素为 `<card-message>`,代表一个卡片消息,每一个 `<card-message>` 元素可以包含多个 `<card>` 元素。

`<card-message>` 元素上需要指定 XML 命名空间,以及 XML Schema 文件的位置:

[!code-xml[Sample Card - Root Element](samples/intro/sample-card.xml#L3-L5)]

- `xmlns=https://kooknet.dev` 指定了默认 XML 命名空间,卡片消息所有的元素均在该命名空间下。
- `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"` 指定了 XML Schema 实例命名空间,并设置命名空间前缀为 `xsi`
- `xsi:schemaLocation="https://kooknet.dev https://kooknet.dev/card-message.xsd"` 调用了 `xsi` 命名空间下的 `schemaLocation` 属性,指定了 `https://kooknet.dev` 命名空间下的 XML Schema 文件位置在 `https://kooknet.dev/card-message.xsd`

#### 卡片

`<card>` 元素代表一个卡片,每一个 `<card>` 元素包含一个 `<modules>` 元素,用于包含卡片的组件。

关于卡片,请参阅 [卡片](card.md)

### 反序列化

使用 [Kook.CardMarkup.CardMarkupSerializer](xref:Kook.CardMarkup.CardMarkupSerializer) 将 XML 卡片消息反序列化为 [ICard](xref:Kook.ICard) 对象:

> [!WARNING]
> `Try...` 方法只适用于同步调用。
> [!NOTE]
> 此示例传入参数为 XML 文件的 `FileInfo` 类实例。
> 所有方法均有传入参数为 `Stream``string` 的重载。
> 传出参数的类型均为 `IEnumerable<ICard>``Task<IEnumerable<ICard>>`
[!code-csharp[Deserialize Card](samples/intro/deserialize-sample-card.cs)]

### 渲染效果

该 XML 卡片消息等效于以下 JSON 格式的卡片消息:

[!code-json[Sample Card](samples/intro/sample-card.json)]

渲染效果如下:

![Sample Card](samples/intro/sample-card.png)
109 changes: 109 additions & 0 deletions docs/guides/card_markup/modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
uid: Guides.CardMarkup.Modules
title: 模块
---

# 模块

[KOOK 开发者文档 - 卡片消息 - 模块](https://developer.kookapp.cn/doc/cardmessage#%E6%A8%A1%E5%9D%97)

## 标题 `header`

| 元素 | 数量 | 说明 |
| --- | --- | --- |
| [plain](elements.md#普通文本-plain) | 1 | 标题文本 |

[!code-xml[Header](samples/definitions/module-header.xml)]

## 内容 `section`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| mode | string | right | | 其它元素的放置位置 |

| 元素 | 数量 | 说明 |
| --- | --- | --- |
| text | 1 | 内容模块的文本元素 |
| text/[plain](elements.md#普通文本-plain) | 1,与 `text/markdown` 互斥 | 内容模块的文本元素(纯文本) |
| text/[kmarkdown](elements.md#kmarkdown-kmarkdown) | 1,与 `text/plain` 互斥 | 内容模块的文本元素(KMarkdown) |
| accessory | 0-1 | 内容模块的其它元素 |
| accessory/[image](elements.md#图片-image) | 1,与 `accessory/button` 互斥 | 内容模块的其它元素(图片) |
| accessory/[button](elements.md#按钮-button) | 1,与 `accessory/image` 互斥 | 内容模块的其它元素(按钮) |

[!code-xml[Section](samples/definitions/module-section.xml)]

## 图片组 `images`

| 元素 | 数量 | 说明 |
| --- | --- | --- |
| [image](elements.md#图片-image) | 1-9 | 图片 |

[!code-xml[Images](samples/definitions/module-images.xml)]

## 容器 `container`

| 元素 | 数量 | 说明 |
| --- | --- | --- |
| [image](elements.md#图片-image) | 1-9 | 图片 |

[!code-xml[Container](samples/definitions/module-container.xml)]

## 交互 `actions`

| 元素 | 数量 | 说明 |
| --- | --- | --- |
| [button](elements.md#按钮-button) | 1-4 | 按钮 |

[!code-xml[Actions](samples/definitions/module-actions.xml)]

## 备注 `context`

| 元素 | 数量 | 说明 |
| --- | --- | --- |
| [plain](elements.md#普通文本-plain) |`kmarkdown` `image` 合计至多 10,至少 1 | 纯文本 |
| [kmarkdown](elements.md#kmarkdown-kmarkdown) |`plain` `image` 合计至多 10,至少 1 | KMarkdown |
| [image](elements.md#图片-image) |`plain` `kmarkdown` 合计至多 10,至少 1 | 图片 |

[!code-xml[Context](samples/definitions/module-context.xml)]

## 分割线 `divider`

[!code-xml[Divider](samples/definitions/module-divider.xml)]

## 文件 (文件/视频) `file` `video`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| src | anyURI | null || 文件 URL,必须以 `https://` 开头 |
| title | string | null | | 文件标题 |

[!code-xml[File](samples/definitions/module-file.xml)]
[!code-xml[Video](samples/definitions/module-video.xml)]

## 文件 (音频) `audio`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| src | anyURI | null || 文件 URL,必须以 `https://` 开头 |
| title | string | null | | 文件标题 |
| cover | anyURI | null | | 封面 URL,必须以 `https://` 开头 |

[!code-xml[Audio](samples/definitions/module-audio.xml)]

## 倒计时 `countdown`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| start | ulong | null | ⚠️ | 起始的毫秒时间戳,仅 `mode="second"` 时需要 |
| end | ulong | null || 到期的毫秒时间戳 |
| mode | string | null || 显示模式,可选值为 `day` `hour` `second` |

[!code-xml[Countdown](samples/definitions/module-countdown.xml)]

## 邀请 `invite`

| 属性 | 类型 | 默认值 | 必需 | 说明 |
| --- | --- | --- | --- | --- |
| code | string | null || 邀请链接或者邀请码 |

[!code-xml[Invite](samples/definitions/module-invite.xml)]
7 changes: 7 additions & 0 deletions docs/guides/card_markup/samples/card/sample-01.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<card>
<modules>
<header>
<plain>测试卡片</plain>
</header>
</modules>
</card>
21 changes: 21 additions & 0 deletions docs/guides/card_markup/samples/card/sample-02.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<card theme="warning" color="#aaaaaa" size="small">
<modules>

<header>
<plain>测试卡片</plain>
</header>

<images>
<image src="https://img.kaiheila.cn/assets/2021-01/pWsmcLsPJq08c08c.jpeg" />
<image src="https://img.kaiheila.cn/assets/2021-01/YIfHfnvxaV0dw0dw.jpg" />
<image src="https://img.kaiheila.cn/assets/2021-01/YevTY6eGJa0fk0f2.jpeg" />
<image src="https://img.kaiheila.cn/assets/2021-01/r2K9RjHZ4s0xc0xc.jpeg" />
<image src="https://img.kaiheila.cn/assets/2021-01/klosFRTVy90jz0k0.jpg" />
<image src="https://img.kaiheila.cn/assets/2021-01/veHnEhzu6c0dw0dv.jpg" />
<image src="https://img.kaiheila.cn/assets/2021-01/tiVWPIuTrf0dw0dw.jpg" />
<image src="https://img.kaiheila.cn/assets/2021-01/wExzRIrTeR0j60j7.jpeg" />
<image src="https://img.kaiheila.cn/assets/2021-01/AybvLWYQgA0dw0dw.jpg" />
</images>

</modules>
</card>
Loading

0 comments on commit 76aa2f6

Please sign in to comment.