openclaw-ringcentral 2026.1.29-beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/index.ts +18 -0
- package/openclaw.plugin.json +200 -0
- package/package.json +72 -0
- package/src/accounts.test.ts +311 -0
- package/src/accounts.ts +167 -0
- package/src/api.ts +241 -0
- package/src/auth.ts +92 -0
- package/src/channel.ts +545 -0
- package/src/config-schema.ts +78 -0
- package/src/markdown.test.ts +168 -0
- package/src/markdown.ts +158 -0
- package/src/monitor.test.ts +47 -0
- package/src/monitor.ts +742 -0
- package/src/openclaw.d.ts +68 -0
- package/src/runtime.ts +14 -0
- package/src/targets.test.ts +118 -0
- package/src/targets.ts +70 -0
- package/src/types.ts +174 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { toRingCentralMarkdown, needsMarkdownConversion, hasCodeBlocks, markdownToAdaptiveCard } from "./markdown.js";
|
|
3
|
+
|
|
4
|
+
describe("toRingCentralMarkdown", () => {
|
|
5
|
+
it("converts single underscore italic to asterisk", () => {
|
|
6
|
+
expect(toRingCentralMarkdown("_italic_")).toBe("*italic*");
|
|
7
|
+
expect(toRingCentralMarkdown("This is _italic_ text")).toBe("This is *italic* text");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("preserves double asterisk bold", () => {
|
|
11
|
+
expect(toRingCentralMarkdown("**bold**")).toBe("**bold**");
|
|
12
|
+
expect(toRingCentralMarkdown("This is **bold** text")).toBe("This is **bold** text");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("converts double underscore to bold", () => {
|
|
16
|
+
expect(toRingCentralMarkdown("__bold__")).toBe("**bold**");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("removes strikethrough", () => {
|
|
20
|
+
expect(toRingCentralMarkdown("~~strikethrough~~")).toBe("strikethrough");
|
|
21
|
+
expect(toRingCentralMarkdown("This is ~~deleted~~ text")).toBe("This is deleted text");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("removes code blocks", () => {
|
|
25
|
+
expect(toRingCentralMarkdown("```\ncode\n```")).toBe("code");
|
|
26
|
+
expect(toRingCentralMarkdown("```javascript\nconst x = 1;\n```")).toBe("const x = 1;");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("removes inline code", () => {
|
|
30
|
+
expect(toRingCentralMarkdown("`code`")).toBe("code");
|
|
31
|
+
expect(toRingCentralMarkdown("Use `npm install` to install")).toBe("Use npm install to install");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("converts headings to bold", () => {
|
|
35
|
+
expect(toRingCentralMarkdown("# Heading 1")).toBe("**Heading 1**");
|
|
36
|
+
expect(toRingCentralMarkdown("## Heading 2")).toBe("**Heading 2**");
|
|
37
|
+
expect(toRingCentralMarkdown("### Heading 3")).toBe("**Heading 3**");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("normalizes bullet lists", () => {
|
|
41
|
+
expect(toRingCentralMarkdown("- item")).toBe("* item");
|
|
42
|
+
expect(toRingCentralMarkdown("+ item")).toBe("* item");
|
|
43
|
+
expect(toRingCentralMarkdown("* item")).toBe("* item");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("preserves links", () => {
|
|
47
|
+
expect(toRingCentralMarkdown("[link](https://example.com)")).toBe("[link](https://example.com)");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("preserves blockquotes", () => {
|
|
51
|
+
expect(toRingCentralMarkdown("> quote")).toBe("> quote");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("preserves numbered lists", () => {
|
|
55
|
+
expect(toRingCentralMarkdown("1. first\n2. second")).toBe("1. first\n2. second");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("handles complex markdown", () => {
|
|
59
|
+
const input = `# Title
|
|
60
|
+
|
|
61
|
+
This is _italic_ and **bold** text.
|
|
62
|
+
|
|
63
|
+
- Item 1
|
|
64
|
+
- Item 2
|
|
65
|
+
|
|
66
|
+
\`\`\`
|
|
67
|
+
code block
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
Use \`inline code\` here.`;
|
|
71
|
+
|
|
72
|
+
const expected = `**Title**
|
|
73
|
+
|
|
74
|
+
This is *italic* and **bold** text.
|
|
75
|
+
|
|
76
|
+
* Item 1
|
|
77
|
+
* Item 2
|
|
78
|
+
|
|
79
|
+
code block
|
|
80
|
+
|
|
81
|
+
Use inline code here.`;
|
|
82
|
+
|
|
83
|
+
expect(toRingCentralMarkdown(input)).toBe(expected);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("does not convert underscore in URLs or paths", () => {
|
|
87
|
+
// URLs with underscores should be preserved in links
|
|
88
|
+
expect(toRingCentralMarkdown("[link](https://example.com/path_with_underscore)"))
|
|
89
|
+
.toBe("[link](https://example.com/path_with_underscore)");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("needsMarkdownConversion", () => {
|
|
94
|
+
it("returns true for text with underscore italic", () => {
|
|
95
|
+
expect(needsMarkdownConversion("_italic_")).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns true for text with strikethrough", () => {
|
|
99
|
+
expect(needsMarkdownConversion("~~strike~~")).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("returns true for text with code blocks", () => {
|
|
103
|
+
expect(needsMarkdownConversion("```code```")).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("returns true for text with inline code", () => {
|
|
107
|
+
expect(needsMarkdownConversion("`code`")).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("returns true for text with headings", () => {
|
|
111
|
+
expect(needsMarkdownConversion("# Heading")).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns false for plain text", () => {
|
|
115
|
+
expect(needsMarkdownConversion("plain text")).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("returns false for already compatible markdown", () => {
|
|
119
|
+
expect(needsMarkdownConversion("*italic* and **bold**")).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("hasCodeBlocks", () => {
|
|
124
|
+
it("returns true for text with code blocks", () => {
|
|
125
|
+
expect(hasCodeBlocks("```\ncode\n```")).toBe(true);
|
|
126
|
+
expect(hasCodeBlocks("```js\nconst x = 1;\n```")).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns false for text without code blocks", () => {
|
|
130
|
+
expect(hasCodeBlocks("plain text")).toBe(false);
|
|
131
|
+
expect(hasCodeBlocks("`inline code`")).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("markdownToAdaptiveCard", () => {
|
|
136
|
+
it("converts simple text to adaptive card", () => {
|
|
137
|
+
const result = markdownToAdaptiveCard("Hello world");
|
|
138
|
+
expect(result.type).toBe("AdaptiveCard");
|
|
139
|
+
expect(result.version).toBe("1.3");
|
|
140
|
+
expect(result.body).toHaveLength(1);
|
|
141
|
+
expect(result.body[0].type).toBe("TextBlock");
|
|
142
|
+
expect(result.body[0].text).toBe("Hello world");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("converts code blocks with monospace font", () => {
|
|
146
|
+
const result = markdownToAdaptiveCard("```js\nconst x = 1;\n```");
|
|
147
|
+
expect(result.body).toHaveLength(1);
|
|
148
|
+
expect(result.body[0].fontType).toBe("Monospace");
|
|
149
|
+
expect(result.body[0].text).toBe("const x = 1;");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("handles mixed text and code blocks", () => {
|
|
153
|
+
const result = markdownToAdaptiveCard("Some text\n\n```\ncode\n```\n\nMore text");
|
|
154
|
+
expect(result.body).toHaveLength(3);
|
|
155
|
+
expect(result.body[0].text).toBe("Some text");
|
|
156
|
+
expect(result.body[1].fontType).toBe("Monospace");
|
|
157
|
+
expect(result.body[1].text).toBe("code");
|
|
158
|
+
expect(result.body[2].text).toBe("More text");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("converts headings with bold styling", () => {
|
|
162
|
+
const result = markdownToAdaptiveCard("# Title");
|
|
163
|
+
expect(result.body).toHaveLength(1);
|
|
164
|
+
expect(result.body[0].weight).toBe("Bolder");
|
|
165
|
+
expect(result.body[0].size).toBe("Medium");
|
|
166
|
+
expect(result.body[0].text).toBe("Title");
|
|
167
|
+
});
|
|
168
|
+
});
|
package/src/markdown.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert standard Markdown to RingCentral Mini-Markdown format.
|
|
3
|
+
*
|
|
4
|
+
* RingCentral Mini-Markdown differences:
|
|
5
|
+
* - `_text_` = underline (not italic)
|
|
6
|
+
* - `*text*` = italic
|
|
7
|
+
* - `**text**` = bold
|
|
8
|
+
* - `[text](url)` = link
|
|
9
|
+
* - `> quote` = blockquote
|
|
10
|
+
* - `* item` = bullet list
|
|
11
|
+
*
|
|
12
|
+
* Not supported: strikethrough (~~), code blocks (```), headings (#), etc.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert standard markdown to RingCentral mini-markdown
|
|
17
|
+
*/
|
|
18
|
+
export function toRingCentralMarkdown(text: string): string {
|
|
19
|
+
let result = text;
|
|
20
|
+
|
|
21
|
+
// 1. Preserve links - temporarily replace them to avoid modifying URLs
|
|
22
|
+
const linkPlaceholders: string[] = [];
|
|
23
|
+
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match) => {
|
|
24
|
+
const idx = linkPlaceholders.length;
|
|
25
|
+
linkPlaceholders.push(match);
|
|
26
|
+
return `\x00LINK_${idx}\x00`;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 2. Convert __text__ (some markdown bold) to **text**
|
|
30
|
+
result = result.replace(/__([^_]+)__/g, "**$1**");
|
|
31
|
+
|
|
32
|
+
// 3. Convert single _text_ to *text* for italic
|
|
33
|
+
// Match _text_ but not inside words (e.g., snake_case_name)
|
|
34
|
+
result = result.replace(/(?<=^|[\s\p{P}])_([^_\n]+)_(?=$|[\s\p{P}])/gu, "*$1*");
|
|
35
|
+
|
|
36
|
+
// 4. Remove strikethrough ~~text~~ (not supported)
|
|
37
|
+
result = result.replace(/~~([^~]+)~~/g, "$1");
|
|
38
|
+
|
|
39
|
+
// 5. Convert code blocks to plain text (not well supported)
|
|
40
|
+
// Remove triple backtick code blocks
|
|
41
|
+
result = result.replace(/```[\s\S]*?```/g, (match) => {
|
|
42
|
+
// Extract content without the backticks and language identifier
|
|
43
|
+
const content = match.replace(/```\w*\n?/, "").replace(/\n?```$/, "");
|
|
44
|
+
return content;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Convert inline code `text` to plain text
|
|
48
|
+
result = result.replace(/`([^`]+)`/g, "$1");
|
|
49
|
+
|
|
50
|
+
// 6. Convert headings to bold (# Heading -> **Heading**)
|
|
51
|
+
result = result.replace(/^#{1,6}\s+(.+)$/gm, "**$1**");
|
|
52
|
+
|
|
53
|
+
// 7. Convert horizontal rules (---, ***, ___) to simple separator
|
|
54
|
+
result = result.replace(/^[-*_]{3,}$/gm, "---");
|
|
55
|
+
|
|
56
|
+
// 8. Normalize bullet lists (-, +, *) to * for consistency
|
|
57
|
+
result = result.replace(/^(\s*)[-+]\s+/gm, "$1* ");
|
|
58
|
+
|
|
59
|
+
// 9. Restore links
|
|
60
|
+
result = result.replace(/\x00LINK_(\d+)\x00/g, (_match, idx) => {
|
|
61
|
+
return linkPlaceholders[Number(idx)];
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// 10. Keep numbered lists as-is (1. item)
|
|
65
|
+
// 11. Keep blockquotes as-is (> quote)
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if text contains markdown that needs conversion
|
|
72
|
+
*/
|
|
73
|
+
export function needsMarkdownConversion(text: string): boolean {
|
|
74
|
+
// Check for patterns that need conversion
|
|
75
|
+
return (
|
|
76
|
+
// Single underscore italic
|
|
77
|
+
/(?<![\\*_])_[^_\n]+_(?![_])/.test(text) ||
|
|
78
|
+
// Strikethrough
|
|
79
|
+
/~~[^~]+~~/.test(text) ||
|
|
80
|
+
// Code blocks
|
|
81
|
+
/```[\s\S]*?```/.test(text) ||
|
|
82
|
+
// Inline code
|
|
83
|
+
/`[^`]+`/.test(text) ||
|
|
84
|
+
// Headings
|
|
85
|
+
/^#{1,6}\s+.+$/m.test(text)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if text contains code blocks that would benefit from Adaptive Card formatting
|
|
91
|
+
*/
|
|
92
|
+
export function hasCodeBlocks(text: string): boolean {
|
|
93
|
+
return /```[\s\S]*?```/.test(text);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert markdown text to an Adaptive Card structure.
|
|
98
|
+
* This preserves code blocks with proper formatting.
|
|
99
|
+
*/
|
|
100
|
+
export function markdownToAdaptiveCard(text: string): {
|
|
101
|
+
type: "AdaptiveCard";
|
|
102
|
+
$schema: string;
|
|
103
|
+
version: string;
|
|
104
|
+
body: Array<{ type: string; text?: string; wrap?: boolean; fontType?: string; size?: string; weight?: string }>;
|
|
105
|
+
} {
|
|
106
|
+
const body: Array<{ type: string; text?: string; wrap?: boolean; fontType?: string; size?: string; weight?: string }> = [];
|
|
107
|
+
|
|
108
|
+
// Split by code blocks
|
|
109
|
+
const parts = text.split(/(```[\s\S]*?```)/g);
|
|
110
|
+
|
|
111
|
+
for (const part of parts) {
|
|
112
|
+
if (!part.trim()) continue;
|
|
113
|
+
|
|
114
|
+
if (part.startsWith("```")) {
|
|
115
|
+
// Extract language and code
|
|
116
|
+
const match = part.match(/```(\w*)\n?([\s\S]*?)\n?```/);
|
|
117
|
+
if (match) {
|
|
118
|
+
const [, , code] = match;
|
|
119
|
+
// Add code block with monospace font
|
|
120
|
+
body.push({
|
|
121
|
+
type: "TextBlock",
|
|
122
|
+
text: code.trim(),
|
|
123
|
+
wrap: true,
|
|
124
|
+
fontType: "Monospace",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// Regular text - convert markdown
|
|
129
|
+
const converted = toRingCentralMarkdown(part.trim());
|
|
130
|
+
if (converted) {
|
|
131
|
+
// Check if it's a heading (starts with **)
|
|
132
|
+
const headingMatch = converted.match(/^\*\*(.+)\*\*$/);
|
|
133
|
+
if (headingMatch && !converted.includes("\n")) {
|
|
134
|
+
body.push({
|
|
135
|
+
type: "TextBlock",
|
|
136
|
+
text: headingMatch[1],
|
|
137
|
+
wrap: true,
|
|
138
|
+
size: "Medium",
|
|
139
|
+
weight: "Bolder",
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
body.push({
|
|
143
|
+
type: "TextBlock",
|
|
144
|
+
text: converted,
|
|
145
|
+
wrap: true,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
type: "AdaptiveCard",
|
|
154
|
+
$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
155
|
+
version: "1.3",
|
|
156
|
+
body,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isSenderAllowed } from "./monitor.js";
|
|
3
|
+
|
|
4
|
+
describe("isSenderAllowed", () => {
|
|
5
|
+
it("returns true when allowFrom contains wildcard", () => {
|
|
6
|
+
expect(isSenderAllowed("12345", ["*"])).toBe(true);
|
|
7
|
+
expect(isSenderAllowed("any-user", ["*"])).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("returns true when sender ID matches exactly", () => {
|
|
11
|
+
expect(isSenderAllowed("12345", ["12345"])).toBe(true);
|
|
12
|
+
expect(isSenderAllowed("12345", ["other", "12345"])).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns true when sender ID matches with ringcentral: prefix", () => {
|
|
16
|
+
expect(isSenderAllowed("12345", ["ringcentral:12345"])).toBe(true);
|
|
17
|
+
expect(isSenderAllowed("12345", ["RINGCENTRAL:12345"])).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns true when sender ID matches with rc: prefix", () => {
|
|
21
|
+
expect(isSenderAllowed("12345", ["rc:12345"])).toBe(true);
|
|
22
|
+
expect(isSenderAllowed("12345", ["RC:12345"])).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns true when sender ID matches with user: prefix", () => {
|
|
26
|
+
expect(isSenderAllowed("12345", ["user:12345"])).toBe(true);
|
|
27
|
+
expect(isSenderAllowed("12345", ["USER:12345"])).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("returns false when sender ID not in allowFrom", () => {
|
|
31
|
+
expect(isSenderAllowed("12345", ["67890"])).toBe(false);
|
|
32
|
+
expect(isSenderAllowed("12345", [])).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("handles case-insensitive matching", () => {
|
|
36
|
+
expect(isSenderAllowed("ABC123", ["abc123"])).toBe(true);
|
|
37
|
+
expect(isSenderAllowed("abc123", ["ABC123"])).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles whitespace in allowFrom entries", () => {
|
|
41
|
+
expect(isSenderAllowed("12345", [" 12345 "])).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("ignores empty entries in allowFrom", () => {
|
|
45
|
+
expect(isSenderAllowed("12345", ["", "12345", " "])).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
});
|