feat(microsoft-graph-files): O365 file management integration (Graph API)

Microsoft Graph file management for OneDrive / SharePoint / Teams with app-only
(client credentials) authentication, stateless over urllib (no dependency). 19
commands: browse sites/drives/content, create folders, delete/upload/replace/
download files (content passed via base64 or source URL; download returns the
pre-authenticated Graph URL), site permission management (list/create/update/
delete), SharePoint list reading (lists/items/get-item), and Excel worksheet
editing (append row, read range, update cell).

Each script obtains a bearer token via the client-credentials grant and calls
Graph directly. Re-implemented cleanly from a customized source: dropped the
hosted-proxy/auth-code/managed-identity/certificate flows, the duplicated and
broken Excel helpers, and the platform-specific file-entry handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guillaume BOURGEOIS
2026-06-26 18:13:43 +02:00
parent b98d315fe4
commit 3608cbb74e
20 changed files with 3453 additions and 0 deletions
@@ -0,0 +1,164 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
parent = drive_item_base(I["object_type"], I["object_type_id"], I["parent_id"])
body = {"name": I["folder_name"], "folder": {}, "@microsoft.graph.conflictBehavior": "rename"}
out(graph("POST", parent + "/children", json_body=body))
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,167 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
site_id = resolve_site_id()
body = {
"roles": _list(I["role"]),
"grantedToIdentities": [{"application": {"id": I["app_id"], "displayName": I["display_name"]}}],
}
out(graph("POST", "/sites/" + site_id + "/permissions", json_body=body))
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,164 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
uri = drive_item_base(I["object_type"], I["object_type_id"], I["item_id"])
graph("DELETE", uri, resp="response")
out({"deleted": I["item_id"]})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,165 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
site_id = resolve_site_id()
uri = "/sites/" + site_id + "/permissions/" + I["permission_id"]
graph("DELETE", uri, resp="response")
out({"deleted": I["permission_id"], "site_id": site_id})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,168 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
base = drive_item_base(I["object_type"], I["object_type_id"], I["item_id"])
meta = graph("GET", base)
url = meta.get("@microsoft.graph.downloadUrl")
result = {"id": meta.get("id"), "name": meta.get("name"), "size": meta.get("size"), "download_url": url}
if _bool(I.get("as_base64")) and url:
result["content_base64"] = base64.b64encode(fetch_url(url)).decode("ascii")
out(result)
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,179 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
data = I["data"]
if isinstance(data, str):
data = json.loads(data)
if not data:
fail("data is empty")
return
base = worksheet_base(I["object_type"] or "sites", I["object_type_id"], I["item_id"], I["worksheet_name"])
used = graph("GET", base + "/usedRange", params={"$select": "rowCount"})
row_count = int(used.get("rowCount", 0))
start_row = int(I.get("start_row") or 1)
target_row = max(start_row, row_count + 1)
max_col = max(int(k) for k in data)
row_values = [""] * max_col
for k, v in data.items():
row_values[int(k) - 1] = v
addr = "A" + str(target_row) + ":" + col_letter(max_col) + str(target_row)
res = graph("PATCH", base + "/range(address='" + addr + "')", json_body={"values": [row_values]})
out({"address": res.get("address", addr), "row": target_row, "values": row_values})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,165 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
base = worksheet_base(I.get("object_type") or "sites", I["object_type_id"], I["item_id"], I["worksheet_name"])
range_addr = I.get("range") or "A1:Z500"
res = graph("GET", base + "/range(address='" + range_addr + "')")
out({"address": res.get("address"), "values": res.get("text") or res.get("values")})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,170 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
match = re.search(r"\d+", str(I["column"]))
if not match:
fail("Invalid column: " + str(I["column"]))
return
col_num = int(match.group())
addr = col_letter(col_num) + str(int(I["row_index"]))
base = worksheet_base(I.get("object_type") or "sites", I["object_type_id"], I["item_id"], I["worksheet_name"])
graph("PATCH", base + "/range(address='" + addr + "')", json_body={"values": [[I["value"]]]})
out({"cell": addr, "value": I["value"]})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,165 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
site_id = resolve_site_id()
list_id = resolve_list_id(site_id)
uri = "/sites/" + site_id + "/lists/" + list_id + "/items/" + I["item_id"]
out(graph("GET", uri, params={"$expand": "fields"}))
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,168 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
if I.get("next_page_url"):
res = graph("GET", I["next_page_url"], params={"$top": I.get("limit")})
else:
item = I.get("item_id") or "root"
uri = drive_item_base(I["object_type"], I["object_type_id"], item) + "/children"
res = graph("GET", uri, params={"$top": I.get("limit")})
out({"children": res.get("value", []), "next": res.get("@odata.nextLink")})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,170 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
params = {"$top": I.get("limit")}
if I.get("next_page_url"):
res = graph("GET", I["next_page_url"], params=params)
elif I.get("site_id"):
res = graph("GET", "/sites/" + I["site_id"] + "/drives", params=params)
else:
fail("Provide site_id or next_page_url")
return
out({"drives": res.get("value", []), "next": res.get("@odata.nextLink")})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,170 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
site_id = resolve_site_id()
list_id = resolve_list_id(site_id)
expand = "fields($select=" + I["fields_select"] + ")" if I.get("fields_select") else "fields"
params = {"$top": I.get("limit"), "$filter": I.get("filter"), "$orderby": I.get("orderby"), "$expand": expand}
if I.get("next_page_url"):
res = graph("GET", I["next_page_url"], params={"$top": I.get("limit")})
else:
res = graph("GET", "/sites/" + site_id + "/lists/" + list_id + "/items", params=params)
out({"items": res.get("value", []), "next": res.get("@odata.nextLink")})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,167 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
site_id = resolve_site_id()
if I.get("next_page_url"):
res = graph("GET", I["next_page_url"], params={"$top": I.get("limit")})
else:
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": I.get("limit")})
out({"lists": res.get("value", []), "next": res.get("@odata.nextLink")})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,163 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
res = graph("GET", "/sites", params={"search": I.get("keyword") or "*"})
out({"sites": res.get("value", []), "next": res.get("@odata.nextLink")})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,166 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
site_id = resolve_site_id()
pid = I.get("permission_id")
uri = "/sites/" + site_id + "/permissions" + ("/" + pid if pid else "")
res = graph("GET", uri)
out(res if pid else {"permissions": res.get("value", [])})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,164 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
content = input_bytes()
uri = drive_item_base(I["object_type"], I["object_type_id"], I["item_id"]) + "/content"
out(graph("PUT", uri, data=content))
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,162 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
graph("GET", "/sites", params={"search": "*", "$top": 1})
out({"ok": True})
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,164 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
site_id = resolve_site_id()
uri = "/sites/" + site_id + "/permissions/" + I["permission_id"]
out(graph("PATCH", uri, json_body={"roles": _list(I["role"])}))
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))
@@ -0,0 +1,166 @@
import json, os, re, ssl, sys, base64, urllib.parse, urllib.request, urllib.error
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
GRAPH = (S.get("host") or "https://graph.microsoft.com").rstrip("/")
LOGIN = (S.get("login_url") or "https://login.microsoftonline.com").rstrip("/")
def out(value):
print(json.dumps(value, default=str))
def fail(message, **extra):
payload = {"error": message}
payload.update(extra)
print(json.dumps(payload, default=str))
sys.exit(1)
def _bool(v):
return str(v).lower() in ("1", "true", "yes")
def _list(v):
if isinstance(v, list):
return v
return [x.strip() for x in str(v).split(",") if x.strip()]
def _ctx():
return ssl._create_unverified_context() if _bool(S.get("insecure")) else ssl.create_default_context()
_TOKEN = None
def token():
global _TOKEN
if _TOKEN is not None:
return _TOKEN
data = urllib.parse.urlencode({
"client_id": S.get("client_id", ""),
"client_secret": S.get("client_secret", ""),
"grant_type": "client_credentials",
"scope": GRAPH + "/.default",
}).encode("utf-8")
url = LOGIN + "/" + urllib.parse.quote(S.get("tenant_id", "")) + "/oauth2/v2.0/token"
req = urllib.request.Request(url, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=60, context=_ctx()) as r:
_TOKEN = json.loads(r.read())["access_token"]
except urllib.error.HTTPError as e:
raise Exception("Authentication failed: HTTP " + str(e.code) + " " + e.read().decode("utf-8", "replace"))
return _TOKEN
def graph(method, path, params=None, json_body=None, data=None, headers=None, resp="json"):
url = path if path.startswith("http") else (GRAPH + "/v1.0" + path)
if params:
clean = {k: v for k, v in params.items() if v not in (None, "")}
if clean:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean)
h = {"Authorization": "Bearer " + token(), "Accept": "application/json"}
body = None
if json_body is not None:
body = json.dumps(json_body).encode("utf-8")
h["Content-Type"] = "application/json"
elif data is not None:
body = data
h["Content-Type"] = "application/octet-stream"
if headers:
h.update(headers)
req = urllib.request.Request(url, data=body, headers=h, method=method)
try:
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
raw = r.read()
if resp == "response":
return {"status": r.status}
if resp == "content":
return raw
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raise Exception("Graph HTTP " + str(e.code) + ": " + e.read().decode("utf-8", "replace"))
def fetch_url(url):
"""Fetch raw bytes from an arbitrary URL (no Graph auth header)."""
req = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(req, timeout=180, context=_ctx()) as r:
return r.read()
def input_bytes():
"""Resolve upload content from content_base64 or source_url."""
if I.get("content_base64"):
return base64.b64decode(I["content_base64"])
if I.get("source_url"):
return fetch_url(I["source_url"])
fail("Provide either content_base64 or source_url")
def col_letter(n):
s = ""
n = int(n)
while n > 0:
n, rem = divmod(n - 1, 26)
s = chr(65 + rem) + s
return s
def resolve_site_id():
if I.get("site_id"):
return I["site_id"]
name = I.get("site_name")
if not name:
fail("Provide site_id or site_name")
res = graph("GET", "/sites", params={"search": name})
value = res.get("value", [])
if not value:
fail("Site not found: " + name)
return value[0]["id"]
def resolve_list_id(site_id):
if I.get("list_id"):
return I["list_id"]
name = I.get("list_name")
if not name:
fail("Provide list_id or list_name")
res = graph("GET", "/sites/" + site_id + "/lists", params={"$top": 999})
for lst in res.get("value", []):
if (lst.get("displayName") or lst.get("name") or "").lower() == name.lower():
return lst["id"]
fail("List not found: " + name)
def drive_item_base(object_type, object_type_id, item_id):
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id
def worksheet_base(object_type, object_type_id, item_id, worksheet):
ws = urllib.parse.quote(worksheet)
if object_type == "drives":
return "/drives/" + object_type_id + "/items/" + item_id + "/workbook/worksheets/" + ws
return "/" + object_type + "/" + object_type_id + "/drive/items/" + item_id + "/workbook/worksheets/" + ws
def run():
content = input_bytes()
name = urllib.parse.quote(I["file_name"])
base = drive_item_base(I["object_type"], I["object_type_id"], I["parent_id"])
uri = base + ":/" + name + ":/content"
out(graph("PUT", uri, data=content))
try:
run()
except SystemExit:
raise
except Exception as e:
fail(str(e))