@tanskong/office-assistant 1.0.0
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/README.md +57 -0
- package/bin/office-assistant.js +54 -0
- package/package.json +52 -0
- package/requirements.txt +3 -0
- package/src/.dev_mode +2 -0
- package/src/__init__.py +0 -0
- package/src/license_manager.py +141 -0
- package/src/officer.py +391 -0
- package/src/server.py +267 -0
- package/src/utils.py +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Office Assistant
|
|
2
|
+
|
|
3
|
+
AI-powered Office automation via MCP protocol.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **RunPython**: Execute Python code to control Office applications freely
|
|
8
|
+
- **Smart Workspace**: Desktop folder for safe file processing
|
|
9
|
+
- **Full Office Suite**: Excel, Word, PowerPoint, Outlook, Visio, WPS, and more
|
|
10
|
+
- **License Protection**: Machine-bound license activation
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @yourname/office-assistant
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Activation
|
|
19
|
+
|
|
20
|
+
1. Set environment variable:
|
|
21
|
+
```bash
|
|
22
|
+
set OFFICE_ASSISTANT_LICENSE=OFFICE-ASSISTANT-XXXX-XXXX-XXXX-XXXX
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. Run activation:
|
|
26
|
+
```bash
|
|
27
|
+
npx @yourname/office-assistant activate
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. Start the server:
|
|
31
|
+
```bash
|
|
32
|
+
npx @yourname/office-assistant
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Disclaimer
|
|
36
|
+
|
|
37
|
+
Please place files in `Desktop/智能办公区/数据` before processing.
|
|
38
|
+
We are not responsible for data loss when operating on original file locations.
|
|
39
|
+
|
|
40
|
+
## Tools
|
|
41
|
+
|
|
42
|
+
| Tool | Description |
|
|
43
|
+
|------|-------------|
|
|
44
|
+
| `run_python` | Execute Python code (PRIMARY TOOL) |
|
|
45
|
+
| `available_apps` | List installed Office applications |
|
|
46
|
+
| `launch_app` | Launch an Office application |
|
|
47
|
+
| `quit_app` | Quit an Office application |
|
|
48
|
+
| `open_file` | Open an Office file |
|
|
49
|
+
| `save_file` | Save current document |
|
|
50
|
+
| `screenshot` | Capture screenshot |
|
|
51
|
+
| `download` | Download file from URL |
|
|
52
|
+
| `speak` | Text-to-speech |
|
|
53
|
+
| `demonstrate` | Run demonstration |
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
Commercial license required. See activation instructions above.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Office Assistant - MCP Server Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This script:
|
|
6
|
+
* 1. Checks Python environment
|
|
7
|
+
* 2. Verifies license activation
|
|
8
|
+
* 3. Launches the Python MCP server
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawn } = require('child_process');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
const PYTHON_SCRIPT = path.join(__dirname, '..', 'src', 'server.py');
|
|
16
|
+
|
|
17
|
+
function findPython() {
|
|
18
|
+
// Try 'python' first, then 'python3'
|
|
19
|
+
const candidates = ['python', 'python3'];
|
|
20
|
+
for (const cmd of candidates) {
|
|
21
|
+
try {
|
|
22
|
+
const result = require('child_process').execSync(`${cmd} --version`, { encoding: 'utf8' });
|
|
23
|
+
console.log(`[INFO] Found Python: ${result.trim()}`);
|
|
24
|
+
return cmd;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
console.error('[ERROR] Python not found. Please install Python 3.10+');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function main() {
|
|
34
|
+
const python = findPython();
|
|
35
|
+
|
|
36
|
+
console.log('[INFO] Starting Office Assistant MCP Server...');
|
|
37
|
+
console.log(`[INFO] Python script: ${PYTHON_SCRIPT}`);
|
|
38
|
+
|
|
39
|
+
const args = process.argv.slice(2);
|
|
40
|
+
const server = spawn(python, [PYTHON_SCRIPT, ...args], {
|
|
41
|
+
stdio: ['inherit', 'inherit', 'inherit']
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
server.on('close', (code) => {
|
|
45
|
+
process.exit(code);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
server.on('error', (err) => {
|
|
49
|
+
console.error('[ERROR] Failed to start server:', err.message);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanskong/office-assistant",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Office Assistant - AI-powered Office automation via MCP protocol with license protection",
|
|
5
|
+
"main": "bin/office-assistant.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"office-assistant": "bin/office-assistant.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/office-assistant.js",
|
|
11
|
+
"activate": "node bin/office-assistant.js activate",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"office",
|
|
17
|
+
"excel",
|
|
18
|
+
"word",
|
|
19
|
+
"powerpoint",
|
|
20
|
+
"automation",
|
|
21
|
+
"ai",
|
|
22
|
+
"microsoft-office",
|
|
23
|
+
"wps"
|
|
24
|
+
],
|
|
25
|
+
"author": "tanskong",
|
|
26
|
+
"license": "UNLICENSED",
|
|
27
|
+
"private": false,
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"registry": "https://registry.npmjs.org/"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"os": [
|
|
36
|
+
"win32"
|
|
37
|
+
],
|
|
38
|
+
"files": [
|
|
39
|
+
"bin/",
|
|
40
|
+
"src/",
|
|
41
|
+
"README.md",
|
|
42
|
+
"requirements.txt"
|
|
43
|
+
],
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/yourname/office-assistant.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/yourname/office-assistant/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/yourname/office-assistant#readme"
|
|
52
|
+
}
|
package/requirements.txt
ADDED
package/src/.dev_mode
ADDED
package/src/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Office Assistant License Activation System
|
|
4
|
+
==========================================
|
|
5
|
+
1. User activates with license key
|
|
6
|
+
2. System captures MAC address and machine fingerprint
|
|
7
|
+
3. Saves activation record locally
|
|
8
|
+
4. On every startup, verifies MAC address matches
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import json
|
|
14
|
+
import hashlib
|
|
15
|
+
import uuid
|
|
16
|
+
import socket
|
|
17
|
+
import subprocess
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
|
|
20
|
+
APP_DATA_DIR = os.path.join(os.environ.get("APPDATA", os.path.dirname(__file__)), "office-assistant")
|
|
21
|
+
ACTIVATION_FILE = os.path.join(APP_DATA_DIR, "activation.dat")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_mac_address():
|
|
25
|
+
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0, 48, 8)][::-1])
|
|
26
|
+
return mac.upper().replace(':', '')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_machine_fingerprint():
|
|
30
|
+
mac = get_mac_address()
|
|
31
|
+
hostname = socket.gethostname()
|
|
32
|
+
try:
|
|
33
|
+
result = subprocess.run(['wmic', 'cpu', 'get', 'ProcessorId'], capture_output=True, text=True)
|
|
34
|
+
cpu_id = result.stdout.strip().split('\n')[-1].strip()
|
|
35
|
+
except:
|
|
36
|
+
cpu_id = 'CPU-UNKNOWN'
|
|
37
|
+
combined = f"{mac}|{hostname}|{cpu_id}"
|
|
38
|
+
return hashlib.sha256(combined.encode()).hexdigest()[:32].upper()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_license_key():
|
|
42
|
+
return os.environ.get("OFFICE_ASSISTANT_LICENSE", "")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validate_key_format(key):
|
|
46
|
+
if not key:
|
|
47
|
+
return False, "No license key provided"
|
|
48
|
+
key = key.strip()
|
|
49
|
+
if not key.startswith("OFFICE-ASSISTANT-"):
|
|
50
|
+
return False, "Invalid key format. Key must start with OFFICE-ASSISTANT-"
|
|
51
|
+
return True, "OK"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def activate(key):
|
|
55
|
+
print("\n" + "=" * 50)
|
|
56
|
+
print(" Office Assistant Activation")
|
|
57
|
+
print("=" * 50)
|
|
58
|
+
|
|
59
|
+
if not key:
|
|
60
|
+
print("\n[ERROR] No license key provided!")
|
|
61
|
+
print("\nPlease set environment variable OFFICE_ASSISTANT_LICENSE")
|
|
62
|
+
print(" Windows: set OFFICE_ASSISTANT_LICENSE=your-key")
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
valid, msg = validate_key_format(key)
|
|
66
|
+
if not valid:
|
|
67
|
+
print(f"\n[ERROR] Invalid license key: {key}")
|
|
68
|
+
print(f" Error: {msg}")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
if os.path.exists(ACTIVATION_FILE):
|
|
72
|
+
print("\n[WARN] Already activated!")
|
|
73
|
+
print(f" Activation file: {ACTIVATION_FILE}")
|
|
74
|
+
print("\n To re-activate, delete activation.dat first.")
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
os.makedirs(APP_DATA_DIR, exist_ok=True)
|
|
78
|
+
|
|
79
|
+
mac = get_mac_address()
|
|
80
|
+
fingerprint = get_machine_fingerprint()
|
|
81
|
+
|
|
82
|
+
activation_data = {
|
|
83
|
+
"license_key": key.strip(),
|
|
84
|
+
"mac_address": mac,
|
|
85
|
+
"fingerprint": fingerprint,
|
|
86
|
+
"activated_at": datetime.now().isoformat(),
|
|
87
|
+
"hostname": socket.gethostname()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
with open(ACTIVATION_FILE, 'w') as f:
|
|
91
|
+
json.dump(activation_data, f, indent=2)
|
|
92
|
+
|
|
93
|
+
print("\n[OK] Activation Successful!")
|
|
94
|
+
print(f"\n License Key: {key}")
|
|
95
|
+
print(f" MAC Address: {mac}")
|
|
96
|
+
print(f" Fingerprint: {fingerprint}")
|
|
97
|
+
print(f" Activated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
98
|
+
print(f"\n Activation file: {ACTIVATION_FILE}")
|
|
99
|
+
print(f"\n This computer is now bound to this license.")
|
|
100
|
+
print("=" * 50)
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def verify_license():
|
|
105
|
+
"""Verify license on server startup"""
|
|
106
|
+
dev_marker = os.path.join(os.path.dirname(__file__), ".dev_mode")
|
|
107
|
+
if os.path.exists(dev_marker):
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
if not os.path.exists(ACTIVATION_FILE):
|
|
111
|
+
print("[ERROR] Office Assistant is not activated. Please run activation first.")
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
with open(ACTIVATION_FILE, 'r') as f:
|
|
116
|
+
data = json.load(f)
|
|
117
|
+
|
|
118
|
+
current_mac = get_mac_address()
|
|
119
|
+
current_fingerprint = get_machine_fingerprint()
|
|
120
|
+
stored_mac = data.get("mac_address", "")
|
|
121
|
+
stored_fingerprint = data.get("fingerprint", "")
|
|
122
|
+
stored_key = data.get("license_key", "").strip()
|
|
123
|
+
current_key = os.environ.get("OFFICE_ASSISTANT_LICENSE", "").strip()
|
|
124
|
+
|
|
125
|
+
if current_key != stored_key:
|
|
126
|
+
print("[ERROR] License key mismatch.")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
if current_mac != stored_mac or current_fingerprint != stored_fingerprint:
|
|
130
|
+
print("[ERROR] License is bound to another device.")
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f"[ERROR] License verification failed: {e}")
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
key = get_license_key()
|
|
140
|
+
success = activate(key)
|
|
141
|
+
sys.exit(0 if success else 1)
|
package/src/officer.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import winreg
|
|
2
|
+
import pywintypes
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import win32com.client
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TheOfficer:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self._excel = None
|
|
12
|
+
self._word = None
|
|
13
|
+
self._outlook = None
|
|
14
|
+
self._visio = None
|
|
15
|
+
self._access = None
|
|
16
|
+
self._project = None
|
|
17
|
+
self._publisher = None
|
|
18
|
+
self._onenote = None
|
|
19
|
+
self._powerpoint = None
|
|
20
|
+
self._ket = None
|
|
21
|
+
self._kwpp = None
|
|
22
|
+
self._kwps = None
|
|
23
|
+
|
|
24
|
+
self._default_folder = "D:\\@OfficeMCP"
|
|
25
|
+
self.Version = "1.0.5"
|
|
26
|
+
self.Speaker = None
|
|
27
|
+
self.ComObjects = {}
|
|
28
|
+
self._printable = False
|
|
29
|
+
self.Data = OfficerData()
|
|
30
|
+
|
|
31
|
+
self.MicrosoftApplications = [
|
|
32
|
+
'Word', 'Excel', 'PowerPoint',
|
|
33
|
+
'Visio', 'Access', 'MSProject',
|
|
34
|
+
'Outlook', 'Publisher', "OneNote", "Kwps", "Ket", "Kwpp"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def RootFolder(self) -> str:
|
|
39
|
+
if not os.path.exists(self._default_folder):
|
|
40
|
+
os.makedirs(self._default_folder)
|
|
41
|
+
return self._default_folder
|
|
42
|
+
|
|
43
|
+
def GetComObject(self, com_name: str, active: bool = True):
|
|
44
|
+
comobject = self.ComObjects.get(com_name)
|
|
45
|
+
if comobject is None:
|
|
46
|
+
try:
|
|
47
|
+
comobject = win32com.client.GetActiveObject(com_name)
|
|
48
|
+
except Exception:
|
|
49
|
+
comobject = win32com.client.Dispatch(com_name)
|
|
50
|
+
self.ComObjects[com_name] = comobject
|
|
51
|
+
return comobject
|
|
52
|
+
|
|
53
|
+
def Speak(self, text: str = "Hello!, I'm Office Assistant.", volume: int = 80, rate: int = 2) -> bool:
|
|
54
|
+
try:
|
|
55
|
+
if self.Speaker is None:
|
|
56
|
+
self.Speaker = self.GetComObject("SAPI.SPVOICE")
|
|
57
|
+
self.Speaker.Volume = volume
|
|
58
|
+
self.Speaker.Rate = rate
|
|
59
|
+
self.Speaker.Speak(text)
|
|
60
|
+
return True
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(e)
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
def DownloadImage(self, url: str, save_path: str = None) -> str:
|
|
66
|
+
import datetime
|
|
67
|
+
if save_path is None:
|
|
68
|
+
now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
69
|
+
save_path = f"downloaded_{now}.png"
|
|
70
|
+
return self.DownloadURL(url, save_path)
|
|
71
|
+
|
|
72
|
+
def DownloadURL(self, url: str, save_path: str = None) -> str:
|
|
73
|
+
import urllib.request
|
|
74
|
+
import datetime
|
|
75
|
+
if save_path is None:
|
|
76
|
+
now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
77
|
+
save_path = f"downloaded_{now}.png"
|
|
78
|
+
file_path = self.FilePath(save_path)
|
|
79
|
+
try:
|
|
80
|
+
with urllib.request.urlopen(url) as response, open(file_path, 'wb') as out_file:
|
|
81
|
+
out_file.write(response.read())
|
|
82
|
+
return file_path
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f'Download failed: {e}')
|
|
85
|
+
return ""
|
|
86
|
+
|
|
87
|
+
def Visible(self, app_name: str, visible=None) -> bool:
|
|
88
|
+
try:
|
|
89
|
+
app = self.Application(app_name)
|
|
90
|
+
if app is None:
|
|
91
|
+
return False
|
|
92
|
+
if visible is not None:
|
|
93
|
+
app.Visible = visible
|
|
94
|
+
return app.Visible
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(e)
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
def Quit(self, app_name: str, force: bool = False) -> bool:
|
|
100
|
+
app_name_attr = "_" + app_name.lower()
|
|
101
|
+
if hasattr(self, app_name_attr):
|
|
102
|
+
app = getattr(self, app_name_attr)
|
|
103
|
+
try:
|
|
104
|
+
return self.QuitApplication(app, force)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print(e)
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
def QuitApplication(self, app, force: bool = False) -> bool:
|
|
110
|
+
if not force:
|
|
111
|
+
try:
|
|
112
|
+
app.Quit()
|
|
113
|
+
return True
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(e)
|
|
116
|
+
return False
|
|
117
|
+
import win32process
|
|
118
|
+
import win32api
|
|
119
|
+
import win32con
|
|
120
|
+
hwnd = app.Hwnd
|
|
121
|
+
t, p = win32process.GetWindowThreadProcessId(hwnd)
|
|
122
|
+
try:
|
|
123
|
+
handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, p)
|
|
124
|
+
if handle:
|
|
125
|
+
win32api.TerminateProcess(handle, 0)
|
|
126
|
+
win32api.CloseHandle(handle)
|
|
127
|
+
return True
|
|
128
|
+
except:
|
|
129
|
+
pass
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def Application(self, app_name: str, asNewInstance: bool = False):
|
|
133
|
+
app_name_attr = "_" + app_name.lower()
|
|
134
|
+
if hasattr(self, app_name_attr):
|
|
135
|
+
app = getattr(self, app_name_attr)
|
|
136
|
+
try:
|
|
137
|
+
name = app.Name
|
|
138
|
+
return app
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
if app_name not in self.MicrosoftApplications:
|
|
142
|
+
return None
|
|
143
|
+
if not self.IsAppAvailable(app_name):
|
|
144
|
+
return None
|
|
145
|
+
app_full_name = app_name + ".Application"
|
|
146
|
+
if asNewInstance:
|
|
147
|
+
app = win32com.client.Dispatch(app_full_name)
|
|
148
|
+
self.__dict__[app_name_attr] = app
|
|
149
|
+
return app
|
|
150
|
+
else:
|
|
151
|
+
try:
|
|
152
|
+
app = win32com.client.GetActiveObject(app_full_name)
|
|
153
|
+
except pywintypes.com_error:
|
|
154
|
+
app = win32com.client.Dispatch(app_full_name)
|
|
155
|
+
self.__dict__[app_name_attr] = app
|
|
156
|
+
return app
|
|
157
|
+
|
|
158
|
+
def RunningApps(self) -> list:
|
|
159
|
+
apps = []
|
|
160
|
+
for prog_id in self.MicrosoftApplications:
|
|
161
|
+
try:
|
|
162
|
+
app = win32com.client.GetActiveObject(prog_id + ".Application")
|
|
163
|
+
if app is not None:
|
|
164
|
+
apps.append(prog_id)
|
|
165
|
+
except (FileNotFoundError, pywintypes.com_error):
|
|
166
|
+
continue
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print(f"[DEBUG] Error verifying {prog_id}: {str(e)}")
|
|
169
|
+
return apps
|
|
170
|
+
|
|
171
|
+
def AvailableApps(self) -> list:
|
|
172
|
+
apps = []
|
|
173
|
+
for prog_id in self.MicrosoftApplications:
|
|
174
|
+
try:
|
|
175
|
+
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, prog_id + ".Application"):
|
|
176
|
+
pass
|
|
177
|
+
apps.append(prog_id)
|
|
178
|
+
except (FileNotFoundError, pywintypes.com_error):
|
|
179
|
+
continue
|
|
180
|
+
except Exception as e:
|
|
181
|
+
print(f"[DEBUG] Error verifying {prog_id}: {str(e)}")
|
|
182
|
+
return apps
|
|
183
|
+
|
|
184
|
+
def IsAppAvailable(self, app_name: str) -> bool:
|
|
185
|
+
try:
|
|
186
|
+
if not app_name.endswith(".Application"):
|
|
187
|
+
app_name = app_name + ".Application"
|
|
188
|
+
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, app_name):
|
|
189
|
+
pass
|
|
190
|
+
return True
|
|
191
|
+
except Exception:
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
def Demonstrate(self) -> str:
|
|
195
|
+
if not self.DemonstrateExcel():
|
|
196
|
+
return "There's no Excel installed or something wrong"
|
|
197
|
+
if not self.DemonstratePowerPoint():
|
|
198
|
+
return "There's no PowerPoint installed or something wrong"
|
|
199
|
+
return "Demo succeeded"
|
|
200
|
+
|
|
201
|
+
def DemonstrateExcel(self) -> bool:
|
|
202
|
+
excel = self.Excel
|
|
203
|
+
if excel is None:
|
|
204
|
+
return False
|
|
205
|
+
book = excel.Workbooks.Add()
|
|
206
|
+
sheet = excel.ActiveSheet
|
|
207
|
+
excel.Visible = True
|
|
208
|
+
sheet.Cells(1, 1).Value = "Hello, World From Office Assistant!"
|
|
209
|
+
sheet.Cells(1, 1).Font.Size = 20
|
|
210
|
+
sheet.Cells(1, 1).Font.Bold = True
|
|
211
|
+
sheet.Cells(2, 1).Value = "This is a demonstration of the Office Assistant server."
|
|
212
|
+
sheet.Cells(3, 1).Value = "You can use run_python tool to control all Microsoft Office Applications."
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
def DemonstratePowerPoint(self) -> bool:
|
|
216
|
+
ppt = self.Application("PowerPoint")
|
|
217
|
+
if ppt is None:
|
|
218
|
+
return False
|
|
219
|
+
ppt.Visible = True
|
|
220
|
+
presentation = ppt.Presentations.Add()
|
|
221
|
+
slide = presentation.Slides.Add(1, 12)
|
|
222
|
+
shape = slide.Shapes.AddTextbox(1, 100, 20, 800, 100)
|
|
223
|
+
shape.TextFrame.TextRange.Text = "Hello, World From Office Assistant!"
|
|
224
|
+
shape.TextFrame.TextRange.Font.Bold = True
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
def FilePath(self, file_name: str = None, subfolder: str = None) -> str:
|
|
228
|
+
if subfolder is None:
|
|
229
|
+
file_path = os.path.join(self.RootFolder, file_name)
|
|
230
|
+
else:
|
|
231
|
+
file_path = os.path.join(self.RootFolder, subfolder, file_name)
|
|
232
|
+
return file_path
|
|
233
|
+
|
|
234
|
+
def ScreenShot(self, save_path: str = None) -> str:
|
|
235
|
+
import win32gui
|
|
236
|
+
import win32ui
|
|
237
|
+
import win32con
|
|
238
|
+
import win32api
|
|
239
|
+
from PIL import Image
|
|
240
|
+
import datetime
|
|
241
|
+
hdesktop = win32gui.GetDesktopWindow()
|
|
242
|
+
width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
|
|
243
|
+
height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
|
|
244
|
+
left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
|
|
245
|
+
top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
|
|
246
|
+
desktop_dc = win32gui.GetWindowDC(hdesktop)
|
|
247
|
+
img_dc = win32ui.CreateDCFromHandle(desktop_dc)
|
|
248
|
+
mem_dc = img_dc.CreateCompatibleDC()
|
|
249
|
+
screenshot = win32ui.CreateBitmap()
|
|
250
|
+
screenshot.CreateCompatibleBitmap(img_dc, width, height)
|
|
251
|
+
mem_dc.SelectObject(screenshot)
|
|
252
|
+
mem_dc.BitBlt((0, 0), (width, height), img_dc, (left, top), win32con.SRCCOPY)
|
|
253
|
+
bmpinfo = screenshot.GetInfo()
|
|
254
|
+
bmpstr = screenshot.GetBitmapBits(True)
|
|
255
|
+
img = Image.frombuffer(
|
|
256
|
+
'RGB',
|
|
257
|
+
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
|
|
258
|
+
bmpstr, 'raw', 'BGRX', 0, 1)
|
|
259
|
+
if save_path is None:
|
|
260
|
+
now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
261
|
+
save_path = self.FilePath(f"screenshot_{now}.png")
|
|
262
|
+
else:
|
|
263
|
+
save_path = self.FilePath(save_path)
|
|
264
|
+
img.save(save_path)
|
|
265
|
+
mem_dc.DeleteDC()
|
|
266
|
+
win32gui.DeleteObject(screenshot.GetHandle())
|
|
267
|
+
img_dc.DeleteDC()
|
|
268
|
+
win32gui.ReleaseDC(hdesktop, desktop_dc)
|
|
269
|
+
return save_path
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def Kwps(self):
|
|
273
|
+
try:
|
|
274
|
+
name = self._kwps.Name
|
|
275
|
+
return self._kwps
|
|
276
|
+
except Exception:
|
|
277
|
+
self._kwps = self.Application("Kwps", False)
|
|
278
|
+
return self._kwps
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def Ket(self):
|
|
282
|
+
try:
|
|
283
|
+
name = self._ket.Name
|
|
284
|
+
return self._ket
|
|
285
|
+
except Exception:
|
|
286
|
+
self._ket = self.Application("Ket", False)
|
|
287
|
+
return self._ket
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def Kwpp(self):
|
|
291
|
+
try:
|
|
292
|
+
name = self._kwpp.Name
|
|
293
|
+
return self._kwpp
|
|
294
|
+
except Exception:
|
|
295
|
+
self._kwpp = self.Application("Kwpp", False)
|
|
296
|
+
return self._kwpp
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def Excel(self):
|
|
300
|
+
try:
|
|
301
|
+
name = self._excel.Name
|
|
302
|
+
return self._excel
|
|
303
|
+
except Exception:
|
|
304
|
+
self._excel = self.Application("Excel", False)
|
|
305
|
+
return self._excel
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def Word(self):
|
|
309
|
+
try:
|
|
310
|
+
name = self._word.Name
|
|
311
|
+
return self._word
|
|
312
|
+
except Exception:
|
|
313
|
+
self._word = self.Application("Word", False)
|
|
314
|
+
return self._word
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def PowerPoint(self):
|
|
318
|
+
try:
|
|
319
|
+
name = self._powerpoint.Name
|
|
320
|
+
return self._powerpoint
|
|
321
|
+
except Exception:
|
|
322
|
+
self._powerpoint = self.Application("PowerPoint", False)
|
|
323
|
+
return self._powerpoint
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def Visio(self):
|
|
327
|
+
try:
|
|
328
|
+
name = self._visio.Name
|
|
329
|
+
return self._visio
|
|
330
|
+
except Exception:
|
|
331
|
+
self._visio = self.Application("Visio", False)
|
|
332
|
+
return self._visio
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def Access(self):
|
|
336
|
+
try:
|
|
337
|
+
name = self._access.Name
|
|
338
|
+
return self._access
|
|
339
|
+
except Exception:
|
|
340
|
+
self._access = self.Application("Access", False)
|
|
341
|
+
return self._access
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def Project(self):
|
|
345
|
+
try:
|
|
346
|
+
name = self._project.Name
|
|
347
|
+
return self._project
|
|
348
|
+
except Exception:
|
|
349
|
+
self._project = self.Application("MSProject", False)
|
|
350
|
+
return self._project
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def Outlook(self):
|
|
354
|
+
try:
|
|
355
|
+
name = self._outlook.Name
|
|
356
|
+
return self._outlook
|
|
357
|
+
except Exception:
|
|
358
|
+
self._outlook = self.Application("Outlook", False)
|
|
359
|
+
return self._outlook
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def Publisher(self):
|
|
363
|
+
try:
|
|
364
|
+
name = self._publisher.Name
|
|
365
|
+
return self._publisher
|
|
366
|
+
except Exception:
|
|
367
|
+
self._publisher = self.Application("Publisher", False)
|
|
368
|
+
return self._publisher
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def OneNote(self):
|
|
372
|
+
try:
|
|
373
|
+
name = self._onenote.Name
|
|
374
|
+
return self._onenote
|
|
375
|
+
except Exception:
|
|
376
|
+
self._onenote = self.Application("OneNote", False)
|
|
377
|
+
return self._onenote
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class OfficerData:
|
|
381
|
+
def init(self):
|
|
382
|
+
current_attrs = set(vars(self).keys())
|
|
383
|
+
for attr in current_attrs - self._initial_attrs:
|
|
384
|
+
delattr(self, attr)
|
|
385
|
+
self.data = {}
|
|
386
|
+
self.output = ""
|
|
387
|
+
self.helloworld = "Hello World"
|
|
388
|
+
|
|
389
|
+
def __init__(self):
|
|
390
|
+
self._initial_attrs = set(vars(self).keys())
|
|
391
|
+
self.init()
|
package/src/server.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Office Assistant MCP Server
|
|
4
|
+
===========================
|
|
5
|
+
Minimalist design: RunPython as the primary tool.
|
|
6
|
+
Smart Office Workspace on Desktop for data safety.
|
|
7
|
+
|
|
8
|
+
Disclaimer: Please place files in "Desktop/智能办公区/数据" before processing.
|
|
9
|
+
We are not responsible for data loss when operating on original file locations.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
from license_manager import verify_license
|
|
16
|
+
verify_license()
|
|
17
|
+
|
|
18
|
+
from fastmcp import FastMCP
|
|
19
|
+
from officer import TheOfficer
|
|
20
|
+
from utils import get_workspace, get_data_dir, get_export_dir, get_download_dir
|
|
21
|
+
|
|
22
|
+
mcp = FastMCP("Office Assistant")
|
|
23
|
+
Officer = TheOfficer()
|
|
24
|
+
|
|
25
|
+
APP_MAP = {
|
|
26
|
+
'xlsx': 'Excel', 'xls': 'Excel', 'csv': 'Excel',
|
|
27
|
+
'docx': 'Word', 'doc': 'Word',
|
|
28
|
+
'pptx': 'PowerPoint', 'ppt': 'PowerPoint',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_active_doc(app, app_name):
|
|
33
|
+
try:
|
|
34
|
+
if app_name.lower() in ['excel', 'ket']:
|
|
35
|
+
return app.ActiveWorkbook
|
|
36
|
+
elif app_name.lower() in ['word', 'kwps']:
|
|
37
|
+
return app.ActiveDocument
|
|
38
|
+
elif app_name.lower() in ['powerpoint', 'kwpp']:
|
|
39
|
+
return app.ActivePresentation
|
|
40
|
+
except:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _build_globals(app, doc, app_name):
|
|
45
|
+
base = {
|
|
46
|
+
'app': app,
|
|
47
|
+
'doc': doc,
|
|
48
|
+
'Officer': Officer,
|
|
49
|
+
'__builtins__': __builtins__,
|
|
50
|
+
}
|
|
51
|
+
if app_name.lower() in ['excel', 'ket']:
|
|
52
|
+
base['sheet'] = doc.ActiveSheet if doc else None
|
|
53
|
+
elif app_name.lower() in ['word', 'kwps']:
|
|
54
|
+
base['selection'] = app.Selection
|
|
55
|
+
elif app_name.lower() in ['powerpoint', 'kwpp']:
|
|
56
|
+
base['slide'] = doc.Slides(doc.Slides.Count) if doc else None
|
|
57
|
+
return base
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ========== Application Control Tools ==========
|
|
61
|
+
|
|
62
|
+
@mcp.tool()
|
|
63
|
+
def available_apps() -> list:
|
|
64
|
+
"""List installed Microsoft Office applications."""
|
|
65
|
+
return Officer.AvailableApps()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
def launch_app(app_name: str = "Excel", visible: bool = True) -> bool:
|
|
70
|
+
"""Launch a Microsoft Office application."""
|
|
71
|
+
try:
|
|
72
|
+
app = Officer.Application(app_name)
|
|
73
|
+
app.Visible = visible
|
|
74
|
+
return True
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"Launch failed: {e}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@mcp.tool()
|
|
81
|
+
def quit_app(app_name: str = "Excel", force: bool = False) -> bool:
|
|
82
|
+
"""Quit a Microsoft Office application."""
|
|
83
|
+
return Officer.Quit(app_name, force)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ========== File Operation Tools ==========
|
|
87
|
+
|
|
88
|
+
@mcp.tool()
|
|
89
|
+
def open_file(file_path: str) -> dict:
|
|
90
|
+
"""
|
|
91
|
+
Open an Office file.
|
|
92
|
+
|
|
93
|
+
Supported formats:
|
|
94
|
+
- Excel: .xlsx, .xls, .csv
|
|
95
|
+
- Word: .docx, .doc
|
|
96
|
+
- PowerPoint: .pptx, .ppt
|
|
97
|
+
|
|
98
|
+
Tip: Place files in "Desktop/智能办公区/数据" for safe processing.
|
|
99
|
+
"""
|
|
100
|
+
ext = file_path.split('.')[-1].lower()
|
|
101
|
+
app_name = APP_MAP.get(ext, 'Excel')
|
|
102
|
+
app = Officer.Application(app_name)
|
|
103
|
+
|
|
104
|
+
if app_name == 'Excel':
|
|
105
|
+
doc = app.Workbooks.Open(file_path)
|
|
106
|
+
elif app_name == 'Word':
|
|
107
|
+
doc = app.Documents.Open(file_path)
|
|
108
|
+
elif app_name == 'PowerPoint':
|
|
109
|
+
doc = app.Presentations.Open(file_path)
|
|
110
|
+
|
|
111
|
+
app.Visible = True
|
|
112
|
+
return {"success": True, "app": app_name, "file": file_path}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@mcp.tool()
|
|
116
|
+
def save_file(file_path: str = None) -> dict:
|
|
117
|
+
"""Save the current active document."""
|
|
118
|
+
for app_name in ['Excel', 'Word', 'PowerPoint']:
|
|
119
|
+
try:
|
|
120
|
+
app = Officer.Application(app_name)
|
|
121
|
+
if app_name == 'Excel':
|
|
122
|
+
doc = app.ActiveWorkbook
|
|
123
|
+
elif app_name == 'Word':
|
|
124
|
+
doc = app.ActiveDocument
|
|
125
|
+
else:
|
|
126
|
+
doc = app.ActivePresentation
|
|
127
|
+
|
|
128
|
+
if doc:
|
|
129
|
+
if file_path:
|
|
130
|
+
doc.SaveAs(file_path)
|
|
131
|
+
else:
|
|
132
|
+
doc.Save()
|
|
133
|
+
return {"success": True, "file": doc.FullName}
|
|
134
|
+
except:
|
|
135
|
+
continue
|
|
136
|
+
return {"success": False, "error": "No active document found"}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ========== Core Tool: RunPython ==========
|
|
140
|
+
|
|
141
|
+
@mcp.tool()
|
|
142
|
+
def run_python(code: str, app_name: str = "Excel") -> dict:
|
|
143
|
+
"""
|
|
144
|
+
Execute Python code to control Office applications (PRIMARY TOOL).
|
|
145
|
+
|
|
146
|
+
Available global variables:
|
|
147
|
+
- app: The Office application object
|
|
148
|
+
- doc: The current active document
|
|
149
|
+
- sheet: Active worksheet (Excel)
|
|
150
|
+
- selection: Current selection (Word)
|
|
151
|
+
- slide: Active slide (PowerPoint)
|
|
152
|
+
- Officer: TheOfficer instance for accessing other apps
|
|
153
|
+
|
|
154
|
+
Excel example:
|
|
155
|
+
sheet.Cells(1, 1).Value = "Hello"
|
|
156
|
+
doc.Save()
|
|
157
|
+
|
|
158
|
+
Word example:
|
|
159
|
+
doc.Content.Text = "Hello World"
|
|
160
|
+
doc.Save()
|
|
161
|
+
|
|
162
|
+
PowerPoint example:
|
|
163
|
+
slide.Shapes.AddTextbox(1, 100, 100, 400, 50).TextFrame.TextRange.Text = "Title"
|
|
164
|
+
"""
|
|
165
|
+
app = Officer.Application(app_name)
|
|
166
|
+
doc = _get_active_doc(app, app_name)
|
|
167
|
+
globals_dict = _build_globals(app, doc, app_name)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
exec(code, globals_dict)
|
|
171
|
+
return {"success": True}
|
|
172
|
+
except Exception as e:
|
|
173
|
+
return {"success": False, "error": str(e)}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ========== System Tools ==========
|
|
177
|
+
|
|
178
|
+
@mcp.tool()
|
|
179
|
+
def screenshot(save_name: str = None) -> str:
|
|
180
|
+
"""Capture a screenshot and save to the export folder."""
|
|
181
|
+
path = Officer.ScreenShot(save_name)
|
|
182
|
+
return path
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@mcp.tool()
|
|
186
|
+
def download(url: str, save_name: str = None) -> str:
|
|
187
|
+
"""Download a file from URL to the download folder."""
|
|
188
|
+
return Officer.DownloadURL(url, save_name)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@mcp.tool()
|
|
192
|
+
def speak(text: str = "Hello", volume: int = 80, rate: int = 0) -> bool:
|
|
193
|
+
"""Text-to-speech. Volume: 0-100, Rate: -10 to 10."""
|
|
194
|
+
return Officer.Speak(text, volume, rate)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@mcp.tool()
|
|
198
|
+
def demonstrate() -> dict:
|
|
199
|
+
"""Run a demonstration of Office automation capabilities."""
|
|
200
|
+
try:
|
|
201
|
+
result = Officer.Demonstrate()
|
|
202
|
+
return {"success": True, "output": result}
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return {"success": False, "error": str(e)}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@mcp.resource("resource://instructions")
|
|
208
|
+
def get_instructions() -> str:
|
|
209
|
+
"""Get usage instructions and disclaimer."""
|
|
210
|
+
workspace = get_workspace()
|
|
211
|
+
return f"""
|
|
212
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
213
|
+
║ Office Assistant - 使用指南 ║
|
|
214
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
215
|
+
|
|
216
|
+
【免责声明】
|
|
217
|
+
请将需要处理的文件复制到「桌面/智能办公区/数据」文件夹后再操作。
|
|
218
|
+
直接在原文件位置操作导致的数据丢失,本软件概不负责。
|
|
219
|
+
|
|
220
|
+
工作目录: {workspace}
|
|
221
|
+
- 数据/: 放置源文件
|
|
222
|
+
- 导出/: 截图、PDF等导出文件
|
|
223
|
+
- 下载/: 下载的文件
|
|
224
|
+
|
|
225
|
+
【主力工具: run_python】
|
|
226
|
+
执行 Python 代码控制 Office 应用。
|
|
227
|
+
|
|
228
|
+
可用变量:
|
|
229
|
+
- app: Office 应用对象
|
|
230
|
+
- doc: 当前文档
|
|
231
|
+
- sheet: 当前工作表 (Excel)
|
|
232
|
+
- selection: 当前选区 (Word)
|
|
233
|
+
- slide: 当前幻灯片 (PowerPoint)
|
|
234
|
+
- Officer: 办公助手 (Officer.Excel, Officer.Word 等)
|
|
235
|
+
|
|
236
|
+
【Excel 示例】
|
|
237
|
+
sheet.Cells(1, 1).Value = "Hello"
|
|
238
|
+
sheet.Cells(1, 1).Font.Bold = True
|
|
239
|
+
doc.Save()
|
|
240
|
+
|
|
241
|
+
【Word 示例】
|
|
242
|
+
doc.Content.Text = "Hello World"
|
|
243
|
+
doc.Save()
|
|
244
|
+
|
|
245
|
+
【PowerPoint 示例】
|
|
246
|
+
slide.Shapes.AddTextbox(1, 100, 100, 400, 50).TextFrame.TextRange.Text = "Title"
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ========== Entry Point ==========
|
|
251
|
+
|
|
252
|
+
def run():
|
|
253
|
+
args = sys.argv[1:]
|
|
254
|
+
transport = args[0].lower() if args else "stdio"
|
|
255
|
+
|
|
256
|
+
if transport == "stdio":
|
|
257
|
+
print("Office Assistant started (stdio mode)")
|
|
258
|
+
mcp.run("stdio")
|
|
259
|
+
else:
|
|
260
|
+
host = "127.0.0.1"
|
|
261
|
+
port = 8888
|
|
262
|
+
print(f"Office Assistant started (SSE mode: http://{host}:{port}/sse)")
|
|
263
|
+
mcp.run(transport="sse", host=host, port=port)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
run()
|
package/src/utils.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_workspace():
|
|
5
|
+
"""Get or create the smart office workspace on Desktop"""
|
|
6
|
+
desktop = Path.home() / "Desktop"
|
|
7
|
+
workspace = desktop / "智能办公区"
|
|
8
|
+
workspace.mkdir(exist_ok=True)
|
|
9
|
+
(workspace / "数据").mkdir(exist_ok=True)
|
|
10
|
+
(workspace / "导出").mkdir(exist_ok=True)
|
|
11
|
+
(workspace / "下载").mkdir(exist_ok=True)
|
|
12
|
+
return workspace
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_data_dir():
|
|
16
|
+
return get_workspace() / "数据"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_export_dir():
|
|
20
|
+
return get_workspace() / "导出"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_download_dir():
|
|
24
|
+
return get_workspace() / "下载"
|