(this.getFieldValue('name'),this.id)
43 | this.workspace.typings.add(this.type_value)
44 | this.onchange = ()=>{
45 | this.type_value.set(this.getFieldValue('name'))
46 | }
47 | const dispose = this.dispose
48 | this.dispose = (h)=>{
49 | this.workspace.typings.delete(this.type_value)
50 | dispose.bind(this)(h)
51 | }
52 | }
53 | }
54 |
55 | export const TypeAnyBlock = {
56 | "type": "type_any",
57 | "message0": "任意类型",
58 | "output": "Type",
59 | "colour": "#ce4bc9",
60 | "tooltip": "",
61 | }
62 |
63 | export const TypeNeverBlock = {
64 | "type": "type_never",
65 | "message0": "永远不会出现的类型",
66 | "output": "Type",
67 | "colour": "#ce4bc9",
68 | "tooltip": ""
69 | }
70 |
71 | export const TypeStringBlock = {
72 | "type": "type_string",
73 | "message0": "字符串",
74 | "output": "Type",
75 | "colour": "#ce4bc9",
76 | "tooltip": "",
77 | }
78 |
79 | export const TypeNumberBlock = {
80 | "type": "type_number",
81 | "message0": "数字",
82 | "output": "Type",
83 | "colour": "#ce4bc9",
84 | "tooltip": "",
85 | }
86 |
87 | export const TypeBooleanBlock = {
88 | "type": "type_boolean",
89 | "message0": "布尔值",
90 | "output": "Type",
91 | "colour": "#ce4bc9",
92 | "tooltip": "",
93 | }
94 |
95 | export const TypeArrayBlock = {
96 | "type": "type_array",
97 | "message0": "由%1组成的数组",
98 | "args0": [
99 | {
100 | "type": "input_value",
101 | "name": "type",
102 | "check": "Type"
103 | }
104 | ],
105 | "output": "Type",
106 | "colour": "#ce4bc9",
107 | }
108 |
109 | export const TypeUnionRootBlock = {
110 | "type": "type_union_root",
111 | "message0": "联合类型 %1",
112 | "args0": [
113 | {
114 | "type": "input_statement",
115 | "name": "types",
116 | "check": "Type"
117 | }
118 | ]
119 | }
120 |
121 | export const TypeUnionEntityBlock = {
122 | "type": "type_union_entity",
123 | "message0": "项目",
124 | "previousStatement": "Type",
125 | "nextStatement": "Type",
126 | "colour": 160
127 | }
128 |
129 | export const TypeUnionBlock = {
130 | "type": "type_union",
131 | "output": "Type",
132 | "message0": "联合类型",
133 | "args0":[],
134 | init(){
135 | this.updateShape_()
136 | },
137 | "mutator":"union_mutator",
138 | "colour": "#ce4bc9",
139 | }
140 |
141 | export const TypeObjectRootBlock = {
142 | "type": "type_object_root",
143 | "message0": "对象 %1",
144 | "args0": [
145 | {
146 | "type": "input_statement",
147 | "name": "properties",
148 | "check": "Type"
149 | },
150 | ],
151 | "colour": "#ce4bc9",
152 | "tooltip": "",
153 | "helpUrl": ""
154 | }
155 |
156 | export const TypeObjectEntityBlock = {
157 | "type": "type_object_entity",
158 | "message0": "属性 %1",
159 | "args0": [
160 | {
161 | "type": "field_input",
162 | "name": "name",
163 | "text": "属性名称"
164 | }
165 | ],
166 | "previousStatement": "Type",
167 | "nextStatement": "Type",
168 | "colour": 160
169 | }
170 |
171 | export const TypeObjectBlock = {
172 | "type": "type_object",
173 | "message0": "对象",
174 | "output": "Type",
175 | "colour": "#ce4bc9",
176 | "tooltip": "",
177 | "helpUrl": "",
178 | "mutator":"object_mutator"
179 | }
180 |
181 | export const TypeGetter = {
182 | "type": "type_getter",
183 | "message0": "类型",
184 | "output": "Type",
185 | "colour": "#ce4bc9",
186 | "tooltip": "",
187 | "helpUrl": "",
188 | init(this:Block){
189 | let field
190 | if(this.workspace.typings) {
191 | field = new FieldBindingStringDropdown(this.workspace.typings)
192 | this.inputList[0].appendField(field, "type")
193 | }
194 | }
195 | }
196 |
197 | export const TypeBlocks = [
198 | TypeRootBlock,
199 | TypeAnyBlock,
200 | TypeNeverBlock,
201 | TypeStringBlock,
202 | TypeNumberBlock,
203 | TypeBooleanBlock,
204 | TypeArrayBlock,
205 | TypeUnionRootBlock,
206 | TypeUnionEntityBlock,
207 | TypeUnionBlock,
208 | TypeDefinitionBlock,
209 | TypeObjectRootBlock,
210 | TypeObjectEntityBlock,
211 | TypeObjectBlock,
212 | TypeGetter
213 | ]
214 |
--------------------------------------------------------------------------------
/client/meta.vue:
--------------------------------------------------------------------------------
1 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
插件基本信息
98 |
插件ID:{{props.current}}
99 |
插件UUID: {{blockUUID?.uuid}}
100 |
插件作者: {{meta.author??'未署名'}}修改
101 | 插件描述:
102 |
数据库
103 |
新建数据表
104 |
105 |
106 |
107 |
108 |
109 |
指令
110 |
111 |
112 |
{{command}} 删除
113 | 设置指令信息
114 |
115 |
116 | 最低权限等级
117 |
118 |
119 |
120 |
存储空间
121 | 暂未支持
122 |
其他信息
123 | SHA256校验值:
124 |
125 |
126 |
127 |
128 |
129 |
130 |
146 |
--------------------------------------------------------------------------------
/client/blockly/blocks/event.ts:
--------------------------------------------------------------------------------
1 | import {javascriptGenerator} from "blockly/javascript";
2 |
3 | export const MiddlewareBlock = {
4 | "type": "middleware",
5 | "message0": "当接受到聊天消息 %1 %2",
6 | "args0": [
7 | {
8 | "type": "input_dummy"
9 | },
10 | {
11 | "type": "input_statement",
12 | "name": "callback"
13 | }
14 | ],
15 | "extensions":['session_provider'],
16 | "colour": 230,
17 | "tooltip": "",
18 | "helpUrl": ""
19 | }
20 |
21 | export function middlewareBlockGenerator(block){
22 | let statements_callback = javascriptGenerator.statementToCode(block, 'callback');
23 | return `ctx.middleware(async (session,next)=>{\n${statements_callback} return next();\n})`
24 | }
25 |
26 | export const OnMessageEvent = {
27 | "type": "on_message_event",
28 | "message0": "当消息 %1 %2 运行 %3",
29 | "args0": [
30 | {
31 | "type": "field_dropdown",
32 | "name": "event_name",
33 | "options": [
34 | [
35 | "被删除(撤回)",
36 | "message-deleted"
37 | ],
38 | [
39 | "消息被修改",
40 | "message-updated"
41 | ],
42 | [
43 | "被机器人发送",
44 | "send"
45 | ]
46 | ]
47 | },
48 | {
49 | "type": "input_dummy"
50 | },
51 | {
52 | "type": "input_statement",
53 | "name": "listener"
54 | }
55 | ],
56 | "extensions":['session_provider'],
57 | "colour": 230,
58 | "tooltip": "",
59 | "helpUrl": ""
60 | }
61 |
62 | export const OnGuildMemberEvent = {
63 | "type": "on_guild_member_event",
64 | "message0": "当群组成员 %1 %2 运行 %3",
65 | "args0": [
66 | {
67 | "type": "field_dropdown",
68 | "name": "event_name",
69 | "options": [
70 | [
71 | "加入",
72 | "guild-member-added"
73 | ],
74 | [
75 | "退出/踢出",
76 | "guild-member-deleted"
77 | ],
78 | [
79 | "申请加入",
80 | "guild-member-request"
81 | ]
82 | ]
83 | },
84 | {
85 | "type": "input_dummy"
86 | },
87 | {
88 | "type": "input_statement",
89 | "name": "listener"
90 | }
91 | ],
92 | "extensions":['session_provider'],
93 | "colour": 230,
94 | "tooltip": "",
95 | "helpUrl": ""
96 | }
97 |
98 | export const OnGuildEvent = {
99 | "type": "on_guild_event",
100 | "message0": "当你的机器人 %1 群组 %2 运行 %3",
101 | "args0": [
102 | {
103 | "type": "field_dropdown",
104 | "name": "event_name",
105 | "options": [
106 | [
107 | "加入",
108 | "guild-added"
109 | ],
110 | [
111 | "退出/被踢出",
112 | "guild-deleted"
113 | ],
114 | [
115 | "被邀请到一个",
116 | "guild-request"
117 | ]
118 | ]
119 | },
120 | {
121 | "type": "input_dummy"
122 | },
123 | {
124 | "type": "input_statement",
125 | "name": "listener"
126 | }
127 | ],
128 | "extensions":['session_provider'],
129 | "colour": 230,
130 | "tooltip": "",
131 | "helpUrl": ""
132 | }
133 |
134 | export function eventBlockGenerator(block){
135 | var dropdown_event_name = block.getFieldValue('event_name')
136 | var statements_listener = javascriptGenerator.statementToCode(block, 'listener')
137 | return `ctx.on('${dropdown_event_name}',async (session)=>{\n${statements_listener}\n})`
138 | }
139 |
140 | export const PluginApplyBlock = {
141 | "type":"plugin_apply",
142 | "message0":"当启用插件时 %1 执行 %2",
143 | "args0":[
144 | {
145 | "type":"input_dummy"
146 | },
147 | {
148 | "type":"input_statement",
149 | "name":"apply"
150 | }
151 | ],
152 | "inputsInline": false,
153 | "colour": 230,
154 | "tooltip": "",
155 | "helpUrl": ""
156 | }
157 |
158 | export function pluginApplyBlockGenerator(block){
159 | return javascriptGenerator.statementToCode(block, 'apply')
160 | }
161 |
162 | export const CommandBlock = {
163 | "type": "command",
164 | "message0": "创建一个新的指令 %1 %2 调用函数 %3",
165 | "args0": [
166 | {
167 | "type": "field_input",
168 | "name": "name",
169 | "text": "指令名称"
170 | },
171 | {
172 | "type": "input_dummy"
173 | },
174 | {
175 | "type": "input_statement",
176 | "name": "action"
177 | }
178 | ],
179 | "mutator":"parameter_list",
180 | "extensions":['session_provider','argument_provider'],
181 | "colour": 230,
182 | "tooltip": "",
183 | "helpUrl": ""
184 | };
185 | export function commandBlockGenerator(block){
186 | let text_name = block.getFieldValue('name');
187 | let parameters = block.parameters ?? []
188 | let statements_action = javascriptGenerator.statementToCode(block, 'action');
189 | console.info(block.workspace.meta)
190 | let configure = block.workspace.meta.commands?.[text_name] ?? {}
191 | let description = configure.description ? `,\`${configure.description.replace('\n','\\n')}\`` : ''
192 | if(configure['description']) delete configure['description']
193 | let configure_object = configure && Object.keys(configure).length>0 ? `,${JSON.stringify(configure)}` : ''
194 | let command_definition = text_name + ' ' + parameters.map((parameter)=>{
195 | const {required,name,type} = parameter
196 |
197 | return (required?'<':'[') + name + (type!='any_parameter'?':'+type.split('_')[0]:'') + (required?'>':']')
198 | }).join(' ')
199 | return `ctx.command('${command_definition.trim()}'${description}${configure_object}).action(async ({session},...args)=>{\n${statements_action}\n});\n`;
200 | }
201 |
202 | export const EventBlocks = [
203 | MiddlewareBlock,
204 | OnMessageEvent,
205 | OnGuildMemberEvent,
206 | OnGuildEvent,
207 | PluginApplyBlock,
208 | CommandBlock
209 | ]
210 |
211 | export const eventBlockGenerators = {
212 | 'middleware':middlewareBlockGenerator,
213 | 'command':commandBlockGenerator,
214 | 'plugin_apply':pluginApplyBlockGenerator,
215 | 'on_message_event':eventBlockGenerator,
216 | 'on_guild_member_event':eventBlockGenerator,
217 | 'on_guild_event':eventBlockGenerator
218 | }
219 |
--------------------------------------------------------------------------------
/client/blockly/msg/zh.ts:
--------------------------------------------------------------------------------
1 | import * as Blockly from 'blockly'
2 | Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_TITLE_INIT'] =
3 | '初始化全局变量';
4 | Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_NAME'] = '变量名称';
5 | Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_TO'] = '为';
6 | Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_COLLAPSED_TEXT'] = '全局变量';
7 | Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_TOOLTIP'] =
8 | '创建一个全局变量并赋值';
9 | Blockly.Msg['LANG_VARIABLES_GLOBAL_PREFIX'] = '[全局变量]';
10 | Blockly.Msg['LANG_VARIABLES_GET_TITLE_GET'] = '读取变量';
11 | Blockly.Msg['LANG_VARIABLES_GET_COLLAPSED_TEXT'] = '设置变量';
12 | Blockly.Msg['LANG_VARIABLES_GET_TOOLTIP'] =
13 | '返回变量的值';
14 | Blockly.Msg['LANG_VARIABLES_SET_TITLE_SET'] = '设置变量';
15 | Blockly.Msg['LANG_VARIABLES_SET_TITLE_TO'] = '为';
16 | Blockly.Msg['LANG_VARIABLES_SET_COLLAPSED_TEXT'] = '设置';
17 | Blockly.Msg['LANG_VARIABLES_SET_TOOLTIP'] =
18 | '设置变量';
19 | Blockly.Msg['LANG_VARIABLES_VARIABLE'] = ' 变量';
20 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_TITLE_INIT'] = '初始化局部变量';
21 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_DEFAULT_NAME'] = '局部变量1';
22 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_INPUT_TO'] = '为';
23 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_IN_DO'] = '作用域';
24 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_COLLAPSED_TEXT'] = '局部变量';
25 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_TOOLTIP'] =
26 | 'Allows you to create variables that are only accessible in the do part' +
27 | ' of this block.';
28 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_TRANSLATED_NAME'] =
29 | 'initialize local in do';
30 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_EXPRESSION_IN_RETURN'] = 'in';
31 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_EXPRESSION_COLLAPSED_TEXT'] =
32 | 'local';
33 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_EXPRESSION_TOOLTIP'] =
34 | 'Allows you to create variables that are only accessible in the return' +
35 | ' part of this block.';
36 | Blockly.Msg['LANG_VARIABLES_LOCAL_DECLARATION_EXPRESSION_TRANSLATED_NAME'] =
37 | 'initialize local in return';
38 | Blockly.Msg['LANG_VARIABLES_LOCAL_MUTATOR_CONTAINER_TITLE_LOCAL_NAMES'] =
39 | 'local names';
40 | Blockly.Msg['LANG_VARIABLES_LOCAL_MUTATOR_CONTAINER_TOOLTIP'] = '';
41 | Blockly.Msg['LANG_VARIABLES_LOCAL_MUTATOR_ARG_TITLE_NAME'] = 'name';
42 | Blockly.Msg['LANG_VARIABLES_LOCAL_MUTATOR_ARG_DEFAULT_VARIABLE'] = 'x';
43 | Blockly.Msg['LANG_PROCEDURES_DEFNORETURN_DEFINE'] = 'to';
44 | Blockly.Msg['LANG_PROCEDURES_DEFNORETURN_PROCEDURE'] = 'procedure';
45 | Blockly.Msg['LANG_PROCEDURES_DEFNORETURN_DO'] = 'do';
46 | Blockly.Msg['LANG_PROCEDURES_DEFNORETURN_COLLAPSED_PREFIX'] = 'to ';
47 | Blockly.Msg['LANG_PROCEDURES_DEFNORETURN_TOOLTIP'] =
48 | 'A procedure that does not return a value.';
49 | Blockly.Msg['LANG_PROCEDURES_DOTHENRETURN_THEN_RETURN'] = 'result';
50 | Blockly.Msg['LANG_PROCEDURES_DOTHENRETURN_DO'] = 'do';
51 | Blockly.Msg['LANG_PROCEDURES_DOTHENRETURN_RETURN'] = 'result';
52 | Blockly.Msg['LANG_PROCEDURES_DOTHENRETURN_TOOLTIP'] =
53 | 'Runs the blocks in \'do\' and returns a statement. Useful if you need' +
54 | ' to run a procedure before returning a value to a variable.';
55 | Blockly.Msg['LANG_PROCEDURES_DOTHENRETURN_COLLAPSED_TEXT'] = 'do/result';
56 | Blockly.Msg['LANG_PROCEDURES_DEFRETURN_DEFINE'] = 'to';
57 | Blockly.Msg['LANG_PROCEDURES_DEFRETURN_PROCEDURE'] = 'procedure';
58 | Blockly.Msg['LANG_PROCEDURES_DEFRETURN_RETURN'] = 'result';
59 | Blockly.Msg['LANG_PROCEDURES_DEFRETURN_COLLAPSED_PREFIX'] = 'to ';
60 | Blockly.Msg['LANG_PROCEDURES_DEFRETURN_TOOLTIP'] =
61 | 'A procedure returning a result value.';
62 | Blockly.Msg['LANG_PROCEDURES_DEF_DUPLICATE_WARNING'] =
63 | 'Warning:\nThis procedure has\nduplicate inputs.';
64 | Blockly.Msg['LANG_PROCEDURES_CALLNORETURN_CALL'] = 'call ';
65 | Blockly.Msg['LANG_PROCEDURES_CALLNORETURN_PROCEDURE'] = 'procedure';
66 | Blockly.Msg['LANG_PROCEDURES_CALLNORETURN_COLLAPSED_PREFIX'] = 'call ';
67 | Blockly.Msg['LANG_PROCEDURES_CALLNORETURN_TOOLTIP'] =
68 | 'Call a procedure with no return value.';
69 | Blockly.Msg['LANG_PROCEDURES_CALLNORETURN_TRANSLATED_NAME'] = 'call no return';
70 | Blockly.Msg['LANG_PROCEDURES_CALLRETURN_COLLAPSED_PREFIX'] = 'call ';
71 | Blockly.Msg['LANG_PROCEDURES_CALLRETURN_TOOLTIP'] =
72 | 'Call a procedure with a return value.';
73 | Blockly.Msg['LANG_PROCEDURES_CALLRETURN_TRANSLATED_NAME'] = 'call return';
74 | Blockly.Msg['LANG_PROCEDURES_MUTATORCONTAINER_TITLE'] = 'inputs';
75 | Blockly.Msg['LANG_PROCEDURES_MUTATORARG_TITLE'] = 'input:';
76 | Blockly.Msg['LANG_PROCEDURES_HIGHLIGHT_DEF'] = 'Highlight Procedure';
77 | Blockly.Msg['LANG_PROCEDURES_MUTATORCONTAINER_TOOLTIP'] = '';
78 | Blockly.Msg['LANG_PROCEDURES_MUTATORARG_TOOLTIP'] = '';
79 | Blockly.Msg['LANG_CONTROLS_FOR_INPUT_WITH'] = 'count with';
80 | Blockly.Msg['LANG_CONTROLS_FOR_INPUT_VAR'] = 'x';
81 | Blockly.Msg['LANG_CONTROLS_FOR_INPUT_FROM'] = 'from';
82 | Blockly.Msg['LANG_CONTROLS_FOR_INPUT_TO'] = 'to';
83 | Blockly.Msg['LANG_CONTROLS_FOR_INPUT_DO'] = 'do';
84 | Blockly.Msg['LANG_CONTROLS_FOR_TOOLTIP'] =
85 | 'Count from a start number to an end number.\nFor each count, set the' +
86 | ' current count number to\nvariable \'%1\', and then do some statements.';
87 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_ITEM'] = 'for each';
88 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_VAR'] = 'number';
89 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_START'] = 'from';
90 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_END'] = 'to';
91 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_STEP'] = 'by';
92 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_DO'] = 'do';
93 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_COLLAPSED_TEXT'] =
94 | 'for number in range';
95 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_COLLAPSED_PREFIX'] = 'for';
96 | Blockly.Msg['LANG_CONTROLS_FORRANGE_INPUT_COLLAPSED_SUFFIX'] = ' in range';
97 | Blockly.Msg['LANG_CONTROLS_FORRANGE_TOOLTIP'] =
98 | 'Runs the blocks in the \'do\' section for each numeric value in the' +
99 | ' range from start to end, stepping the value each time. Use the given' +
100 | ' variable name to refer to the current value.';
101 | Blockly.Msg['LANG_CONTROLS_FOREACH_INPUT_ITEM'] = 'for each';
102 | Blockly.Msg['LANG_CONTROLS_FOREACH_INPUT_VAR'] = 'item';
103 | Blockly.Msg['LANG_CONTROLS_FOREACH_INPUT_INLIST'] = 'in list';
104 | Blockly.Msg['LANG_CONTROLS_FOREACH_INPUT_DO'] = 'do';
105 | Blockly.Msg['LANG_CONTROLS_FOREACH_INPUT_COLLAPSED_TEXT'] = 'for item in list';
106 | Blockly.Msg['LANG_CONTROLS_FOREACH_INPUT_COLLAPSED_PREFIX'] = 'for ';
107 | Blockly.Msg['LANG_CONTROLS_FOREACH_INPUT_COLLAPSED_SUFFIX'] = ' in list';
108 | Blockly.Msg['LANG_CONTROLS_FOREACH_TOOLTIP'] =
109 | 'Runs the blocks in the \'do\' section for each item in the list. Use' +
110 | ' the given variable name to refer to the current list item.';
111 | Blockly.Msg['LANG_CONTROLS_FOREACH_DICT_INPUT'] =
112 | 'for each %1 with %2 in dictionary %3';
113 | Blockly.Msg['LANG_CONTROLS_FOREACH_DICT_INPUT_DO'] = 'do';
114 | Blockly.Msg['LANG_CONTROLS_FOREACH_DICT_INPUT_KEY'] = 'key';
115 | Blockly.Msg['LANG_CONTROLS_FOREACH_DICT_INPUT_VALUE'] = 'value';
116 | Blockly.Msg['LANG_CONTROLS_FOREACH_DICT_TITLE'] = 'for each in dictionary';
117 | Blockly.Msg['LANG_CONTROLS_FOREACH_DICT_TOOLTIP'] =
118 | 'Runs the blocks in the \'do\' section for each key-value entry in the' +
119 | ' dictionary. Use the given variable names to refer to the key/value of' +
120 | ' the current dictionary item.';
121 | Blockly.Msg['ERROR_SELECT_VALID_ITEM_FROM_DROPDOWN'] =
122 | 'Select a valid item in the drop down.';
123 | Blockly.Msg['ERROR_BLOCK_CANNOT_BE_IN_DEFINITION'] =
124 | 'This block cannot be in a definition';
125 | Blockly.Msg['HORIZONTAL_PARAMETERS'] = 'Arrange Parameters Horizontally';
126 | Blockly.Msg['VERTICAL_PARAMETERS'] = 'Arrange Parameters Vertically';
127 | Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_INPUT_DO'] = 'do';
128 | Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_INPUT_RETURN'] = 'result';
129 | Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_TOOLTIP'] =
130 | 'Runs the blocks in \'do\' and returns a statement. Useful if you need to' +
131 | ' run a procedure before returning a value to a variable.';
132 | Blockly.Msg['LANG_CONTROLS_DO_THEN_ RETURN_COLLAPSED_TEXT'] = 'do/result';
133 | Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_TITLE'] = 'do result';
134 |
--------------------------------------------------------------------------------
/client/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Blockly - {{store.blockly.filter((v)=>v.id?.toString()===currentId?.toString())?.[0]?.name ?? '主页'}} {{saving?'保存中...':''}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 在左侧选择或创建一个Blockly代码
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
31 |
32 |
33 |
34 | Loading...
35 |
36 |
37 |
38 |
39 |
40 |
41 |
编译
42 |
代码结果
43 |
运行日志
44 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
149 |
150 |
170 |
171 |
212 |
--------------------------------------------------------------------------------
/client/blockly/typing/index.ts:
--------------------------------------------------------------------------------
1 | import {Connection, ConnectionChecker, IConnectionChecker, INPUT_VALUE, OUTPUT_VALUE, WorkspaceSvg} from 'blockly'
2 | export {initializeType} from './overwritting'
3 |
4 | export type TsTerminalType = string | number | boolean | undefined
5 |
6 | export abstract class Type{
7 | prototype:P = undefined as any
8 |
9 | constructor(...data:P extends never?[]:[P]) {
10 | if(data.length)
11 | this.prototype = data[0]
12 | }
13 |
14 | getPrototype(): P {
15 | return this.prototype;
16 | }
17 |
18 | abstract getTypeName():string
19 | abstract canWriteTo(target:Type):boolean
20 | abstract equalsTo(target:Type):boolean
21 | canWriteBy?(target:Type):boolean
22 | }
23 |
24 | export class ConstTerminalType extends Type{
25 |
26 | getTypeName(): string {
27 | return 'const'
28 | }
29 |
30 | canWriteTo(target: Type) {
31 | return this.equalsTo(target) || target.getTypeName() == typeof this.getPrototype() || target.canWriteBy?.(this)
32 | }
33 |
34 | equalsTo(target: Type) {
35 | return target.getTypeName() == this.getTypeName() && target.getPrototype() == this.getPrototype()
36 | }
37 | }
38 |
39 | export abstract class TerminalType extends Type{
40 |
41 | canWriteTo(target: Type) : boolean {
42 | return this.equalsTo(target) || target.canWriteBy?.(this)
43 | }
44 |
45 | abstract getTypeName(): string
46 |
47 | equalsTo(target: Type) {
48 | return target.getTypeName() == this.getTypeName()
49 | }
50 |
51 | }
52 |
53 | export class StringType extends TerminalType{
54 | getTypeName(): string {
55 | return "string";
56 | }
57 | }
58 |
59 | export class NumberType extends TerminalType{
60 | getTypeName(): string {
61 | return "number";
62 | }
63 | }
64 |
65 | export class BooleanType extends TerminalType{
66 | getTypeName(): string {
67 | return "boolean";
68 | }
69 | }
70 |
71 | export class UndefinedType extends TerminalType{
72 | getTypeName(): string {
73 | return "undefined";
74 | }
75 | }
76 |
77 | export abstract class ComplexType extends Type
{
78 |
79 | }
80 |
81 | export class TupleType extends ComplexType{
82 |
83 | getTypeName(): string {
84 | return "tuple";
85 | }
86 |
87 | canWriteTo(target: Type) : boolean {
88 | if(this.equalsTo(target))
89 | return true
90 | if(target.canWriteBy?.(this))
91 | return true
92 | if(!['tuple','array'].includes(target.getTypeName()))
93 | return false
94 | if(target.getTypeName() == 'array'){
95 | const target_prototype = (target as ArrayType).getPrototype()
96 | for(let i=0;i).getPrototype()
103 | if(target_prototype.length!=this.prototype.length)
104 | return false
105 | for(let i=0;i).getPrototype()
116 | if(target_prototype.length!=this.prototype.length)
117 | return false
118 | for(let i=0;i extends ComplexType{
128 | getTypeName(): string {
129 | return "array";
130 | }
131 |
132 | canWriteTo(target: Type) {
133 | return this.equalsTo(target) || target.canWriteBy?.(this)
134 | }
135 |
136 | equalsTo(target: Type): boolean {
137 | return target.getTypeName() == this.getTypeName() && this.getPrototype().equalsTo(target.getPrototype())
138 | }
139 | }
140 |
141 | export class ObjectType> extends ComplexType
{
142 | getTypeName(): string {
143 | return "object";
144 | }
145 |
146 | canWriteTo(target: Type) {
147 | if(this.equalsTo(target) || target.canWriteBy?.(this))
148 | return true
149 | if(target.getTypeName() != 'object')
150 | return false
151 | const target_prototype = (target as ObjectType>).getPrototype()
152 | for(const key in this.prototype){
153 | if(!target_prototype[key] || !target_prototype[key].canWriteTo(this.prototype[key]))
154 | return false
155 | }
156 | return true
157 | }
158 |
159 | canWriteBy(target: Type) : boolean {
160 | return this.equalsTo(target) || target.canWriteTo(this)
161 | }
162 |
163 | equalsTo(target: Type): boolean {
164 | if(target.getTypeName()!=this.getTypeName())
165 | return false
166 | const target_prototype = (target as ObjectType>).getPrototype()
167 | if(Object.keys(target_prototype).length!=Object.keys(this.prototype).length)
168 | return false
169 | for(const key in this.prototype){
170 | if(!target_prototype[key] || !target_prototype[key].equalsTo(this.prototype[key]))
171 | return false
172 | }
173 | return true
174 | }
175 | }
176 |
177 | export class UnionType extends ComplexType{
178 | getTypeName(): string {
179 | return "union";
180 | }
181 |
182 | canWriteTo(target: Type) {
183 | for(const type of this.prototype){
184 | if(!type.canWriteTo(target)){
185 | return false
186 | }
187 | }
188 | return true
189 | }
190 |
191 | equalsTo(target: Type) {
192 | if(target.getTypeName()!=this.getTypeName())
193 | return false
194 | const target_prototype = (target as UnionType).getPrototype()
195 | if(target_prototype.length!=this.prototype.length)
196 | return false
197 | for(const type of this.prototype){
198 | let found = target_prototype.findIndex(t=>t.equalsTo(type))
199 | if(found==-1)
200 | return false
201 | target_prototype.splice(found,1)
202 | }
203 | return true
204 | }
205 |
206 | canWriteBy(target:Type){
207 | return this.prototype.some((type)=>{
208 | return target.canWriteTo(type)
209 | })
210 | }
211 | }
212 |
213 | export class ClassType extends Type{
214 | getTypeName(): string {
215 | return "class";
216 | }
217 |
218 | canWriteTo(target: Type) {
219 | return this.equalsTo(target) || target.canWriteBy?.(this)
220 | }
221 |
222 | equalsTo(target: Type) {
223 | return target.getTypeName() == this.getTypeName() && this.prototype == target.getPrototype()
224 | }
225 | }
226 |
227 | export class AnyType extends Type{
228 | getTypeName(): string {
229 | return "any";
230 | }
231 | canWriteTo(target: Type): boolean {
232 | return true
233 | }
234 | canWriteBy(target: Type): boolean {
235 | return true
236 | }
237 | equalsTo(target: Type): boolean {
238 | return target.getTypeName() == this.getTypeName()
239 | }
240 | }
241 |
242 | export class NeverType extends Type{
243 | getTypeName(): string {
244 | return "never";
245 | }
246 | canWriteTo(target: Type): boolean {
247 | return this.equalsTo(target)
248 | }
249 | canWriteBy(target: Type): boolean {
250 | return this.equalsTo(target)
251 | }
252 | equalsTo(target: Type): boolean {
253 | return target.getTypeName() == this.getTypeName()
254 | }
255 | }
256 |
257 | export function unify(types:T):T[0]|UnionType{
258 | if(!Array.isArray(types) || types.length <=1)
259 | return types[0]
260 | return new UnionType(types as Type[])
261 | }
262 |
263 | declare module "blockly"{
264 | interface Block{
265 | getOutputType():Type
266 | }
267 | interface Input{
268 | input_type:Type
269 | }
270 | }
271 |
272 | export class TypedConnectionChecker extends ConnectionChecker{
273 | protected workspace : WorkspaceSvg
274 | constructor(workspace:WorkspaceSvg) {
275 | super();
276 | this.workspace = workspace
277 | }
278 | doTypeChecks(a: Connection, b: Connection): boolean {
279 | if(!(a.type == 1 || b.type == 1))
280 | return super.doTypeChecks(a,b)
281 | const input = a.type == 1 ? a : b
282 | const output = a.type == 1 ? b : a
283 | const output_type = output.getSourceBlock().getOutputType?.()
284 | const input_type = input.getParentInput().input_type
285 | if(!output_type || !input_type)
286 | return super.doTypeChecks(a,b)
287 | return super.doTypeChecks(a,b) && output_type.canWriteTo(input_type)
288 | }
289 | }
290 |
291 | export function registerTypeExtension(workspace:WorkspaceSvg){
292 | workspace.addChangeListener(()=>{
293 |
294 | })
295 | }
296 |
--------------------------------------------------------------------------------
/client/blockly/plugins/type.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Block,
3 | BlocklyOptions,
4 | BlockSvg,
5 | Bubble,
6 | FieldImage,
7 | Icon,
8 | Mutator,
9 | Options,
10 | Workspace,
11 | WorkspaceSvg
12 | } from "blockly";
13 | import * as Blockly from "blockly";
14 | import {ReactiveBindingSet, ReactiveValue} from "../binding";
15 | import {} from '../typing/'
16 | import {convertTypeToBlock} from "../typing/converter";
17 | const {dom,Svg,Coordinate} = Blockly.utils
18 |
19 | declare module "blockly"{
20 | interface Workspace{
21 | typings: ReactiveBindingSet>
22 | }
23 | interface Block{
24 | type_manager: TypeManager
25 | getTypeOutput(inputs:any[]):any
26 | }
27 | }
28 |
29 | function disableAllBlock(root_block:Block){
30 | root_block.setMovable(false)
31 | root_block.setEditable(false)
32 | root_block.setDeletable(false)
33 | root_block.getChildren(false).forEach(disableAllBlock)
34 | }
35 |
36 | function renderAllBlock(root_block:BlockSvg){
37 | root_block.render()
38 | root_block.getChildren(false).forEach(renderAllBlock)
39 | }
40 |
41 | export class TypeMutator extends Icon{
42 |
43 | /** Width of workspace. */
44 | private workspaceWidth = 0;
45 |
46 | /** Height of workspace. */
47 | private workspaceHeight = 0;
48 | private svgDialog: SVGGElement;
49 |
50 | protected drawIcon_(group: Element) {
51 | // Square with rounded corners.
52 | dom.createSvgElement(
53 | Svg.RECT, {
54 | 'class': 'blocklyIconShape',
55 | 'rx': '4',
56 | 'ry': '4',
57 | 'height': '16',
58 | 'width': '16',
59 | },
60 | group);
61 | const g = dom.createSvgElement(Svg.G,{
62 | transform:"scale(0.024,0.024)translate(45,60)",
63 | },group)
64 | // Gear teeth.
65 | dom.createSvgElement(
66 | Svg.PATH, {
67 | 'class': 'blocklyIconSymbol',
68 | 'd': 'M118.6 80c-11.5 0-21.4 7.9-24 19.1L57 260.3c20.5-6.2 48.3-12.3 78.7-12.3c32.3 0 61.8 6.9 82.8 13.5c10.6 3.3 19.3 6.7 25.4 9.2c3.1 1.3 5.5 2.4 7.3 3.2c.9 .4 1.6 .7 2.1 1l.6 .3 .2 .1 .1 0 0 0 0 0s0 0-6.3 12.7h0l6.3-12.7c5.8 2.9 10.4 7.3 13.5 12.7h40.6c3.1-5.3 7.7-9.8 13.5-12.7l6.3 12.7h0c-6.3-12.7-6.3-12.7-6.3-12.7l0 0 0 0 .1 0 .2-.1 .6-.3c.5-.2 1.2-.6 2.1-1c1.8-.8 4.2-1.9 7.3-3.2c6.1-2.6 14.8-5.9 25.4-9.2c21-6.6 50.4-13.5 82.8-13.5c30.4 0 58.2 6.1 78.7 12.3L481.4 99.1c-2.6-11.2-12.6-19.1-24-19.1c-3.1 0-6.2 .6-9.2 1.8L416.9 94.3c-12.3 4.9-26.3-1.1-31.2-13.4s1.1-26.3 13.4-31.2l31.3-12.5c8.6-3.4 17.7-5.2 27-5.2c33.8 0 63.1 23.3 70.8 56.2l43.9 188c1.7 7.3 2.9 14.7 3.5 22.1c.3 1.9 .5 3.8 .5 5.7v6.7V352v16c0 61.9-50.1 112-112 112H419.7c-59.4 0-108.5-46.4-111.8-105.8L306.6 352H269.4l-1.2 22.2C264.9 433.6 215.8 480 156.3 480H112C50.1 480 0 429.9 0 368V352 310.7 304c0-1.9 .2-3.8 .5-5.7c.6-7.4 1.8-14.8 3.5-22.1l43.9-188C55.5 55.3 84.8 32 118.6 32c9.2 0 18.4 1.8 27 5.2l31.3 12.5c12.3 4.9 18.3 18.9 13.4 31.2s-18.9 18.3-31.2 13.4L127.8 81.8c-2.9-1.2-6-1.8-9.2-1.8zM64 325.4V368c0 26.5 21.5 48 48 48h44.3c25.5 0 46.5-19.9 47.9-45.3l2.5-45.6c-2.3-.8-4.9-1.7-7.5-2.5c-17.2-5.4-39.9-10.5-63.6-10.5c-23.7 0-46.2 5.1-63.2 10.5c-3.1 1-5.9 1.9-8.5 2.9zM512 368V325.4c-2.6-.9-5.5-1.9-8.5-2.9c-17-5.4-39.5-10.5-63.2-10.5c-23.7 0-46.4 5.1-63.6 10.5c-2.7 .8-5.2 1.7-7.5 2.5l2.5 45.6c1.4 25.4 22.5 45.3 47.9 45.3H464c26.5 0 48-21.5 48-48z',
69 | },
70 | g);
71 | }
72 | display_workspace_:WorkspaceSvg
73 | createDisplay():SVGGElement{
74 | this.svgDialog = dom.createSvgElement(
75 | Svg.SVG, {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH});
76 |
77 | const block = this.getBlock();
78 | const workspaceOptions = new Options(({
79 | 'disable': true,
80 | 'media': block.workspace.options.pathToMedia,
81 | 'rtl': block.RTL,
82 | 'horizontalLayout': false,
83 | 'renderer': block.workspace.options.renderer,
84 | 'rendererOverrides': block.workspace.options.rendererOverrides,
85 | } as BlocklyOptions));
86 |
87 | this.display_workspace_ = new WorkspaceSvg(workspaceOptions)
88 |
89 | const background = this.display_workspace_.createDom('blocklyMutatorBackground')
90 |
91 | this.svgDialog.appendChild(background);
92 |
93 | return this.svgDialog;
94 |
95 | }
96 | setVisible(_visible: boolean) {
97 | console.info("BP0")
98 | if(_visible == this.isVisible())return
99 | if(_visible){
100 | const block = this.getBlock();
101 | console.info(this.iconXY_)
102 | this.bubble_ = new Bubble(
103 | block.workspace, this.createDisplay(), block.pathObject.svgPath,
104 | (this.iconXY_), null, null);
105 | const ws = this.display_workspace_!;
106 | this.bubble_.setSvgId(block.id);
107 | this.bubble_.registerMoveEvent(this.onBubbleMove.bind(this));
108 | const tree = ws.options.languageTree;
109 | const flyout = ws.getFlyout();
110 | this.resizeBubble();
111 | this.applyColour();
112 | const root_block = this.display_workspace_.newBlock('type_root')
113 | root_block.initSvg()
114 | root_block.moveTo(new Coordinate(10,10))
115 | this.display_workspace_.addTopBlock(root_block)
116 | if(this.getBlock().getOutputType){
117 | const topType = convertTypeToBlock(this.display_workspace_,this.getBlock().getOutputType())
118 | if(topType){
119 | topType.initSvg()
120 | root_block.getInput('type').connection.connect(topType.outputConnection)
121 | topType.render()
122 | }
123 | }else{
124 | const topType = this.display_workspace_.newBlock('type_any')
125 | topType.initSvg()
126 | root_block.getInput('type').connection.connect(topType.outputConnection)
127 | topType.render()
128 | }
129 | renderAllBlock(root_block)
130 | disableAllBlock(root_block)
131 | this.display_workspace_.scrollCenter()
132 | this.resizeBubble()
133 | }else{
134 | this.display_workspace_.getTopBlocks(false)
135 | .forEach((b)=> {
136 | this.display_workspace_.removeTopBlock(b)
137 | })
138 | this.svgDialog = null;
139 | this.display_workspace_.dispose()
140 | this.display_workspace_ = null
141 | this.bubble_.dispose();
142 | this.bubble_ = null;
143 | this.workspaceWidth = 0;
144 | this.workspaceHeight = 0;
145 | /*if (this.sourceListener) {
146 | block.workspace.removeChangeListener(this.sourceListener);
147 | this.sourceListener = null;
148 | }*/
149 | }
150 | }
151 | protected resizeBubble() {
152 | if (!this.display_workspace_) {
153 | return;
154 | }
155 | const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH;
156 | const canvas = this.display_workspace_.getCanvas();
157 | const workspaceSize = canvas.getBBox();
158 | let width = workspaceSize.width + workspaceSize.x;
159 | let height = workspaceSize.height + doubleBorderWidth * 3;
160 | const flyout = this.display_workspace_.getFlyout();
161 | if (flyout) {
162 | const flyoutScrollMetrics =
163 | flyout.getWorkspace().getMetricsManager().getScrollMetrics();
164 | height = Math.max(height, flyoutScrollMetrics.height + 20);
165 | width += flyout.getWidth();
166 | }
167 |
168 | const isRtl = this.getBlock().RTL;
169 | if (isRtl) {
170 | width = -workspaceSize.x;
171 | }
172 | width += doubleBorderWidth * 3;
173 | if (Math.abs(this.workspaceWidth - width) > doubleBorderWidth ||
174 | Math.abs(this.workspaceHeight - height) > doubleBorderWidth) {
175 | this.workspaceWidth = width;
176 | this.workspaceHeight = height;
177 | this.bubble_!.setBubbleSize(
178 | width + doubleBorderWidth, height + doubleBorderWidth);
179 | this.svgDialog!.setAttribute('width', `${width}`);
180 | this.svgDialog!.setAttribute('height', `${height}`);
181 | this.display_workspace_.setCachedParentSvgSize(width, height);
182 | }
183 | if (isRtl) {
184 | canvas.setAttribute('transform', `translate(${this.workspaceWidth}, 0)`);
185 | }
186 | this.display_workspace_.resize();
187 | }
188 | onBubbleMove(){
189 | this.display_workspace_?.recordDragTargets();
190 | }
191 | }
192 |
193 | export class TypeManager{
194 | constructor(protected block:Block) {
195 | const output = block
196 | }
197 | }
198 |
199 | export function registerTypeManager(workspace:Workspace){
200 | workspace.typings = new ReactiveBindingSet>()
201 | Object.keys(Blockly.Blocks).forEach(k=>{
202 | const _init = Blockly.Blocks[k].init
203 | if(k.startsWith('type_'))return
204 | Blockly.Blocks[k].init = function(this:BlockSvg){
205 | _init.call(this)
206 | this.type_manager = new TypeManager(this)
207 | if(!this.outputConnection)return
208 | const mutator = new TypeMutator(this)
209 | //mutator.setVisible(true)
210 | const getIcon_ = this.getIcons.bind(this)
211 | this.getIcons = ()=>{
212 | return [...getIcon_(),mutator]
213 | }
214 | }
215 | })
216 | }
217 |
--------------------------------------------------------------------------------
/client/blockly/toolbox.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | EQ
7 |
8 |
9 | AND
10 |
11 |
12 |
13 | TRUE
14 |
15 |
16 |
17 |
18 |
19 |
20 | 1000
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 10
30 |
31 |
32 |
33 |
34 | WHILE
35 |
36 |
37 | i
38 |
39 |
40 | 1
41 |
42 |
43 |
44 |
45 | 10
46 |
47 |
48 |
49 |
50 | 1
51 |
52 |
53 |
54 |
55 | j
56 |
57 |
58 | BREAK
59 |
60 |
61 |
62 |
63 | 0
64 |
65 |
66 |
67 | ADD
68 |
69 |
70 | 1
71 |
72 |
73 |
74 |
75 | 1
76 |
77 |
78 |
79 |
80 | ROOT
81 |
82 |
83 | 9
84 |
85 |
86 |
87 |
88 | SIN
89 |
90 |
91 | 45
92 |
93 |
94 |
95 |
96 | PI
97 |
98 |
99 |
100 | EVEN
101 |
102 |
103 | 0
104 |
105 |
106 |
107 |
108 | ROUND
109 |
110 |
111 | 3.1
112 |
113 |
114 |
115 |
116 |
117 | SUM
118 |
119 |
120 |
121 |
122 | 64
123 |
124 |
125 |
126 |
127 | 10
128 |
129 |
130 |
131 |
132 |
133 |
134 | 50
135 |
136 |
137 |
138 |
139 | 1
140 |
141 |
142 |
143 |
144 | 100
145 |
146 |
147 |
148 |
149 |
150 |
151 | 1
152 |
153 |
154 |
155 |
156 | 100
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | item
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | abc
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | FIRST
193 |
194 |
195 | text
196 |
197 |
198 |
199 |
200 | abc
201 |
202 |
203 |
204 |
205 |
206 | FROM_START
207 |
208 |
209 | text
210 |
211 |
212 |
213 |
214 |
215 | FROM_START
216 | FROM_START
217 |
218 |
219 | text
220 |
221 |
222 |
223 |
224 | UPPERCASE
225 |
226 |
227 | abc
228 |
229 |
230 |
231 |
232 | BOTH
233 |
234 |
235 | abc
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | 5
253 |
254 |
255 |
256 |
257 |
258 |
259 | FIRST
260 |
261 |
262 | list
263 |
264 |
265 |
266 |
267 |
268 | GET
269 | FROM_START
270 |
271 |
272 | list
273 |
274 |
275 |
276 |
277 |
278 | SET
279 | FROM_START
280 |
281 |
282 | list
283 |
284 |
285 |
286 |
287 |
288 | FROM_START
289 | FROM_START
290 |
291 |
292 | list
293 |
294 |
295 |
296 |
297 |
298 | SPLIT
299 |
300 |
301 | ,
302 |
303 |
304 |
305 |
306 | NUMERIC
307 | 1
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 | item
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 | https://koishi.chat/logo.png
354 |
355 |
356 |
357 |
358 |
359 |
360 | https://example.org/example.mp3
361 |
362 |
363 |
364 |
365 |
366 |
367 | https://example.org/example.mp4
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 | https://koishi.chat/
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 | 键值对键
394 |
395 |
396 |
397 |
398 |
399 | 写入值
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 | 键值对键
410 |
411 |
412 |
413 |
414 | 键值对键
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 | 60000
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 | Hello,world
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
--------------------------------------------------------------------------------