Initial microsoft-graph skill - Outlook and OneDrive via Graph API
This commit is contained in:
commit
b777ac65a1
3 changed files with 567 additions and 0 deletions
81
SKILL.md
Normal file
81
SKILL.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
name: microsoft-graph
|
||||||
|
description: Microsoft Graph API access for Mikkel's Outlook and OneDrive (mikpar@outlook.dk). Use for email, calendar, and file operations via Graph API. Triggers on Outlook, OneDrive, or Microsoft 365 tasks.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Microsoft Graph Skill
|
||||||
|
|
||||||
|
Microsoft Graph API client for Mikkel's personal Microsoft account (mikpar@outlook.dk).
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Authenticate (first time / token expired)
|
||||||
|
python3 scripts/graph_client.py auth
|
||||||
|
|
||||||
|
# List recent emails
|
||||||
|
python3 scripts/graph_client.py mail list --limit 10
|
||||||
|
|
||||||
|
# Read specific email
|
||||||
|
python3 scripts/graph_client.py mail read --id <message_id>
|
||||||
|
|
||||||
|
# Send email
|
||||||
|
python3 scripts/graph_client.py mail send --to "to@example.com" --subject "Test" --body "Hello"
|
||||||
|
|
||||||
|
# List OneDrive root
|
||||||
|
python3 scripts/graph_client.py drive list
|
||||||
|
|
||||||
|
# List folder contents
|
||||||
|
python3 scripts/graph_client.py drive list --path "Documents/Parlobyg"
|
||||||
|
|
||||||
|
# Download file
|
||||||
|
python3 scripts/graph_client.py drive download --path "file.pdf" --output ./file.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Credentials
|
||||||
|
|
||||||
|
- **Account:** mikpar@outlook.dk
|
||||||
|
- **Client ID:** 96861287-203e-4d84-ad3b-379d63ba5eb2
|
||||||
|
- **Tenant ID:** 3a22994f-512f-447e-b8fa-19bbc849bd0c
|
||||||
|
- **Client Secret:** kiB8Q~TmhWDKHRdLqBi_RqRIIAjTkljyL6xE0aHU
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Uses **device code flow** for personal Microsoft accounts:
|
||||||
|
1. Run `python3 scripts/graph_client.py auth`
|
||||||
|
2. Open the URL shown (https://microsoft.com/devicelogin)
|
||||||
|
3. Enter the code displayed
|
||||||
|
4. Sign in with mikpar@outlook.dk
|
||||||
|
5. Token is saved to `~/.config/microsoft-graph/token.json`
|
||||||
|
|
||||||
|
Token auto-refreshes. Re-auth only needed if refresh token expires (~90 days).
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### Outlook Email
|
||||||
|
- 📧 List/search inbox
|
||||||
|
- 📖 Read messages
|
||||||
|
- ✉️ Send emails
|
||||||
|
- 📝 Create drafts
|
||||||
|
- 📁 Manage folders
|
||||||
|
|
||||||
|
### OneDrive
|
||||||
|
- 📂 Browse folders
|
||||||
|
- 🔍 Search files
|
||||||
|
- ⬇️ Download files
|
||||||
|
- ⬆️ Upload files
|
||||||
|
- 🔗 Create sharing links
|
||||||
|
|
||||||
|
### Calendar (if needed)
|
||||||
|
- 📅 List events
|
||||||
|
- ➕ Create events
|
||||||
|
- 🔔 Check availability
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
- Base URL: `https://graph.microsoft.com/v1.0`
|
||||||
|
- Scopes: `Mail.Read Mail.Send Files.ReadWrite User.Read offline_access`
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Graph API Docs](references/graph-api.md)
|
||||||
121
references/graph-api.md
Normal file
121
references/graph-api.md
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
# Microsoft Graph API Reference
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
`https://graph.microsoft.com/v1.0`
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Uses OAuth 2.0 device code flow for personal Microsoft accounts.
|
||||||
|
|
||||||
|
### Endpoints
|
||||||
|
- Device code: `https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode`
|
||||||
|
- Token: `https://login.microsoftonline.com/consumers/oauth2/v2.0/token`
|
||||||
|
|
||||||
|
### Scopes
|
||||||
|
- `Mail.Read` — Read emails
|
||||||
|
- `Mail.Send` — Send emails
|
||||||
|
- `Files.ReadWrite` — OneDrive access
|
||||||
|
- `User.Read` — User profile
|
||||||
|
- `offline_access` — Refresh tokens
|
||||||
|
|
||||||
|
## Mail API
|
||||||
|
|
||||||
|
### List messages
|
||||||
|
```
|
||||||
|
GET /me/mailFolders/inbox/messages?$top=10&$orderby=receivedDateTime desc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Read message
|
||||||
|
```
|
||||||
|
GET /me/messages/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Send message
|
||||||
|
```
|
||||||
|
POST /me/sendMail
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"subject": "Subject",
|
||||||
|
"body": {"contentType": "Text", "content": "Body"},
|
||||||
|
"toRecipients": [{"emailAddress": {"address": "to@example.com"}}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search messages
|
||||||
|
```
|
||||||
|
GET /me/messages?$search="from:sender@example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
### List folders
|
||||||
|
```
|
||||||
|
GET /me/mailFolders
|
||||||
|
```
|
||||||
|
|
||||||
|
## OneDrive API
|
||||||
|
|
||||||
|
### List root
|
||||||
|
```
|
||||||
|
GET /me/drive/root/children
|
||||||
|
```
|
||||||
|
|
||||||
|
### List folder
|
||||||
|
```
|
||||||
|
GET /me/drive/root:/{path}:/children
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get file metadata
|
||||||
|
```
|
||||||
|
GET /me/drive/root:/{path}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Download file
|
||||||
|
Get the `@microsoft.graph.downloadUrl` from file metadata.
|
||||||
|
|
||||||
|
### Upload small file (<4MB)
|
||||||
|
```
|
||||||
|
PUT /me/drive/root:/{path}:/content
|
||||||
|
Content-Type: application/octet-stream
|
||||||
|
|
||||||
|
<file bytes>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create folder
|
||||||
|
```
|
||||||
|
POST /me/drive/root/children
|
||||||
|
{
|
||||||
|
"name": "New Folder",
|
||||||
|
"folder": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Calendar API
|
||||||
|
|
||||||
|
### List events
|
||||||
|
```
|
||||||
|
GET /me/events?$top=10&$orderby=start/dateTime
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create event
|
||||||
|
```
|
||||||
|
POST /me/events
|
||||||
|
{
|
||||||
|
"subject": "Meeting",
|
||||||
|
"start": {"dateTime": "2026-03-24T10:00:00", "timeZone": "Europe/Copenhagen"},
|
||||||
|
"end": {"dateTime": "2026-03-24T11:00:00", "timeZone": "Europe/Copenhagen"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Common errors:
|
||||||
|
- `401 Unauthorized` — Token expired, need to refresh or re-auth
|
||||||
|
- `403 Forbidden` — Missing permissions
|
||||||
|
- `404 NotFound` — Resource doesn't exist
|
||||||
|
- `429 TooManyRequests` — Rate limited, back off
|
||||||
|
|
||||||
|
## Rate Limits
|
||||||
|
|
||||||
|
- Per-app: ~10,000 requests / 10 minutes
|
||||||
|
- Per-mailbox: ~10,000 requests / 10 minutes
|
||||||
|
- Throttled responses include `Retry-After` header
|
||||||
365
scripts/graph_client.py
Executable file
365
scripts/graph_client.py
Executable file
|
|
@ -0,0 +1,365 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Microsoft Graph API Client for Mikkel (mikpar@outlook.dk)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 graph_client.py auth # Authenticate
|
||||||
|
python3 graph_client.py mail list --limit 10 # List emails
|
||||||
|
python3 graph_client.py mail read --id <id> # Read email
|
||||||
|
python3 graph_client.py mail send --to x@y.com --subject "Hi" --body "Hello"
|
||||||
|
python3 graph_client.py drive list # List OneDrive root
|
||||||
|
python3 graph_client.py drive list --path "Documents" # List folder
|
||||||
|
python3 graph_client.py drive download --path "file.pdf" --output ./file.pdf
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
CLIENT_ID = "96861287-203e-4d84-ad3b-379d63ba5eb2"
|
||||||
|
TENANT_ID = "3a22994f-512f-447e-b8fa-19bbc849bd0c"
|
||||||
|
CLIENT_SECRET = "kiB8Q~TmhWDKHRdLqBi_RqRIIAjTkljyL6xE0aHU"
|
||||||
|
SCOPES = "Mail.Read Mail.Send Files.ReadWrite User.Read offline_access"
|
||||||
|
|
||||||
|
# Use 'consumers' endpoint for personal Microsoft accounts
|
||||||
|
AUTH_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0"
|
||||||
|
GRAPH_URL = "https://graph.microsoft.com/v1.0"
|
||||||
|
|
||||||
|
TOKEN_FILE = os.path.expanduser("~/.config/microsoft-graph/token.json")
|
||||||
|
|
||||||
|
|
||||||
|
def save_token(token_data):
|
||||||
|
"""Save token to file."""
|
||||||
|
os.makedirs(os.path.dirname(TOKEN_FILE), exist_ok=True)
|
||||||
|
token_data["saved_at"] = int(time.time())
|
||||||
|
with open(TOKEN_FILE, "w") as f:
|
||||||
|
json.dump(token_data, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
def load_token():
|
||||||
|
"""Load token from file."""
|
||||||
|
if not os.path.exists(TOKEN_FILE):
|
||||||
|
return None
|
||||||
|
with open(TOKEN_FILE, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_token(refresh_token_str):
|
||||||
|
"""Refresh the access token."""
|
||||||
|
data = urlencode({
|
||||||
|
"client_id": CLIENT_ID,
|
||||||
|
"client_secret": CLIENT_SECRET,
|
||||||
|
"refresh_token": refresh_token_str,
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"scope": SCOPES,
|
||||||
|
}).encode()
|
||||||
|
|
||||||
|
req = Request(f"{AUTH_URL}/token", data=data, method="POST")
|
||||||
|
req.add_header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urlopen(req, timeout=30) as resp:
|
||||||
|
token_data = json.loads(resp.read().decode())
|
||||||
|
save_token(token_data)
|
||||||
|
return token_data
|
||||||
|
except HTTPError as e:
|
||||||
|
print(f"Token refresh failed: {e.code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_access_token():
|
||||||
|
"""Get valid access token, refreshing if needed."""
|
||||||
|
token_data = load_token()
|
||||||
|
if not token_data:
|
||||||
|
print("No token found. Run 'auth' command first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if token is expired (with 5 min buffer)
|
||||||
|
saved_at = token_data.get("saved_at", 0)
|
||||||
|
expires_in = token_data.get("expires_in", 3600)
|
||||||
|
if time.time() > saved_at + expires_in - 300:
|
||||||
|
print("Token expired, refreshing...")
|
||||||
|
token_data = refresh_token(token_data.get("refresh_token"))
|
||||||
|
if not token_data:
|
||||||
|
print("Token refresh failed. Run 'auth' command.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return token_data["access_token"]
|
||||||
|
|
||||||
|
|
||||||
|
def device_code_auth():
|
||||||
|
"""Authenticate using device code flow."""
|
||||||
|
# Request device code
|
||||||
|
data = urlencode({
|
||||||
|
"client_id": CLIENT_ID,
|
||||||
|
"scope": SCOPES,
|
||||||
|
}).encode()
|
||||||
|
|
||||||
|
req = Request(f"{AUTH_URL}/devicecode", data=data, method="POST")
|
||||||
|
req.add_header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urlopen(req, timeout=30) as resp:
|
||||||
|
device_data = json.loads(resp.read().decode())
|
||||||
|
except HTTPError as e:
|
||||||
|
return {"error": f"Device code request failed: {e.code}"}
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("📱 AUTHENTICATION REQUIRED")
|
||||||
|
print("=" * 50)
|
||||||
|
print(f"\n1. Open: {device_data['verification_uri']}")
|
||||||
|
print(f"2. Enter code: {device_data['user_code']}")
|
||||||
|
print(f"3. Sign in with mikpar@outlook.dk")
|
||||||
|
print("\nWaiting for authorization...")
|
||||||
|
|
||||||
|
# Poll for token
|
||||||
|
interval = device_data.get("interval", 5)
|
||||||
|
expires_in = device_data.get("expires_in", 900)
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
while time.time() - start_time < expires_in:
|
||||||
|
time.sleep(interval)
|
||||||
|
|
||||||
|
poll_data = urlencode({
|
||||||
|
"client_id": CLIENT_ID,
|
||||||
|
"client_secret": CLIENT_SECRET,
|
||||||
|
"device_code": device_data["device_code"],
|
||||||
|
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
||||||
|
}).encode()
|
||||||
|
|
||||||
|
poll_req = Request(f"{AUTH_URL}/token", data=poll_data, method="POST")
|
||||||
|
poll_req.add_header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urlopen(poll_req, timeout=30) as resp:
|
||||||
|
token_data = json.loads(resp.read().decode())
|
||||||
|
save_token(token_data)
|
||||||
|
print("\n✅ Authentication successful! Token saved.")
|
||||||
|
return {"status": "authenticated"}
|
||||||
|
except HTTPError as e:
|
||||||
|
error_body = json.loads(e.read().decode())
|
||||||
|
error_code = error_body.get("error")
|
||||||
|
if error_code == "authorization_pending":
|
||||||
|
continue
|
||||||
|
elif error_code == "slow_down":
|
||||||
|
interval += 5
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return {"error": f"Auth failed: {error_body.get('error_description')}"}
|
||||||
|
|
||||||
|
return {"error": "Authentication timed out"}
|
||||||
|
|
||||||
|
|
||||||
|
def graph_request(endpoint, method="GET", data=None, headers=None):
|
||||||
|
"""Make a Graph API request."""
|
||||||
|
token = get_access_token()
|
||||||
|
url = f"{GRAPH_URL}{endpoint}"
|
||||||
|
|
||||||
|
req_headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
if headers:
|
||||||
|
req_headers.update(headers)
|
||||||
|
|
||||||
|
req = Request(url, method=method, headers=req_headers)
|
||||||
|
if data:
|
||||||
|
req.data = json.dumps(data).encode()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urlopen(req, timeout=30) as resp:
|
||||||
|
return json.loads(resp.read().decode())
|
||||||
|
except HTTPError as e:
|
||||||
|
error_body = e.read().decode()
|
||||||
|
try:
|
||||||
|
return {"error": json.loads(error_body)}
|
||||||
|
except:
|
||||||
|
return {"error": f"HTTP {e.code}: {error_body[:200]}"}
|
||||||
|
|
||||||
|
|
||||||
|
def list_mail(limit=10, folder="inbox"):
|
||||||
|
"""List emails."""
|
||||||
|
endpoint = f"/me/mailFolders/{folder}/messages?$top={limit}&$orderby=receivedDateTime desc"
|
||||||
|
endpoint += "&$select=id,subject,from,receivedDateTime,isRead,bodyPreview"
|
||||||
|
result = graph_request(endpoint)
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
for msg in result.get("value", []):
|
||||||
|
messages.append({
|
||||||
|
"id": msg["id"],
|
||||||
|
"subject": msg.get("subject", "(no subject)"),
|
||||||
|
"from": msg.get("from", {}).get("emailAddress", {}).get("address", "unknown"),
|
||||||
|
"date": msg.get("receivedDateTime"),
|
||||||
|
"read": msg.get("isRead", False),
|
||||||
|
"preview": msg.get("bodyPreview", "")[:100]
|
||||||
|
})
|
||||||
|
|
||||||
|
return {"count": len(messages), "messages": messages}
|
||||||
|
|
||||||
|
|
||||||
|
def read_mail(msg_id):
|
||||||
|
"""Read a specific email."""
|
||||||
|
endpoint = f"/me/messages/{msg_id}"
|
||||||
|
result = graph_request(endpoint)
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": result["id"],
|
||||||
|
"subject": result.get("subject"),
|
||||||
|
"from": result.get("from", {}).get("emailAddress", {}),
|
||||||
|
"to": [r.get("emailAddress", {}) for r in result.get("toRecipients", [])],
|
||||||
|
"date": result.get("receivedDateTime"),
|
||||||
|
"body": result.get("body", {}).get("content", "")[:5000]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def send_mail(to, subject, body, cc=None):
|
||||||
|
"""Send an email."""
|
||||||
|
message = {
|
||||||
|
"message": {
|
||||||
|
"subject": subject,
|
||||||
|
"body": {
|
||||||
|
"contentType": "Text",
|
||||||
|
"content": body
|
||||||
|
},
|
||||||
|
"toRecipients": [{"emailAddress": {"address": to}}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc:
|
||||||
|
message["message"]["ccRecipients"] = [{"emailAddress": {"address": cc}}]
|
||||||
|
|
||||||
|
result = graph_request("/me/sendMail", method="POST", data=message)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
return {"status": "sent", "to": to, "subject": subject}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def list_drive(path=None):
|
||||||
|
"""List OneDrive contents."""
|
||||||
|
if path:
|
||||||
|
endpoint = f"/me/drive/root:/{path}:/children"
|
||||||
|
else:
|
||||||
|
endpoint = "/me/drive/root/children"
|
||||||
|
|
||||||
|
endpoint += "?$select=id,name,size,folder,file,lastModifiedDateTime"
|
||||||
|
result = graph_request(endpoint)
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for item in result.get("value", []):
|
||||||
|
items.append({
|
||||||
|
"id": item["id"],
|
||||||
|
"name": item["name"],
|
||||||
|
"type": "folder" if "folder" in item else "file",
|
||||||
|
"size": item.get("size"),
|
||||||
|
"modified": item.get("lastModifiedDateTime")
|
||||||
|
})
|
||||||
|
|
||||||
|
return {"count": len(items), "items": items}
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(path, output):
|
||||||
|
"""Download a file from OneDrive."""
|
||||||
|
# Get download URL
|
||||||
|
endpoint = f"/me/drive/root:/{path}"
|
||||||
|
result = graph_request(endpoint)
|
||||||
|
|
||||||
|
if "error" in result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
download_url = result.get("@microsoft.graph.downloadUrl")
|
||||||
|
if not download_url:
|
||||||
|
return {"error": "No download URL available"}
|
||||||
|
|
||||||
|
# Download file
|
||||||
|
try:
|
||||||
|
req = Request(download_url)
|
||||||
|
with urlopen(req, timeout=60) as resp:
|
||||||
|
with open(output, "wb") as f:
|
||||||
|
f.write(resp.read())
|
||||||
|
return {"status": "downloaded", "path": path, "output": output, "size": os.path.getsize(output)}
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Microsoft Graph API Client")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
subparsers.add_parser("auth", help="Authenticate with device code flow")
|
||||||
|
|
||||||
|
# Mail commands
|
||||||
|
mail_parser = subparsers.add_parser("mail", help="Email operations")
|
||||||
|
mail_sub = mail_parser.add_subparsers(dest="mail_cmd")
|
||||||
|
|
||||||
|
mail_list = mail_sub.add_parser("list", help="List emails")
|
||||||
|
mail_list.add_argument("--limit", type=int, default=10)
|
||||||
|
mail_list.add_argument("--folder", default="inbox")
|
||||||
|
|
||||||
|
mail_read = mail_sub.add_parser("read", help="Read email")
|
||||||
|
mail_read.add_argument("--id", required=True)
|
||||||
|
|
||||||
|
mail_send = mail_sub.add_parser("send", help="Send email")
|
||||||
|
mail_send.add_argument("--to", required=True)
|
||||||
|
mail_send.add_argument("--subject", required=True)
|
||||||
|
mail_send.add_argument("--body", required=True)
|
||||||
|
mail_send.add_argument("--cc")
|
||||||
|
|
||||||
|
# Drive commands
|
||||||
|
drive_parser = subparsers.add_parser("drive", help="OneDrive operations")
|
||||||
|
drive_sub = drive_parser.add_subparsers(dest="drive_cmd")
|
||||||
|
|
||||||
|
drive_list = drive_sub.add_parser("list", help="List files/folders")
|
||||||
|
drive_list.add_argument("--path")
|
||||||
|
|
||||||
|
drive_dl = drive_sub.add_parser("download", help="Download file")
|
||||||
|
drive_dl.add_argument("--path", required=True)
|
||||||
|
drive_dl.add_argument("--output", required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "auth":
|
||||||
|
result = device_code_auth()
|
||||||
|
elif args.command == "mail":
|
||||||
|
if args.mail_cmd == "list":
|
||||||
|
result = list_mail(args.limit, args.folder)
|
||||||
|
elif args.mail_cmd == "read":
|
||||||
|
result = read_mail(args.id)
|
||||||
|
elif args.mail_cmd == "send":
|
||||||
|
result = send_mail(args.to, args.subject, args.body, args.cc)
|
||||||
|
else:
|
||||||
|
mail_parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
elif args.command == "drive":
|
||||||
|
if args.drive_cmd == "list":
|
||||||
|
result = list_drive(args.path)
|
||||||
|
elif args.drive_cmd == "download":
|
||||||
|
result = download_file(args.path, args.output)
|
||||||
|
else:
|
||||||
|
drive_parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in a new issue