feat: oo_bridge_send — cliente registry del puente OnlyOffice (grupo onlyoffice)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""Programmatic client for the OnlyOffice live bridge (apps/onlyoffice_bridge).
|
||||
|
||||
Speaks to the loopback server (server.py, 127.0.0.1:8791) that queues commands
|
||||
for a system plugin running inside OnlyOffice Desktop Editors. The plugin runs
|
||||
each command with the Automation API (Api.*) over the focused document and
|
||||
answers. This function pushes one command, polls for its result and returns a
|
||||
non-throwing dict — it never raises, so callers (pipelines, heredocs, other
|
||||
functions) can compose it without try/except.
|
||||
|
||||
Only stdlib is used (urllib.request, json, time) so the function is importable
|
||||
from any registry consumer without extra dependencies.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
# Monotonic per-process counter combined with a millisecond timestamp keeps the
|
||||
# id unique across quick successive calls without pulling in uuid/random.
|
||||
_counter = [0]
|
||||
|
||||
|
||||
def _next_id() -> str:
|
||||
_counter[0] += 1
|
||||
return "oo%d_%d" % (int(time.time() * 1000), _counter[0])
|
||||
|
||||
|
||||
def oo_bridge_send(
|
||||
cmd: str,
|
||||
ref: str = "",
|
||||
text: str = "",
|
||||
opts: dict = None,
|
||||
target: str = "cell",
|
||||
port: int = 8791,
|
||||
wait_s: float = 6.0,
|
||||
) -> dict:
|
||||
"""Send one command to the OnlyOffice live bridge and return its result.
|
||||
|
||||
Pushes ``{cmd, ref, text, opts, id, target}`` to ``POST /push`` on the
|
||||
loopback bridge server, then long-polls ``GET /pull?id=<id>`` until the
|
||||
plugin answers or ``wait_s`` elapses. The plugin's answer is a JSON string
|
||||
such as ``{"result": ...}`` or ``{"error": "..."}``.
|
||||
|
||||
Args:
|
||||
cmd: Automation-API command name. Cell reads: get_cell, get_range,
|
||||
get_formula, get_used_range, get_sheets. Cell writes: set_cell,
|
||||
set_range, set_formula. Structure: insert_rows, delete_rows,
|
||||
insert_cols, delete_cols, merge, add_sheet, rename_sheet,
|
||||
delete_sheet, activate_sheet. Format: set_format, set_borders,
|
||||
set_colwidth, set_rowheight. Data: add_named_range, sort,
|
||||
autofilter. Charts/tables: add_chart, format_as_table,
|
||||
table_chart. Word: insert_text, get_text.
|
||||
ref: A1 reference or range the command acts on ("B2", "A1:C10",
|
||||
"4:6" for rows, "B:C" for cols). Empty for commands with no ref.
|
||||
text: Text value for write commands (cell value, formula "=SUM(...)",
|
||||
sheet name, inserted paragraph). Empty for reads.
|
||||
opts: Extra parameters as a dict, per command. Common keys: values
|
||||
(2D matrix for set_range), sheet (target sheet by name for any
|
||||
cell command), bold/italic/underline/fill=[r,g,b]/
|
||||
fontColor=[r,g,b]/numFormat/fontName/fontSize/wrap/alignH/alignV
|
||||
(set_format), style (set_borders), width (set_colwidth), height
|
||||
(set_rowheight), by/desc (sort), type/dataRange/pos (add_chart),
|
||||
dataRange/type (table_chart). None is treated as {}.
|
||||
target: Destination editor: "cell" (Spreadsheet), "word" (Document),
|
||||
"slide" (Presentation). Default "cell".
|
||||
port: Loopback bridge server port. Default 8791.
|
||||
wait_s: Seconds to poll /pull before giving up with a timeout error.
|
||||
Default 6.0.
|
||||
|
||||
Returns:
|
||||
A non-throwing dict. On success: ``{"status": "ok", "result": <value>}``
|
||||
where ``<value>`` is whatever the plugin returned (a cell string, a 2D
|
||||
matrix for get_range, a list of sheet names, or "ok" for writes). On
|
||||
failure: ``{"status": "error", "error": <message>}`` when the plugin
|
||||
replied with an error, when the poll timed out (editor not focused or
|
||||
plugin not started), or on any network failure (server not running).
|
||||
Never raises.
|
||||
"""
|
||||
base = "http://127.0.0.1:%d" % port
|
||||
rid = _next_id()
|
||||
payload = {
|
||||
"cmd": cmd,
|
||||
"ref": ref,
|
||||
"text": text,
|
||||
"opts": opts if opts is not None else {},
|
||||
"id": rid,
|
||||
"target": target,
|
||||
}
|
||||
net_timeout = max(wait_s, 2.0)
|
||||
|
||||
# 1) Enqueue the command on the bridge server.
|
||||
try:
|
||||
push_body = json.dumps(payload).encode("utf-8")
|
||||
push_req = urllib.request.Request(
|
||||
base + "/push",
|
||||
data=push_body,
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(push_req, timeout=net_timeout) as resp:
|
||||
resp.read()
|
||||
except Exception as exc: # noqa: BLE001 — no-throw contract
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "push failed: %s (is server.py running on port %d?)"
|
||||
% (exc, port),
|
||||
}
|
||||
|
||||
# 2) Poll for the plugin's answer until it is ready or wait_s elapses.
|
||||
deadline = time.time() + wait_s
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
pull_req = urllib.request.Request(
|
||||
base + "/pull?id=" + rid, method="GET"
|
||||
)
|
||||
with urllib.request.urlopen(pull_req, timeout=net_timeout) as resp:
|
||||
res = json.loads(resp.read().decode("utf-8"))
|
||||
except Exception as exc: # noqa: BLE001 — no-throw contract
|
||||
return {"status": "error", "error": "pull failed: %s" % exc}
|
||||
|
||||
if res.get("ready"):
|
||||
raw = res.get("text", "")
|
||||
if not raw:
|
||||
return {"status": "ok", "result": None}
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except (ValueError, TypeError):
|
||||
# Plugin returned non-JSON text; hand it back raw.
|
||||
return {"status": "ok", "result": raw}
|
||||
if isinstance(parsed, dict) and "error" in parsed:
|
||||
return {"status": "error", "error": parsed["error"]}
|
||||
if isinstance(parsed, dict) and "result" in parsed:
|
||||
return {"status": "ok", "result": parsed["result"]}
|
||||
return {"status": "ok", "result": parsed}
|
||||
|
||||
time.sleep(0.4)
|
||||
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "timeout: no reply in %.1fs "
|
||||
"(editor not focused or plugin not started)" % wait_s,
|
||||
}
|
||||
Reference in New Issue
Block a user