# Directory Structure
```
├── .gitignore
├── config.json
├── Dockerfile
├── go.mod
├── go.sum
├── LICENSE
├── main.go
├── Makefile
├── README.md
├── smithery.yaml
├── wecom_test.go
└── wecom.go
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | mcp-webcombot-server
2 | dist/
3 | docs/
```
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "mcpServers": {
3 | "mcp-difyworkflow-server": {
4 | "command": "mcp-wecombot-server", // mcp-wecombot-server bainary Path or mcp-wecombot-server
5 | "env": {
6 | "WECOM_BOT_WEBHOOK_KEY":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
7 | }
8 | }
9 | }
10 | }
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/deployments
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | required:
9 | - wecomBotWebhookKey
10 | properties:
11 | wecomBotWebhookKey:
12 | type: string
13 | description: The webhook key for the WeCom Bot server.
14 | commandFunction:
15 | # A function that produces the CLI command to start the MCP on stdio.
16 | |-
17 | config => ({ command: 'mcp-wecombot-server', env: { WECOM_BOT_WEBHOOK_KEY: config.wecomBotWebhookKey } })
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Build stage
2 | FROM golang:1.21-alpine AS builder
3 |
4 | # Set the working directory inside the container
5 | WORKDIR /app
6 |
7 | # Copy go.mod and go.sum files first for dependency resolution
8 | COPY go.mod go.sum ./
9 |
10 | # Download dependencies
11 | RUN go mod download
12 |
13 | # Copy the entire source code into the container
14 | COPY . .
15 |
16 | # Build the binary for Linux
17 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/mcp-wecombot-server_linux_amd64 .
18 |
19 | # Final stage
20 | FROM alpine:latest
21 |
22 | # Set working directory
23 | WORKDIR /app
24 |
25 | # Copy the compiled binary from the builder stage
26 | COPY --from=builder /app/dist/mcp-wecombot-server_linux_amd64 /usr/local/bin/mcp-wecombot-server
27 |
28 | # Expose any ports the application requires, if necessary
29 | # EXPOSE <port>
30 |
31 | # Set the entrypoint to the compiled binary
32 | ENTRYPOINT ["mcp-wecombot-server"]
```
--------------------------------------------------------------------------------
/wecom_test.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "net/http/httptest"
9 | "os"
10 | "testing"
11 | )
12 |
13 | // MockWeComServer is a mock server for testing WeComBot.
14 | type MockWeComServer struct {
15 | *httptest.Server
16 | }
17 |
18 | // test upload file need to setting your's test key
19 | var testkey = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
20 |
21 | // NewMockWeComServer creates a new mock WeCom server.
22 | func NewMockWeComServer() *MockWeComServer {
23 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24 | if r.Method == "POST" {
25 | body, _ := io.ReadAll(r.Body)
26 | fmt.Println(string(body))
27 | w.Header().Set("Content-Type", "application/json")
28 | w.WriteHeader(http.StatusOK)
29 | json.NewEncoder(w).Encode(map[string]interface{}{
30 | "errcode": 0,
31 | "errmsg": "ok",
32 | })
33 | }
34 | })
35 |
36 | server := httptest.NewServer(handler)
37 | return &MockWeComServer{Server: server}
38 | }
39 |
40 | // TestSendText tests the SendText method.
41 | func TestSendText(t *testing.T) {
42 | mockServer := NewMockWeComServer()
43 | defer mockServer.Close()
44 |
45 | bot := NewWeComBot(mockServer.URL, "")
46 | err := bot.SendText("Hello, WeCom!", []string{}, []string{})
47 | if err != nil {
48 | t.Errorf("SendText failed: %v", err)
49 | }
50 | }
51 |
52 | // TestSendMarkdown tests the SendMarkdown method.
53 | func TestSendMarkdown(t *testing.T) {
54 | mockServer := NewMockWeComServer()
55 | defer mockServer.Close()
56 |
57 | bot := NewWeComBot(mockServer.URL, "")
58 | err := bot.SendMarkdown("## Hello, WeCom!\nThis is a markdown message.")
59 | if err != nil {
60 | t.Errorf("SendMarkdown failed: %v", err)
61 | }
62 | }
63 |
64 | // TestSendImage tests the SendImage method.
65 | func TestSendImage(t *testing.T) {
66 | mockServer := NewMockWeComServer()
67 | defer mockServer.Close()
68 |
69 | bot := NewWeComBot(mockServer.URL, "")
70 | err := bot.SendImage("SGVsbG8sIFdlQ29tIQ==", "d41d8cd98f00b204e9800998ecf8427e")
71 | if err != nil {
72 | t.Errorf("SendImage failed: %v", err)
73 | }
74 | }
75 |
76 | // TestSendNews tests the SendNews method.
77 | func TestSendNews(t *testing.T) {
78 | mockServer := NewMockWeComServer()
79 | defer mockServer.Close()
80 |
81 | bot := NewWeComBot(mockServer.URL, "")
82 | articles := []NewsArticle{
83 | {
84 | Title: "News Title",
85 | Description: "News Description",
86 | URL: "https://example.com",
87 | PicURL: "https://example.com/image.jpg",
88 | },
89 | }
90 | err := bot.SendNews(articles)
91 | if err != nil {
92 | t.Errorf("SendNews failed: %v", err)
93 | }
94 | }
95 |
96 | // TestSendTemplateCard tests the SendTemplateCard method.
97 | func TestSendTemplateCard(t *testing.T) {
98 | mockServer := NewMockWeComServer()
99 | defer mockServer.Close()
100 |
101 | bot := NewWeComBot(mockServer.URL, "")
102 | err := bot.SendTemplateCard("text_notice", "Main Title", "Main Description", 1, "https://example.com", "", "")
103 | if err != nil {
104 | t.Errorf("SendTemplateCard failed: %v", err)
105 | }
106 | }
107 |
108 | // TestUploadFile tests the UploadFile method.
109 | func TestUploadFile(t *testing.T) {
110 | mockServer := NewMockWeComServer()
111 | defer mockServer.Close()
112 |
113 | mockServer.URL = WECOM_BOT_UPLOAD_URL
114 | bot := NewWeComBot(mockServer.URL, testkey)
115 |
116 | // Create a temporary file for testing
117 | tempFile, err := os.CreateTemp("", "testfile")
118 | if err != nil {
119 | t.Fatalf("Failed to create temporary file: %v", err)
120 | }
121 | defer os.Remove(tempFile.Name())
122 |
123 | // Write some content to the temporary file
124 | _, err = tempFile.WriteString("Hello, WeCom!")
125 | if err != nil {
126 | t.Fatalf("Failed to write to temporary file: %v", err)
127 | }
128 | tempFile.Close()
129 |
130 | mediaID, err := bot.UploadFile(tempFile.Name())
131 | if err != nil {
132 | t.Errorf("UploadFile failed: %v", err)
133 | } else {
134 | t.Logf("UploadFile succeeded, media ID: %s", mediaID)
135 | }
136 | }
137 |
```
--------------------------------------------------------------------------------
/wecom.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "mime/multipart"
9 | "net/http"
10 | "os"
11 | )
12 |
13 | const (
14 | WECOM_BOT_BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook"
15 | WECOM_BOT_SEND_URL = WECOM_BOT_BASE_URL + "/send?key="
16 | WECOM_BOT_UPLOAD_URL = WECOM_BOT_BASE_URL + "/upload_media?key="
17 | )
18 |
19 | type WeComBot struct {
20 | WebhookURL string
21 | WebhookKey string
22 | }
23 |
24 | func NewWeComBot(webhookURL, webhookKey string) *WeComBot {
25 | return &WeComBot{
26 | WebhookURL: webhookURL,
27 | WebhookKey: webhookKey,
28 | }
29 | }
30 |
31 | // SendText sends a text message to the WeCom group.
32 | func (bot *WeComBot) SendText(content string, mentionedList []string, mentionedMobileList []string) error {
33 | payload := map[string]interface{}{
34 | "msgtype": "text",
35 | "text": map[string]interface{}{
36 | "content": content,
37 | "mentioned_list": mentionedList,
38 | "mentioned_mobile_list": mentionedMobileList,
39 | },
40 | }
41 |
42 | return bot.sendRequest(payload)
43 | }
44 |
45 | // SendMarkdown sends a markdown message to the WeCom group.
46 | func (bot *WeComBot) SendMarkdown(content string) error {
47 | payload := map[string]interface{}{
48 | "msgtype": "markdown",
49 | "markdown": map[string]interface{}{
50 | "content": content,
51 | },
52 | }
53 |
54 | return bot.sendRequest(payload)
55 | }
56 |
57 | // SendImage sends an image message to the WeCom group.
58 | func (bot *WeComBot) SendImage(base64Data string, md5 string) error {
59 | payload := map[string]interface{}{
60 | "msgtype": "image",
61 | "image": map[string]interface{}{
62 | "base64": base64Data,
63 | "md5": md5,
64 | },
65 | }
66 |
67 | return bot.sendRequest(payload)
68 | }
69 |
70 | // SendNews sends a news message to the WeCom group.
71 | func (bot *WeComBot) SendNews(articles []NewsArticle) error {
72 | payload := map[string]interface{}{
73 | "msgtype": "news",
74 | "news": map[string]interface{}{
75 | "articles": articles,
76 | },
77 | }
78 |
79 | return bot.sendRequest(payload)
80 | }
81 |
82 | // NewsArticle represents a news article in a news message.
83 | type NewsArticle struct {
84 | Title string `json:"title"`
85 | Description string `json:"description"`
86 | URL string `json:"url"`
87 | PicURL string `json:"picurl"`
88 | }
89 |
90 | // SendTemplateCard sends a template card message to the WeCom group.
91 | func (bot *WeComBot) SendTemplateCard(cardType string, mainTitle string, mainDesc string, cardActionType int, cardActionURL string, cardActionAppID string, cardActionPagePath string) error {
92 | payload := map[string]interface{}{
93 | "msgtype": "template_card",
94 | "template_card": map[string]interface{}{
95 | "card_type": cardType,
96 | "main_title": map[string]interface{}{
97 | "title": mainTitle,
98 | "desc": mainDesc,
99 | },
100 | "card_action": map[string]interface{}{
101 | "type": cardActionType,
102 | "url": cardActionURL,
103 | "appid": cardActionAppID,
104 | "pagepath": cardActionPagePath,
105 | },
106 | },
107 | }
108 |
109 | return bot.sendRequest(payload)
110 | }
111 |
112 | // UploadFile uploads a file to WeCom and returns the media ID.
113 | func (bot *WeComBot) UploadFile(filePath string) (string, error) {
114 | file, err := os.Open(filePath)
115 | if err != nil {
116 | return "", err
117 | }
118 | defer file.Close()
119 |
120 | body := &bytes.Buffer{}
121 | writer := multipart.NewWriter(body)
122 |
123 | part, err := writer.CreateFormFile("media", filePath)
124 | if err != nil {
125 | return "", err
126 | }
127 |
128 | _, err = io.Copy(part, file)
129 | if err != nil {
130 | return "", err
131 | }
132 |
133 | err = writer.Close()
134 | if err != nil {
135 | return "", err
136 | }
137 |
138 | resp, err := http.Post(fmt.Sprintf("%s%s&type=file", bot.WebhookURL, bot.WebhookKey), writer.FormDataContentType(), body)
139 | if err != nil {
140 | return "", err
141 | }
142 | defer resp.Body.Close()
143 |
144 | var result map[string]interface{}
145 | err = json.NewDecoder(resp.Body).Decode(&result)
146 | if err != nil {
147 | return "", err
148 | }
149 |
150 | if result["errcode"].(float64) != 0 {
151 | return "", fmt.Errorf("WeCom API error: %s", result["errmsg"].(string))
152 | }
153 |
154 | return result["media_id"].(string), nil
155 | }
156 |
157 | // sendRequest sends a request to the WeCom API with the given payload.
158 | func (bot *WeComBot) sendRequest(payload map[string]interface{}) error {
159 | jsonPayload, err := json.Marshal(payload)
160 | if err != nil {
161 | return err
162 | }
163 |
164 | url := bot.WebhookURL + bot.WebhookKey
165 | resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonPayload))
166 | if err != nil {
167 | return err
168 | }
169 | defer resp.Body.Close()
170 |
171 | var result map[string]interface{}
172 | err = json.NewDecoder(resp.Body).Decode(&result)
173 | if err != nil {
174 | return err
175 | }
176 |
177 | if result["errcode"].(float64) != 0 {
178 | return fmt.Errorf("WeCom API error: %s", result["errmsg"].(string))
179 | }
180 |
181 | return nil
182 | }
183 |
```
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "strings"
9 |
10 | "github.com/mark3labs/mcp-go/mcp"
11 | "github.com/mark3labs/mcp-go/server"
12 | )
13 |
14 | func main() {
15 | webhookKey := os.Getenv("WECOM_BOT_WEBHOOK_KEY")
16 | if webhookKey == "" {
17 | log.Println("WECOM_BOT_WEBHOOK_KEY environment variable is required")
18 | return
19 | }
20 |
21 | bot := NewWeComBot(WECOM_BOT_SEND_URL, webhookKey)
22 |
23 | s := server.NewMCPServer(
24 | "mcp-wecombot-server",
25 | "1.0.0",
26 | server.WithResourceCapabilities(true, true),
27 | server.WithLogging(),
28 | )
29 |
30 | sendTextTool := mcp.NewTool("send_text",
31 | mcp.WithDescription("Send a text message to WeCom group"),
32 | mcp.WithString("content",
33 | mcp.Required(),
34 | mcp.Description("Text content to send"),
35 | ),
36 | mcp.WithString("mentioned_list",
37 | mcp.Description("List of user IDs to mention,Multiple people use commas to separate, such as @xiaoyang, @wike."),
38 | ),
39 | mcp.WithString("mentioned_mobile_list",
40 | mcp.Description("List of mobile numbers to mention,Multiple people use commas to separate, such as @xiaoyang, @wike."),
41 | ),
42 | )
43 | s.AddTool(sendTextTool, sendTextHandler(bot))
44 |
45 | sendMarkdownTool := mcp.NewTool("send_markdown",
46 | mcp.WithDescription("Send a markdown message to WeCom group"),
47 | mcp.WithString("content",
48 | mcp.Required(),
49 | mcp.Description("Markdown content to send"),
50 | ),
51 | )
52 | s.AddTool(sendMarkdownTool, sendMarkdownHandler(bot))
53 |
54 | sendImageTool := mcp.NewTool("send_image",
55 | mcp.WithDescription("Send an image message to WeCom group"),
56 | mcp.WithString("base64_data",
57 | mcp.Required(),
58 | mcp.Description("Base64 encoded image data"),
59 | ),
60 | mcp.WithString("md5",
61 | mcp.Required(),
62 | mcp.Description("MD5 hash of the image"),
63 | ),
64 | )
65 | s.AddTool(sendImageTool, sendImageHandler(bot))
66 |
67 | sendNewsTool := mcp.NewTool("send_news",
68 | mcp.WithDescription("Send a news message to WeCom group"),
69 | mcp.WithString("title", mcp.Required(), mcp.Description("Title of the news article")),
70 | mcp.WithString("description", mcp.Description("Description of the news article")),
71 | mcp.WithString("url", mcp.Required(), mcp.Description("URL of the news article")),
72 | mcp.WithString("picurl", mcp.Description("Picture URL of the news article")),
73 | )
74 | s.AddTool(sendNewsTool, sendNewsHandler(bot))
75 |
76 | sendTemplateCardTool := mcp.NewTool("send_template_card",
77 | mcp.WithDescription("Send a template card message to WeCom group"),
78 | mcp.WithString("card_type",
79 | mcp.Required(),
80 | mcp.Description("Type of the template card"),
81 | ),
82 | mcp.WithString("main_title",
83 | mcp.Required(),
84 | mcp.Description("Main title of the template card"),
85 | ),
86 | mcp.WithString("main_desc",
87 | mcp.Description("Main description of the template card"),
88 | ),
89 | mcp.WithNumber("card_action_type",
90 | mcp.Required(),
91 | mcp.Description("Type of the card action"),
92 | ),
93 | mcp.WithString("card_action_url",
94 | mcp.Description("URL for the card action"),
95 | ),
96 | mcp.WithString("card_action_appid",
97 | mcp.Description("App ID for the card action"),
98 | ),
99 | mcp.WithString("card_action_pagepath",
100 | mcp.Description("Page path for the card action"),
101 | ),
102 | )
103 | s.AddTool(sendTemplateCardTool, sendTemplateCardHandler(bot))
104 |
105 | uploadFileTool := mcp.NewTool("upload_file",
106 | mcp.WithDescription("Upload a file to WeCom"),
107 | mcp.WithString("file_path",
108 | mcp.Required(),
109 | mcp.Description("Path to the file to upload"),
110 | ),
111 | )
112 | s.AddTool(uploadFileTool, uploadFileHandler(bot))
113 |
114 | if err := server.ServeStdio(s); err != nil {
115 | log.Printf("Server error: %v\n", err)
116 | }
117 | }
118 | func sendTextHandler(bot *WeComBot) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
119 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
120 | var mentionedListStr string
121 | var mentionedMobileListStr string
122 | var mentionedList []string
123 | var mentionedMobileList []string
124 |
125 | content := request.Params.Arguments["content"].(string)
126 |
127 | if request.Params.Arguments["mentioned_list"] == nil && request.Params.Arguments["mentioned_mobile_list"] == nil {
128 | mentionedListStr = ""
129 | mentionedMobileListStr = ""
130 | } else {
131 | mentionedListStr = request.Params.Arguments["mentioned_list"].(string)
132 | mentionedMobileListStr = request.Params.Arguments["mentioned_mobile_list"].(string)
133 | }
134 |
135 | if mentionedListStr != "" && mentionedMobileListStr != "" {
136 | mentionedList = strings.Split(mentionedListStr, ",")
137 | mentionedMobileList = strings.Split(mentionedMobileListStr, ",")
138 | } else {
139 | mentionedList = []string{}
140 | mentionedMobileList = []string{}
141 | }
142 |
143 | err := bot.SendText(content, mentionedList, mentionedMobileList)
144 | if err != nil {
145 | return mcp.NewToolResultError(fmt.Sprintf("Failed to send text message: %v", err)), nil
146 | }
147 |
148 | return mcp.NewToolResultText("Text message sent successfully"), nil
149 | }
150 | }
151 |
152 | func sendMarkdownHandler(bot *WeComBot) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
153 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
154 | content := request.Params.Arguments["content"].(string)
155 |
156 | bot.WebhookURL = WECOM_BOT_SEND_URL
157 | err := bot.SendMarkdown(content)
158 | if err != nil {
159 | return mcp.NewToolResultError(fmt.Sprintf("Failed to send markdown message: %v", err)), nil
160 | }
161 |
162 | return mcp.NewToolResultText("Markdown message sent successfully"), nil
163 | }
164 | }
165 |
166 | func sendImageHandler(bot *WeComBot) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
167 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
168 | base64Data := request.Params.Arguments["base64_data"].(string)
169 | md5 := request.Params.Arguments["md5"].(string)
170 |
171 | bot.WebhookURL = WECOM_BOT_SEND_URL
172 | err := bot.SendImage(base64Data, md5)
173 | if err != nil {
174 | return mcp.NewToolResultError(fmt.Sprintf("Failed to send image message: %v", err)), nil
175 | }
176 |
177 | return mcp.NewToolResultText("Image message sent successfully"), nil
178 | }
179 | }
180 |
181 | func sendNewsHandler(bot *WeComBot) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
182 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
183 | // Extract the parameters from the request
184 | title := request.Params.Arguments["title"].(string)
185 | description := request.Params.Arguments["description"].(string)
186 | url := request.Params.Arguments["url"].(string)
187 | picurl := request.Params.Arguments["picurl"].(string)
188 |
189 | // Create a single NewsArticle
190 | newsArticle := NewsArticle{
191 | Title: title,
192 | Description: description,
193 | URL: url,
194 | PicURL: picurl,
195 | }
196 |
197 | // Send the news article
198 | bot.WebhookURL = WECOM_BOT_SEND_URL
199 | err := bot.SendNews([]NewsArticle{newsArticle})
200 | if err != nil {
201 | return mcp.NewToolResultError(fmt.Sprintf("Failed to send news message: %v", err)), nil
202 | }
203 |
204 | // Return success result
205 | return mcp.NewToolResultText("News message sent successfully"), nil
206 | }
207 | }
208 |
209 | func sendTemplateCardHandler(bot *WeComBot) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
210 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
211 | cardType := request.Params.Arguments["card_type"].(string)
212 | mainTitle := request.Params.Arguments["main_title"].(string)
213 | mainDesc := request.Params.Arguments["main_desc"].(string)
214 | cardActionType := request.Params.Arguments["card_action_type"].(int)
215 | cardActionURL := request.Params.Arguments["card_action_url"].(string)
216 | cardActionAppID := request.Params.Arguments["card_action_appid"].(string)
217 | cardActionPagePath := request.Params.Arguments["card_action_pagepath"].(string)
218 |
219 | bot.WebhookURL = WECOM_BOT_SEND_URL
220 | err := bot.SendTemplateCard(cardType, mainTitle, mainDesc, cardActionType, cardActionURL, cardActionAppID, cardActionPagePath)
221 | if err != nil {
222 | return mcp.NewToolResultError(fmt.Sprintf("Failed to send template card message: %v", err)), nil
223 | }
224 |
225 | return mcp.NewToolResultText("Template card message sent successfully"), nil
226 | }
227 | }
228 |
229 | func uploadFileHandler(bot *WeComBot) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
230 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
231 | filePath := request.Params.Arguments["file_path"].(string)
232 |
233 | bot.WebhookURL = WECOM_BOT_UPLOAD_URL
234 | mediaID, err := bot.UploadFile(filePath)
235 | if err != nil {
236 | return mcp.NewToolResultError(fmt.Sprintf("Failed to upload file: %v", err)), nil
237 | }
238 |
239 | return mcp.NewToolResultText(fmt.Sprintf("File uploaded successfully, media ID: %s", mediaID)), nil
240 | }
241 | }
242 |
```