diff --git a/py-et/.vscode/settings.json b/py-et/.vscode/settings.json new file mode 100644 index 0000000..728e7f9 --- /dev/null +++ b/py-et/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.testing.autoTestDiscoverOnSaveEnabled": true, +} diff --git a/py-et/README b/py-et/README new file mode 100644 index 0000000..8e9c8a9 --- /dev/null +++ b/py-et/README @@ -0,0 +1 @@ +# WPS 表格自动化(插件版) diff --git a/py-et/invoke-et.py b/py-et/invoke-et.py deleted file mode 100644 index 4952359..0000000 --- a/py-et/invoke-et.py +++ /dev/null @@ -1,12 +0,0 @@ -from ET import ET - -if __name__ == "__main__": - - et1 = ET() - sheets_name = et1.get_sheets() - print(f"et1: {sheets_name}") - - et2 = ET() - et2.workbook_name = "工作簿2" - sheets_name = et2.get_sheets() - print(f"et2:{sheets_name}") diff --git a/py-et/src/ET.py b/py-et/src/ET.py new file mode 100644 index 0000000..3079539 --- /dev/null +++ b/py-et/src/ET.py @@ -0,0 +1,46 @@ +import os + + +from .client import Client + + +class ET(Client): + def __init__(self): + super().__init__() + self.workbook_name = "" # 用于判断当前打开的表格 + # self.check_addon_install() # 调试模式暂时不判断 + self.inject_flag = self._inject_js_code() + + def open(self, file_path): + """open et""" + pass + + def get_sheets(self) -> list: + """get all sheets""" + func = "getSheets" + sheets_name = self._wrap_invoke_func(func) + + return sheets_name + + def _wrap_invoke_func(self, func, args={}): + """将工作簿的名称自动带进去""" + if self.workbook_name: + args["workbook_name"] = self.workbook_name + res = self.invoke_func(func, args) + if res.get("code") != 0: + raise Exception(f"调用函数 {func} 失败, 错误信息: {res.get('msg')}") + return res.get("data") + + def _inject_js_code(self) -> bool: + """注入js脚本""" + current_path = os.path.abspath(__file__) + js_code_path = os.path.join(os.path.dirname(current_path), "et.js") + if not os.path.exists(js_code_path): + raise Exception(f"js code not found: {js_code_path}") + # 读取js代码 + with open(js_code_path, encoding="utf8") as f: + code = f.read() + func = "inject" + args = {"code": code} + res = self.invoke_func(func, args) + return res.get("code") == 0 diff --git a/py-et/src/__pycache__/ET.cpython-313.pyc b/py-et/src/__pycache__/ET.cpython-313.pyc new file mode 100644 index 0000000..e9029fc Binary files /dev/null and b/py-et/src/__pycache__/ET.cpython-313.pyc differ diff --git a/py-et/src/__pycache__/client.cpython-313.pyc b/py-et/src/__pycache__/client.cpython-313.pyc new file mode 100644 index 0000000..eca9574 Binary files /dev/null and b/py-et/src/__pycache__/client.cpython-313.pyc differ diff --git a/py-et/ET.py b/py-et/src/client.py similarity index 75% rename from py-et/ET.py rename to py-et/src/client.py index 38304f1..eb6b0ae 100644 --- a/py-et/ET.py +++ b/py-et/src/client.py @@ -1,22 +1,124 @@ -import base64 +import os +import sys import uuid import json -import http.client -import webbrowser -import os -import platform -import xml.dom.minidom as minidom -import sys -import shutil -import zipfile import time +import shutil +import base64 +import zipfile +import platform +import webbrowser +import logging + +logger = logging.getLogger(__file__) + +import http.client +import xml.dom.minidom as minidom + +from xml.etree import ElementTree class Client: def __init__(self): - self.addon_name = "XbotWps" + self.addon_name = "xbot-et" self.addon_type = "et" self.addon_version = "1.0.0" + self.url = "http://127.0.0.1:3889/" + self.start_http_server() + + def start_http_server(self) -> bool: + for _ in range(10): + if self._get_version(): + print("Wps server started") + return True + self._start_http_server() + time.sleep(1) + raise ("Wps server not started") + + def get_addon_list(self) -> list: + """获取插件列表""" + path = "/publishlist" + conn = http.client.HTTPConnection("127.0.0.1", 58890) + conn.request("GET", path, body="{}", headers={}) + response = conn.getresponse() + data = response.read() + conn.close() + json_data = json.loads(data.decode("utf-8")) + return json_data + + def verify_addon_status(self) -> bool: + """验证插件状态""" + url = self.url + "ribbon.xml" + data = {"method": "get", "url": url, "data": ""} + payload = base64.b64encode(json.dumps(data).encode("utf-8")).decode() + print(f"payload: {payload}") + conn = http.client.HTTPConnection("127.0.0.1", 58890) + conn.request("POST", "/redirect/runParams", payload, {}) + response = conn.getresponse() + data = response.read() + conn.close() + try: + root = ElementTree.fromstring(data) + # 检查根标签名是否包含 customUI(包含命名空间) + return root.tag.endswith("customUI") + except ElementTree.ParseError: + return False + + def enable_addon(self) -> bool: + """开启插件""" + data = { + "cmd": "enable", + "name": self.addon_name, + "url": self.url, + "addonType": "et", + "online": "true", + "time": int(time.time()), + } + path = "/deployaddons/runParams" + payload = base64.b64encode(json.dumps(data).encode("utf-8")).decode() + conn = http.client.HTTPConnection("127.0.0.1", 58890) + conn.request("POST", path, payload, {}) + response = conn.getresponse() + data = response.read() + conn.close() + return data.decode() == "OK" + + def disable_addon(self) -> bool: + """禁用插件""" + data = { + "cmd": "disable", + "name": self.addon_name, + "url": self.url, + "addonType": "et", + "online": "true", + "time": int(time.time()), + } + path = "/deployaddons/runParams" + payload = base64.b64encode(json.dumps(data).encode("utf-8")).decode() + conn = http.client.HTTPConnection("127.0.0.1", 58890) + conn.request("POST", path, payload, {}) + + response = conn.getresponse() + data = response.read() + conn.close() + return data.decode() == "OK" + + def is_client_running(self) -> bool: + """检查客户端是否运行""" + # --data-raw '{"app":"et"}' + path = "/isRunning" + data = { + "app": self.addon_type, + } + payload = json.dumps(data) + + conn = http.client.HTTPConnection("127.0.0.1", 58890) + conn.request("POST", path, payload, {}) + + response = conn.getresponse() + data = response.read() + conn.close() + return data.decode() == "Client is running." def invoke_func(self, func, args={}): @@ -35,7 +137,7 @@ class Client: xhr.send(JSON.stringify({{id: '{req_uui}', response: res}}));void(0" data = { - "name": "xbot-et", + "name": self.addon_name, "function": "var res = InvokeFromXbot", "info": info, "showToFront": True, @@ -95,26 +197,6 @@ class Client: print("Installation completed successfully.") - def check_addon_install(self): - """通过文件夹是否存在判断插件是否安装""" - install_dir = self._get_install_directory() - addon_name_with_version = f"{self.addon_name}_{self.addon_version}" - file_path = os.path.join(install_dir, addon_name_with_version) - - if os.path.exists(file_path): - return True - else: - return False - - def check_http_status(self): - for _ in range(10): - if self._get_version(): - print("Wps server started") - return - self._start_http_server() - time.sleep(1) - raise ("Wps server not started") - def _start_http_server(self): """start debug""" webbrowser.open("ksowpscloudsvr://start=RelayHttpServer") @@ -289,37 +371,3 @@ class Client: except Exception as e: print(f"Error extracting archive: {e}") return False - - -class ET(Client): - def __init__(self): - super().__init__() - self.workbook_name = "" # 用于判断当前打开的表格 - self.check_http_status() - # self.check_addon_install() # 调试模式暂时不判断 - self._inject_js_code() - - def open(self, file_path): - """open et""" - pass - - def get_sheets(self) -> list: - """get all sheets""" - func = "getSheets" - sheets_name = self._wrap_invoke_func(func) - return sheets_name - - def _wrap_invoke_func(self, func, args={}): - """将工作簿的名称自动带进去""" - if self.workbook_name: - args["workbook_name"] = self.workbook_name - return self.invoke_func(func, args) - - def _inject_js_code(self): - path = r"C:\Users\32075\OneDrive\文档\Code\RPA_Script\wps-js-extension\XbotWps\py-et\et.js" - with open(path, encoding="utf8") as f: - code = f.read() - func = "inject" - args = {"code": code} - res = self.invoke_func(func, args) - return res diff --git a/py-et/et.js b/py-et/src/et.js similarity index 100% rename from py-et/et.js rename to py-et/src/et.js diff --git a/py-et/tests/__pycache__/test_client.cpython-313-pytest-8.3.5.pyc b/py-et/tests/__pycache__/test_client.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000..f0f505c Binary files /dev/null and b/py-et/tests/__pycache__/test_client.cpython-313-pytest-8.3.5.pyc differ diff --git a/py-et/tests/__pycache__/test_et.cpython-313-pytest-8.3.5.pyc b/py-et/tests/__pycache__/test_et.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000..b6dce82 Binary files /dev/null and b/py-et/tests/__pycache__/test_et.cpython-313-pytest-8.3.5.pyc differ diff --git a/py-et/tests/__pycache__/test_xx.cpython-313-pytest-8.3.5.pyc b/py-et/tests/__pycache__/test_xx.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 0000000..f746425 Binary files /dev/null and b/py-et/tests/__pycache__/test_xx.cpython-313-pytest-8.3.5.pyc differ diff --git a/py-et/tests/test_client.py b/py-et/tests/test_client.py new file mode 100644 index 0000000..4352449 --- /dev/null +++ b/py-et/tests/test_client.py @@ -0,0 +1,70 @@ +# test src/et.py client + +from src.ET import Client +import pytest + + +@pytest.fixture(scope="module") +def test_create_client(): + """创建测试用的client""" + client = Client() + yield client + + +def test_client_get_addon_list(test_create_client): + """测试获取插件列表""" + client = test_create_client + print("Getting addon list...") + addon_list = client.get_addon_list() + print(f"Retrieved {len(addon_list)} addons") + assert isinstance(addon_list, list) + assert len(addon_list) > 0 + assert all(isinstance(addon, dict) for addon in addon_list) + # 遍历所有插件, 并输出 + for i, addon in enumerate(addon_list): + print(f"Addon {i}: {addon}") + print("First addon in list:", addon_list[0] if addon_list else "None") + print("Addon list test passed!") + + +def test_client_verify_addon_status(test_create_client): + """测试插件状态""" + client = test_create_client + print("Verifying addon status...") + status = client.verify_addon_status() + print(f"Addon status: {status}") + assert status is True, "Addon status is not verified successfully" + print("Addon status test passed!") + + +def test_client_enable_addon(test_create_client): + """测试启用插件""" + client = test_create_client + print("Enabling addon...") + status = client.enable_addon() + print(f"Addon enabled: {status}") + assert status is True, "Failed to enable addon" + print("Enable addon test passed!") + + +def test_client_disable_addon(test_create_client): + """测试禁用插件""" + client = test_create_client + print("Disabling addon...") + status = client.disable_addon() + print(f"Addon disabled: {status}") + assert status is True, "Failed to disable addon" + print("Disable addon test passed!") + +def test_client_is_client_running(test_create_client): + """测试客户端是否运行""" + client = test_create_client + print("Checking if client is running...") + is_running = client.is_client_running() + print(f"Client is running: {is_running}") + assert isinstance(is_running, bool), "Client running status is not a boolean" + print("Client running status test passed!") + +if __name__ == "__main__": + # Add direct test execution for better debugging + pass diff --git a/py-et/tests/test_et.py b/py-et/tests/test_et.py new file mode 100644 index 0000000..c959e55 --- /dev/null +++ b/py-et/tests/test_et.py @@ -0,0 +1,39 @@ +import pytest + +from src.ET import ET + + +@pytest.fixture(scope="module") +def test_create_client(): + """创建测试用的client""" + client = ET() + yield client + + +def test_et_check_inject(): + """测试注入""" + et = ET() + print("Injecting...") + assert et.inject_flag is True, "Injection failed" + print("Inject test passed!") + + +def test_et_get_sheets(): + """测试获取工作表""" + et = ET() + print("Getting sheets...") + sheets_name = et.get_sheets() + print(f"Retrieved {len(sheets_name)} sheets") + print(f"Sheets_name: {sheets_name}") + assert isinstance(sheets_name, list) + assert len(sheets_name) > 0 + assert all(isinstance(sheet, str) for sheet in sheets_name) + # 遍历所有工作表, 并输出 + for i, sheet in enumerate(sheets_name): + print(f"Sheet {i}: {sheet}") + print("First sheet in list:", sheets_name[0] if sheets_name else "None") + print("Get sheets test passed!") + + +if __name__ == "__main__": + pass diff --git a/xbot-et/wps-addon-build/xbot-et.7z b/xbot-et/wps-addon-build/xbot-et.7z index 6443525..dafc6e4 100644 Binary files a/xbot-et/wps-addon-build/xbot-et.7z and b/xbot-et/wps-addon-build/xbot-et.7z differ diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/README.md b/xbot-et/wps-addon-build/xbot-et_1.0.0/README.md new file mode 100644 index 0000000..bf03dc2 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/README.md @@ -0,0 +1 @@ +# WPS 表格自动化 diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/images/about.svg b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/about.svg new file mode 100644 index 0000000..82718c1 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/about.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/images/newFromTemp.svg b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/newFromTemp.svg new file mode 100644 index 0000000..f6c70b3 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/images/newFromTemp.svg @@ -0,0 +1,22 @@ + + + + 从模版新建 + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/index.html b/xbot-et/wps-addon-build/xbot-et_1.0.0/index.html new file mode 100644 index 0000000..2efb298 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/index.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/js/ribbon.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/ribbon.js new file mode 100644 index 0000000..cc40e95 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/ribbon.js @@ -0,0 +1,43 @@ + +//这个函数在整个wps加载项中是第一个执行的 +function OnAddinLoad(ribbonUI) { + if (typeof (window.Application.ribbonUI) != "object") { + window.Application.ribbonUI = ribbonUI + } + + if (typeof (window.Application.Enum) != "object") { // 如果没有内置枚举值 + window.Application.Enum = WPS_Enum + } + return true +} + + +function OnAction(control) { + const eleId = control.Id + switch (eleId) { + case "btnShowAbout": + { + alert("关于我们\n\n作者:Jianxin\n\n版本:1.0.0\n\n说明:自动化插件\n\n感谢使用!") + } + break; + case "contactAuthor": + { + wps.ShowDialog(GetUrlPath() + "/ui/author.html", "联系作者", 400 * window.devicePixelRatio, 400 * window.devicePixelRatio, false) + } + default: + break + } + return true +} + + +function GetImage(control) { + const eleId = control.Id + switch (eleId) { + case "btnShowAbout": + return "images/about.svg" + default: + ; + } + return "images/newFromTemp.svg" +} diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/js/util.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/util.js new file mode 100644 index 0000000..8c991a1 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/util.js @@ -0,0 +1,19 @@ +//在后续的wps版本中,wps的所有枚举值都会通过wps.Enum对象来自动支持,现阶段先人工定义 +var WPS_Enum = { + msoCTPDockPositionLeft: 0, + msoCTPDockPositionRight: 2 +} + +function GetUrlPath() { + let e = document.location.toString() + return -1 != (e = decodeURI(e)).indexOf("/") && (e = e.substring(0, e.lastIndexOf("/"))), e +} +/** + * 通过wps提供的接口执行一段脚本 + * @param {*} param 需要执行的脚本 + */ +function shellExecuteByOAAssist(param) { + if (wps != null) { + wps.OAAssist.ShellExecute(param) + } +} \ No newline at end of file diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/js/xbot.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/xbot.js new file mode 100644 index 0000000..1928235 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/js/xbot.js @@ -0,0 +1,64 @@ +let wpsInstance = null; + + +function InvokeFromXbot(param) { + // 1. 解析参数, 如果不是字符串, 则转换为字符串, 并用json解析, 解析失败直接retrun + if (typeof param !== 'string') { + param = JSON.stringify(param); + } + try { + param = JSON.parse(param); + } catch (e) { + return { msg: '参数解析失败', code: -1 }; + } + + // 2. 判断是否有method字段, 如果没有直接return + if (!param.method) { + return { msg: '参数错误', code: -2 }; + } + + // 3. 获取 method 字段对应的方法和 args 参数 + let method = param.method; + + let args = null; + + try { + args = JSON.parse(param.args); + } catch (e) { + args = {}; + } + + + console.log("method:", method); + console.log("args:", args); + // confsole.log("args", typeof args); + + // 4. 判断 method 是否为 inject, 如果是则执行注入方法, 其他走默认方法执行调用函数 + if (method === 'inject') { + let code = args.code; + console.log("start eval code"); + console.log(code); + console.log(new Function(code)()); + wpsInstance = new Function(code)(); // 执行并获取返回值 + console.log("success eval code"); + return { message: "inject ok", code: 0 } + } + + // 5. 判断是否有 wpsInstance, 如果没有直接return + if (!wpsInstance) { + return { msg: '未注入', code: -3 }; + } + + // 6. 判断 wpsInstance 是否有 method 方法, 如果没有直接return + if (!wpsInstance[method]) { + return { msg: '方法不存在', code: -4 }; + } + + // 7. 执行 wpsInstance 的 method 方法, 并返回结果 + try { + const result = wpsInstance[method](args ?? null); + return { msg: '执行成功', code: 0, data: result }; + } catch (e) { + return { msg: '执行失败', code: -5 }; + } +} diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/main.js b/xbot-et/wps-addon-build/xbot-et_1.0.0/main.js new file mode 100644 index 0000000..fc183d6 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/main.js @@ -0,0 +1,4 @@ +//这个文件由index.html包含 +document.write(""); +document.write(""); +document.write(""); diff --git a/xbot-et/wps-addon-build/xbot-et_1.0.0/ribbon.xml b/xbot-et/wps-addon-build/xbot-et_1.0.0/ribbon.xml new file mode 100644 index 0000000..cdaa597 --- /dev/null +++ b/xbot-et/wps-addon-build/xbot-et_1.0.0/ribbon.xml @@ -0,0 +1,15 @@ + + + + + +