feat(gmail): new Gmail integration

Gmail API + Directory API (27 commands): mailbox search/read/send/
trash/label/move, attachments retrieval, vacation auto-reply, filters,
forwarding addresses, delegates, and Workspace user administration;
message ingestion (get_incidents) with a bundled OCSF mapper.
Service-account auth with domain-wide delegation (JWT RS256), runs on
a remote engine (requires PyJWT + cryptography).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Guillaume BOURGEOIS
2026-07-10 23:18:07 +02:00
parent 3f754d14e9
commit 27615636e3
30 changed files with 2974 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
criteria = {}
if inputs.get("from"):
criteria["from"] = inputs.get("from")
if inputs.get("to"):
criteria["to"] = inputs.get("to")
if inputs.get("subject"):
criteria["subject"] = inputs.get("subject")
if inputs.get("query"):
criteria["query"] = inputs.get("query")
if inputs.get("has_attachment"):
criteria["hasAttachment"] = True
if not criteria:
raise Exception("no criteria given")
action = {}
add_labels = [s.strip() for s in str(inputs.get("add_labels") or "").split(",") if s.strip()]
if add_labels:
action["addLabelIds"] = add_labels
remove_labels = [s.strip() for s in str(inputs.get("remove_labels") or "").split(",") if s.strip()]
if remove_labels:
action["removeLabelIds"] = remove_labels
if inputs.get("forward"):
action["forward"] = inputs.get("forward")
result = request("POST", base + "/settings/filters", SCOPES,
body={"criteria": criteria, "action": action}, subject=mailbox)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
res = request("GET", base + "/settings/vacation", SCOPES, subject=mailbox)
print(json.dumps(res))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+112
View File
@@ -0,0 +1,112 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
from datetime import datetime
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def _bool(value, default=False):
if value in (None, ""):
return default
if isinstance(value, str):
return value.strip().lower() in ("1", "true", "yes", "y")
return bool(value)
def _to_epoch_ms(value):
s = str(value).strip()
if s.isdigit():
n = int(s)
if n < 10**12:
n *= 1000
return n
return int(datetime.fromisoformat(s.replace("Z", "+00:00")).timestamp() * 1000)
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
body = {"enableAutoReply": _bool(inputs.get("enable"), default=True)}
if inputs.get("response_subject"):
body["responseSubject"] = str(inputs["response_subject"])
if inputs.get("response_body"):
body["responseBodyPlainText"] = str(inputs["response_body"])
if inputs.get("response_body_html"):
body["responseBodyHtml"] = str(inputs["response_body_html"])
if inputs.get("contacts_only") not in (None, ""):
body["restrictToContacts"] = _bool(inputs.get("contacts_only"))
if inputs.get("domain_only") not in (None, ""):
body["restrictToDomain"] = _bool(inputs.get("domain_only"))
if inputs.get("start_time") not in (None, ""):
body["startTime"] = _to_epoch_ms(inputs["start_time"])
if inputs.get("end_time") not in (None, ""):
body["endTime"] = _to_epoch_ms(inputs["end_time"])
res = request("PUT", base + "/settings/vacation", SCOPES, body=body, subject=mailbox)
print(json.dumps(res))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+97
View File
@@ -0,0 +1,97 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
email = str(inputs.get("email") or "").strip()
first_name = str(inputs.get("first_name") or "").strip()
last_name = str(inputs.get("last_name") or "").strip()
password = str(inputs.get("password") or "")
if not email:
raise Exception("email is required")
if not first_name:
raise Exception("first_name is required")
if not last_name:
raise Exception("last_name is required")
if len(password) < 8 or len(password) > 100:
raise Exception("password must be between 8 and 100 characters")
body = {
"primaryEmail": email,
"name": {
"givenName": first_name,
"familyName": last_name,
"fullName": first_name + " " + last_name,
},
"password": password,
}
result = request("POST", ADMIN_BASE + "/users", SCOPES, body=body)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
delegate_email = str(inputs.get("delegate_email") or "").strip()
if not delegate_email:
raise Exception("delegate_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("POST", base + "/settings/delegates", SCOPES,
body={"delegateEmail": delegate_email}, subject=mailbox)
if not result:
result = {"ok": True, "delegate_email": delegate_email}
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,84 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
delegate_email = str(inputs.get("delegate_email") or "").strip()
if not delegate_email:
raise Exception("delegate_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("DELETE",
base + "/settings/delegates/" + urllib.parse.quote(delegate_email, safe=""),
SCOPES, subject=mailbox)
if not result:
result = {"ok": True, "delegate_email": delegate_email}
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+93
View File
@@ -0,0 +1,93 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://mail.google.com/"]
def _bool(value):
if isinstance(value, str):
return value.strip().lower() in ("1", "true", "yes", "y")
return bool(value)
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
if _bool(inputs.get("permanent")):
request("DELETE", base + "/messages/" + urllib.parse.quote(message_id, safe=""),
SCOPES, subject=mailbox)
print(json.dumps({"ok": True, "message_id": message_id}))
else:
res = request("POST", base + "/messages/" + urllib.parse.quote(message_id, safe="") + "/trash",
SCOPES, body={}, subject=mailbox)
print(json.dumps(res))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+81
View File
@@ -0,0 +1,81 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
result = request("DELETE", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES)
if not result:
result = {"ok": True, "user_key": user_key}
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,81 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
forwarding_email = str(inputs.get("forwarding_email") or "").strip()
if not forwarding_email:
raise Exception("forwarding_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("POST", base + "/settings/forwardingAddresses", SCOPES,
body={"forwardingEmail": forwarding_email}, subject=mailbox)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("GET", base + "/settings/forwardingAddresses", SCOPES, subject=mailbox)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,84 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
forwarding_email = str(inputs.get("forwarding_email") or "").strip()
if not forwarding_email:
raise Exception("forwarding_email is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("DELETE",
base + "/settings/forwardingAddresses/" + urllib.parse.quote(forwarding_email, safe=""),
SCOPES, subject=mailbox)
if not result:
result = {"ok": True, "forwarding_email": forwarding_email}
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.sharing"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
forwarding_email = str(inputs.get("forwarding_email") or "").strip()
if not forwarding_email:
raise Exception("forwarding_email is required")
disposition = str(inputs.get("disposition") or "leaveInInbox")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("PUT", base + "/settings/autoForwarding", SCOPES,
body={"emailAddress": forwarding_email, "enabled": True, "disposition": disposition},
subject=mailbox)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,144 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
msg = request("GET", base + "/messages/" + urllib.parse.quote(message_id, safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
flat = flatten_message(msg)
attachments = []
for att in flat["attachments"]:
att_id = att.get("id", "")
if not att_id:
continue
res = request("GET",
base + "/messages/" + urllib.parse.quote(message_id, safe="")
+ "/attachments/" + urllib.parse.quote(att_id, safe=""),
SCOPES, subject=mailbox)
data = str(res.get("data") or "")
raw = base64.urlsafe_b64decode(data + "=" * (-len(data) % 4)) if data else b""
attachments.append({
"name": att.get("name", ""),
"mime_type": att.get("mime_type", ""),
"size": res.get("size", len(raw)),
"content_base64": base64.b64encode(raw).decode("ascii"),
})
print(json.dumps({"message_id": message_id, "attachments": attachments}))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+143
View File
@@ -0,0 +1,143 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
from datetime import datetime
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def _to_epoch_seconds(value):
s = str(value).strip()
if s.isdigit():
n = int(s)
if n > 10**12:
n //= 1000
return n
return int(datetime.fromisoformat(s.replace("Z", "+00:00")).timestamp())
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
q = str(inputs.get("query") or "").strip()
after = inputs.get("after")
if after not in (None, ""):
q = (q + " " if q else "") + "after:" + str(_to_epoch_seconds(after))
max_results = int(inputs.get("max") or 50)
res = request("GET", base + "/messages", SCOPES,
params={"q": q, "maxResults": max_results}, subject=mailbox)
out = []
for m in res.get("messages", []) or []:
full = request("GET", base + "/messages/" + urllib.parse.quote(str(m.get("id", "")), safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
out.append(flatten_message(full))
print(json.dumps({"result": out}))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+125
View File
@@ -0,0 +1,125 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
msg = request("GET", base + "/messages/" + urllib.parse.quote(message_id, safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
print(json.dumps(flatten_message(msg)))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+126
View File
@@ -0,0 +1,126 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
thread_id = str(inputs.get("thread_id") or "").strip()
if not thread_id:
raise Exception("thread_id is required")
thread = request("GET", base + "/threads/" + urllib.parse.quote(thread_id, safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
messages = [flatten_message(m) for m in thread.get("messages", []) or []]
print(json.dumps({"id": thread_id, "messages": messages}))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+79
View File
@@ -0,0 +1,79 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user.readonly"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
result = request("GET", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+80
View File
@@ -0,0 +1,80 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
result = request("PUT", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES,
body={"includeInGlobalAddressList": bool(inputs.get("visible"))})
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("GET", base + "/settings/filters", SCOPES, subject=mailbox)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+77
View File
@@ -0,0 +1,77 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
res = request("GET", base + "/labels", SCOPES, subject=mailbox)
print(json.dumps(res))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+96
View File
@@ -0,0 +1,96 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user.readonly"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
max_results = int(inputs.get("max_results") or 100)
if max_results > 500:
max_results = 500
domain = str(inputs.get("domain") or "").strip()
customer = ""
if not domain:
default_user = str(_cfg().get("user_id") or "")
if "@" in default_user:
domain = default_user.split("@", 1)[1]
else:
customer = "my_customer"
params = {
"domain": domain,
"query": inputs.get("query"),
"maxResults": max_results,
"pageToken": inputs.get("page_token"),
"showDeleted": "true" if inputs.get("show_deleted") else None,
"customer": customer,
}
result = request("GET", ADMIN_BASE + "/users", SCOPES, params=params)
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+96
View File
@@ -0,0 +1,96 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.modify"]
def _csv(value):
return [x.strip() for x in str(value or "").split(",") if x.strip()]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
message_id = str(inputs.get("message_id") or "").strip()
if not message_id:
raise Exception("message_id is required")
add_labels = _csv(inputs.get("add_labels"))
remove_labels = _csv(inputs.get("remove_labels"))
body = {}
if add_labels:
body["addLabelIds"] = add_labels
if remove_labels:
body["removeLabelIds"] = remove_labels
if not body:
raise Exception("Provide add_labels and/or remove_labels")
res = request("POST", base + "/messages/" + urllib.parse.quote(message_id, safe="") + "/modify",
SCOPES, body=body, subject=mailbox)
print(json.dumps(res))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.settings.basic"]
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
filter_id = str(inputs.get("filter_id") or "").strip()
if not filter_id:
raise Exception("filter_id is required")
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox)
result = request("DELETE", base + "/settings/filters/" + urllib.parse.quote(filter_id, safe=""),
SCOPES, subject=mailbox)
if not result:
result = {"ok": True, "filter_id": filter_id}
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+143
View File
@@ -0,0 +1,143 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def flatten_message(msg):
payload = msg.get("payload", {})
headers = {h.get("name", "").lower(): h.get("value", "") for h in payload.get("headers", [])}
acc = {"body_text": "", "body_html": "", "attachments": []}
def walk(part):
for sub in part.get("parts", []) or []:
walk(sub)
mime = part.get("mimeType", "")
data = part.get("body", {}).get("data")
if part.get("filename"):
acc["attachments"].append({
"id": part.get("body", {}).get("attachmentId", ""),
"name": part.get("filename", ""),
"mime_type": mime,
})
elif data:
text = base64.urlsafe_b64decode(data.encode("ascii")).decode("utf-8", "replace")
if mime == "text/html":
acc["body_html"] += text
else:
acc["body_text"] += text
walk(payload)
return {
"id": msg.get("id"),
"thread_id": msg.get("threadId"),
"labels": msg.get("labelIds", []),
"snippet": msg.get("snippet", ""),
"internal_date": msg.get("internalDate"),
"subject": headers.get("subject", ""),
"from": headers.get("from", ""),
"to": headers.get("to", ""),
"cc": headers.get("cc", ""),
"date": headers.get("date", ""),
"message_id_header": headers.get("message-id", ""),
"body_text": acc["body_text"],
"body_html": acc["body_html"],
"attachments": acc["attachments"],
}
def _bool(value):
if isinstance(value, str):
return value.strip().lower() in ("1", "true", "yes", "y")
return bool(value)
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
max_results = int(inputs.get("max_results") or 100)
if max_results > 500:
max_results = 500
params = {
"q": inputs.get("query"),
"maxResults": max_results,
"pageToken": inputs.get("page_token"),
"labelIds": inputs.get("label_id"),
}
if inputs.get("include_spam_trash") not in (None, ""):
params["includeSpamTrash"] = "true" if _bool(inputs.get("include_spam_trash")) else "false"
res = request("GET", base + "/messages", SCOPES, params=params, subject=mailbox)
out = []
for m in res.get("messages", []) or []:
full = request("GET", base + "/messages/" + urllib.parse.quote(str(m.get("id", "")), safe=""),
SCOPES, params={"format": "full"}, subject=mailbox)
out.append(flatten_message(full))
print(json.dumps({"result": out, "next_page_token": res.get("nextPageToken", "")}))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
+133
View File
@@ -0,0 +1,133 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
import base64
import mimetypes
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = [
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send",
]
def _build_message(inputs, mailbox):
body = str(inputs.get("body") or "")
html_body = str(inputs.get("html_body") or "")
att_name = str(inputs.get("attachment_name") or "")
att_b64 = str(inputs.get("attachment_base64") or "")
if html_body or att_b64:
message = MIMEMultipart("mixed" if att_b64 else "alternative")
if body:
message.attach(MIMEText(body, "plain", "utf-8"))
if html_body:
message.attach(MIMEText(html_body, "html", "utf-8"))
if att_b64:
name = att_name or "attachment.bin"
ctype = mimetypes.guess_type(name)[0] or "application/octet-stream"
main_type, sub_type = ctype.split("/", 1)
part = MIMEBase(main_type, sub_type)
part.set_payload(base64.b64decode(att_b64))
encoders.encode_base64(part)
part.add_header("Content-Disposition", "attachment", filename=name)
message.attach(part)
else:
message = MIMEText(body, "plain", "utf-8")
message["From"] = mailbox
message["To"] = str(inputs.get("to") or "")
if inputs.get("cc"):
message["Cc"] = str(inputs["cc"])
if inputs.get("bcc"):
message["Bcc"] = str(inputs["bcc"])
message["Subject"] = str(inputs.get("subject") or "")
if inputs.get("reply_to"):
message["Reply-To"] = str(inputs["reply_to"])
if inputs.get("in_reply_to"):
message["In-Reply-To"] = str(inputs["in_reply_to"])
if inputs.get("references"):
message["References"] = str(inputs["references"])
return message
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
mailbox = str(inputs.get("user_id") or _cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
if not str(inputs.get("to") or "").strip():
raise Exception("to is required")
if not str(inputs.get("subject") or "").strip():
raise Exception("subject is required")
message = _build_message(inputs, mailbox)
raw = base64.urlsafe_b64encode(message.as_bytes()).decode("ascii")
res = request("POST", base + "/messages/send", SCOPES, body={"raw": raw}, subject=mailbox)
print(json.dumps(res))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,83 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/admin.directory.user"]
ADMIN_BASE = "https://admin.googleapis.com/admin/directory/v1"
def main():
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
user_key = str(inputs.get("user_key") or "").strip()
if not user_key:
raise Exception("user_key is required")
password = str(inputs.get("password") or "")
if not password:
raise Exception("password is required")
result = request("PUT", ADMIN_BASE + "/users/" + urllib.parse.quote(user_key, safe=""), SCOPES,
body={"password": password})
print(json.dumps(result))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)
@@ -0,0 +1,78 @@
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
import jwt
TOKEN_URL = "https://oauth2.googleapis.com/token"
def _cfg():
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
def _token(scopes, subject=None):
cfg = _cfg()
raw = cfg.get("service_account_json", "")
sa = json.loads(raw) if isinstance(raw, str) else raw
if not sa.get("client_email") or not sa.get("private_key"):
raise Exception("service_account_json must contain client_email and private_key")
now = int(time.time())
aud = sa.get("token_uri") or TOKEN_URL
payload = {
"iss": sa["client_email"],
"scope": " ".join(scopes),
"aud": aud,
"iat": now,
"exp": now + 3600,
}
sub = subject or cfg.get("user_id") or ""
if sub:
payload["sub"] = sub
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
data = urllib.parse.urlencode({
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
}).encode("utf-8")
req = urllib.request.Request(aud, data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as r:
tok = json.loads(r.read())
if not tok.get("access_token"):
raise Exception("Token request failed: " + json.dumps(tok))
return tok["access_token"]
def request(method, url, scopes, params=None, body=None, subject=None):
q = {k: str(x) for k, x in (params or {}).items() if x not in (None, "")}
if q:
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(q)
data = json.dumps(body).encode("utf-8") if body is not None else None
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(scopes, subject)}
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=90) as r:
raw = r.read()
return json.loads(raw) if raw else {}
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
def main():
mailbox = str(_cfg().get("user_id") or "me")
base = "https://gmail.googleapis.com/gmail/v1/users/" + urllib.parse.quote(mailbox, safe="")
res = request("GET", base + "/profile", SCOPES, subject=mailbox)
if not res.get("emailAddress"):
raise Exception("Profile response missing emailAddress: " + json.dumps(res))
print(json.dumps({"ok": True, "email": res["emailAddress"]}))
try:
main()
except urllib.error.HTTPError as e:
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
sys.exit(1)
except Exception as e:
print(json.dumps({"error": str(e)}))
sys.exit(1)