outnote 1.1.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {outnote-1.1.0 → outnote-1.2.0}/PKG-INFO +2 -2
- {outnote-1.1.0 → outnote-1.2.0}/pyproject.toml +2 -2
- {outnote-1.1.0 → outnote-1.2.0}/pyproject.toml.orig +2 -2
- outnote-1.2.0/src/outnote/_auth.py +58 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_cli.py +59 -7
- outnote-1.1.0/src/outnote/_auth.py +0 -48
- {outnote-1.1.0 → outnote-1.2.0}/LICENSE +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/README.md +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/__init__.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_base.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_broker.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_calendar.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_files.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_graph.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_mail.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_people.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_teams.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_text.py +0 -0
- {outnote-1.1.0 → outnote-1.2.0}/src/outnote/_work.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: outnote
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Headless Microsoft 365 client - mail, calendar, Teams, OneDrive, SharePoint and OneNote over Graph, no app registration.
|
|
5
5
|
Keywords: microsoft,microsoft-365,microsoft-graph,graph-api,outlook,onenote,onedrive,sharepoint,teams,mail,calendar,headless,no-app-registration,foci,entra
|
|
6
6
|
Author: PianoNic
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
21
21
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
22
|
Classifier: Topic :: Office/Business
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
-
Requires-Dist: ms-entrance>=1.
|
|
24
|
+
Requires-Dist: ms-entrance>=1.4
|
|
25
25
|
Requires-Dist: pypdf>=4 ; extra == 'pdf'
|
|
26
26
|
Requires-Python: >=3.10
|
|
27
27
|
Project-URL: Homepage, https://github.com/PianoNic/OutNote
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "outnote"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.0"
|
|
8
8
|
description = "Headless Microsoft 365 client - mail, calendar, Teams, OneDrive, SharePoint and OneNote over Graph, no app registration."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -43,7 +43,7 @@ classifiers = [
|
|
|
43
43
|
"Topic :: Office/Business",
|
|
44
44
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
45
45
|
]
|
|
46
|
-
dependencies = ["ms-entrance>=1.
|
|
46
|
+
dependencies = ["ms-entrance>=1.4"]
|
|
47
47
|
|
|
48
48
|
[[project.authors]]
|
|
49
49
|
name = "PianoNic"
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "outnote"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.0"
|
|
8
8
|
description = "Headless Microsoft 365 client - mail, calendar, Teams, OneDrive, SharePoint and OneNote over Graph, no app registration."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -47,7 +47,7 @@ classifiers = [
|
|
|
47
47
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
48
48
|
]
|
|
49
49
|
dependencies = [
|
|
50
|
-
"ms-entrance>=1.
|
|
50
|
+
"ms-entrance>=1.4",
|
|
51
51
|
]
|
|
52
52
|
|
|
53
53
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Headless Microsoft 365 sign-in - a thin wrapper over `entrance.foci_login`.
|
|
2
|
+
|
|
3
|
+
All the auth mechanics (PKCE sign-in, device code, the FOCI family refresh
|
|
4
|
+
token, the token broker and its persistence) live in `entrance`; OutNote only
|
|
5
|
+
supplies the Microsoft 365 specifics - the sign-in client and the Office client
|
|
6
|
+
used to validate a stored token.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from entrance import OFFICEHOME, foci_login
|
|
10
|
+
|
|
11
|
+
from ._base import C_OFFICE
|
|
12
|
+
from ._graph import OutNote
|
|
13
|
+
|
|
14
|
+
__all__ = ["authenticate", "OFFICEHOME"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def authenticate(
|
|
18
|
+
username=None,
|
|
19
|
+
password=None,
|
|
20
|
+
*,
|
|
21
|
+
tenant="common",
|
|
22
|
+
totp_secret=None,
|
|
23
|
+
totp_code=None,
|
|
24
|
+
cookies=".ms_session.json",
|
|
25
|
+
frt_store=".ms_frt",
|
|
26
|
+
device=False,
|
|
27
|
+
open_browser=False,
|
|
28
|
+
on_prompt=None,
|
|
29
|
+
**login_kwargs,
|
|
30
|
+
):
|
|
31
|
+
"""Sign in and return an `OutNote` client wired to a token broker.
|
|
32
|
+
|
|
33
|
+
No app registration or admin consent needed. If `frt_store` holds a live
|
|
34
|
+
refresh token, no sign-in happens at all. Otherwise pick how to sign in:
|
|
35
|
+
* default - headless (password + TOTP, or a seeded cookie jar).
|
|
36
|
+
* `device=True` - the device code flow: a short code shown on the terminal
|
|
37
|
+
that you enter at a URL, no browser redirect or copy-paste (the Office
|
|
38
|
+
client is used, which supports it and brokers to every M365 service).
|
|
39
|
+
* `interactive=True` / `"always"` - finish in a browser.
|
|
40
|
+
MFA: pass `totp_secret` (Base32) or `totp_code`. Extra kwargs pass through
|
|
41
|
+
to `entrance.login`.
|
|
42
|
+
"""
|
|
43
|
+
broker = foci_login(
|
|
44
|
+
username,
|
|
45
|
+
password,
|
|
46
|
+
client_id=C_OFFICE if device else OFFICEHOME,
|
|
47
|
+
tenant=tenant,
|
|
48
|
+
totp_secret=totp_secret,
|
|
49
|
+
totp_code=totp_code,
|
|
50
|
+
cookies=cookies,
|
|
51
|
+
frt_store=frt_store,
|
|
52
|
+
probe_client=C_OFFICE,
|
|
53
|
+
device=device,
|
|
54
|
+
open_browser=open_browser,
|
|
55
|
+
on_prompt=on_prompt,
|
|
56
|
+
**login_kwargs,
|
|
57
|
+
)
|
|
58
|
+
return OutNote(broker)
|
|
@@ -10,17 +10,31 @@ import json
|
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
12
|
|
|
13
|
+
from entrance import DeviceCodeError, LoginFailed, TokenError
|
|
14
|
+
|
|
13
15
|
from ._auth import authenticate
|
|
16
|
+
from ._base import GraphError
|
|
14
17
|
from ._text import message_text
|
|
15
18
|
|
|
19
|
+
_SIGNIN_HELP = """Not signed in. Do one of:
|
|
20
|
+
- sign in (easiest): outnote signin
|
|
21
|
+
shows a short code to enter at https://login.microsoft.com/device
|
|
22
|
+
- or set credentials (first run only):
|
|
23
|
+
OUTNOTE_USER / OUTNOTE_PASSWORD / OUTNOTE_TOTP (set on Windows, export on POSIX)
|
|
24
|
+
Either way a refresh token is saved (OUTNOTE_FRT, default .ms_frt), so later runs
|
|
25
|
+
need neither. `outnote signin --browser` signs in in a browser instead."""
|
|
26
|
+
|
|
16
27
|
|
|
17
|
-
def _client():
|
|
28
|
+
def _client(device=False, browser=False):
|
|
18
29
|
return authenticate(
|
|
19
30
|
os.environ.get("OUTNOTE_USER"),
|
|
20
31
|
os.environ.get("OUTNOTE_PASSWORD"),
|
|
21
32
|
totp_secret=os.environ.get("OUTNOTE_TOTP"),
|
|
22
33
|
cookies=os.environ.get("OUTNOTE_COOKIES", ".ms_session.json"),
|
|
23
34
|
frt_store=os.environ.get("OUTNOTE_FRT", ".ms_frt"),
|
|
35
|
+
device=device,
|
|
36
|
+
open_browser=device, # also open the device page automatically
|
|
37
|
+
interactive="always" if browser else False,
|
|
24
38
|
)
|
|
25
39
|
|
|
26
40
|
|
|
@@ -75,6 +89,12 @@ def main(argv=None):
|
|
|
75
89
|
ap = argparse.ArgumentParser(prog="outnote", description="Microsoft 365 over Graph.")
|
|
76
90
|
ap.add_argument("--text", action="store_true", help="readable output instead of JSON")
|
|
77
91
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
92
|
+
p = sub.add_parser(
|
|
93
|
+
"signin",
|
|
94
|
+
aliases=["login"],
|
|
95
|
+
help="sign in (device code by default, or --browser) and save the session",
|
|
96
|
+
)
|
|
97
|
+
p.add_argument("--browser", action="store_true", help="sign in in a browser instead")
|
|
78
98
|
sub.add_parser("me")
|
|
79
99
|
p = sub.add_parser("mail")
|
|
80
100
|
p.add_argument("--folder", default="inbox")
|
|
@@ -100,8 +120,44 @@ def main(argv=None):
|
|
|
100
120
|
p = sub.add_parser("get", help="raw GET of any Graph path")
|
|
101
121
|
p.add_argument("path")
|
|
102
122
|
a = ap.parse_args(argv)
|
|
123
|
+
sys.stdout.reconfigure(encoding="utf-8") # type: ignore[union-attr]
|
|
124
|
+
signin = a.cmd in ("signin", "login")
|
|
125
|
+
browser = getattr(a, "browser", False)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
# `signin` defaults to the device code flow; --browser switches to a browser.
|
|
129
|
+
note = _client(device=signin and not browser, browser=browser)
|
|
130
|
+
except LoginFailed:
|
|
131
|
+
print(_SIGNIN_HELP, file=sys.stderr)
|
|
132
|
+
return 2
|
|
133
|
+
except TokenError as e:
|
|
134
|
+
print(f"Sign-in failed: {e}", file=sys.stderr)
|
|
135
|
+
if e.error in ("invalid_grant", "interaction_required"):
|
|
136
|
+
print("\n" + _SIGNIN_HELP, file=sys.stderr)
|
|
137
|
+
return 2
|
|
138
|
+
except DeviceCodeError as e:
|
|
139
|
+
print(f"Sign-in failed: {e}", file=sys.stderr)
|
|
140
|
+
return 2
|
|
103
141
|
|
|
104
|
-
|
|
142
|
+
if signin:
|
|
143
|
+
me = note.me()
|
|
144
|
+
store = os.environ.get("OUTNOTE_FRT", ".ms_frt")
|
|
145
|
+
print(f"Signed in as {me.get('displayName')} <{me.get('userPrincipalName')}>.")
|
|
146
|
+
print(f"Session saved to {store} - later commands reuse it, no sign-in needed.")
|
|
147
|
+
return 0
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
data = _run(a, note)
|
|
151
|
+
except GraphError as e:
|
|
152
|
+
print(f"Graph error: {e}", file=sys.stderr)
|
|
153
|
+
return 1
|
|
154
|
+
|
|
155
|
+
out = _text(a.cmd, data, note) if a.text else json.dumps(data, indent=2, ensure_ascii=False)
|
|
156
|
+
print(out)
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _run(a, note):
|
|
105
161
|
if a.cmd == "me":
|
|
106
162
|
data = note.me()
|
|
107
163
|
elif a.cmd == "mail":
|
|
@@ -120,11 +176,7 @@ def main(argv=None):
|
|
|
120
176
|
data = note.files(a.path)
|
|
121
177
|
else:
|
|
122
178
|
data = note.get(a.path)
|
|
123
|
-
|
|
124
|
-
out = _text(a.cmd, data, note) if a.text else json.dumps(data, indent=2, ensure_ascii=False)
|
|
125
|
-
sys.stdout.reconfigure(encoding="utf-8") # type: ignore[union-attr]
|
|
126
|
-
print(out)
|
|
127
|
-
return 0
|
|
179
|
+
return data
|
|
128
180
|
|
|
129
181
|
|
|
130
182
|
if __name__ == "__main__":
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""Headless Microsoft 365 sign-in - a thin wrapper over `entrance.foci_login`.
|
|
2
|
-
|
|
3
|
-
All the auth mechanics (PKCE sign-in, the FOCI family refresh token, the token
|
|
4
|
-
broker and its persistence) live in `entrance`; OutNote only supplies the
|
|
5
|
-
Microsoft 365 specifics - the OfficeHome sign-in client (entrance's default)
|
|
6
|
-
and the Office client used to validate a stored token.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from entrance import OFFICEHOME, foci_login
|
|
10
|
-
|
|
11
|
-
from ._base import C_OFFICE
|
|
12
|
-
from ._graph import OutNote
|
|
13
|
-
|
|
14
|
-
__all__ = ["authenticate", "OFFICEHOME"]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def authenticate(
|
|
18
|
-
username=None,
|
|
19
|
-
password=None,
|
|
20
|
-
*,
|
|
21
|
-
tenant="common",
|
|
22
|
-
totp_secret=None,
|
|
23
|
-
totp_code=None,
|
|
24
|
-
cookies=".ms_session.json",
|
|
25
|
-
frt_store=".ms_frt",
|
|
26
|
-
**login_kwargs,
|
|
27
|
-
):
|
|
28
|
-
"""Sign in and return an `OutNote` client wired to a token broker.
|
|
29
|
-
|
|
30
|
-
No app registration or admin consent needed. If `frt_store` holds a live
|
|
31
|
-
refresh token, no sign-in happens at all; otherwise `entrance` signs in and
|
|
32
|
-
the new token is saved there (pass `frt_store=None` to disable).
|
|
33
|
-
`username`/`password` are optional once `cookies` holds a live session.
|
|
34
|
-
MFA: pass `totp_secret` (Base32) or `totp_code`; or `interactive=True` to
|
|
35
|
-
finish in a browser. Extra kwargs pass through to `entrance.login`.
|
|
36
|
-
"""
|
|
37
|
-
broker = foci_login(
|
|
38
|
-
username,
|
|
39
|
-
password,
|
|
40
|
-
tenant=tenant,
|
|
41
|
-
totp_secret=totp_secret,
|
|
42
|
-
totp_code=totp_code,
|
|
43
|
-
cookies=cookies,
|
|
44
|
-
frt_store=frt_store,
|
|
45
|
-
probe_client=C_OFFICE,
|
|
46
|
-
**login_kwargs,
|
|
47
|
-
)
|
|
48
|
-
return OutNote(broker)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|