teems 0.3.0 → 0.3.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +37 -0
- data/lib/teems/api/messages.rb +6 -0
- data/lib/teems/cli.rb +1 -0
- data/lib/teems/commands/auth.rb +5 -3
- data/lib/teems/commands/help.rb +2 -0
- data/lib/teems/commands/meeting.rb +251 -1
- data/lib/teems/commands/meeting_transcript.rb +19 -4
- data/lib/teems/commands/messages.rb +48 -1
- data/lib/teems/commands/org.rb +7 -5
- data/lib/teems/commands/transcripts.rb +466 -0
- data/lib/teems/formatters/transcript_markdown.rb +93 -0
- data/lib/teems/services/headless_extract.rb +2 -3
- data/lib/teems/services/safari_js_runner.rb +1 -3
- data/lib/teems/services/safari_oauth.rb +54 -20
- data/lib/teems/services/token_extractor.rb +2 -2
- data/lib/teems/support/subprocess.rb +29 -0
- data/lib/teems/version.rb +1 -1
- data/lib/teems.rb +3 -0
- metadata +4 -1
|
@@ -116,12 +116,28 @@ module Teems
|
|
|
116
116
|
|
|
117
117
|
# Polls Safari's URL bar via AppleScript and manages Safari tab navigation
|
|
118
118
|
module SafariOAuthPolling
|
|
119
|
+
# Sign-in can require an account picker, MFA or a PIV/CAC certificate prompt, so the
|
|
120
|
+
# redirect is polled in chunks for several minutes rather than a single short burst.
|
|
121
|
+
POLL_TICK_SECONDS = 0.1
|
|
122
|
+
POLL_CHUNK_SECONDS = 15
|
|
123
|
+
POLL_CHUNKS = 12
|
|
124
|
+
|
|
119
125
|
private
|
|
120
126
|
|
|
121
127
|
def poll_safari_query_redirect
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
POLL_CHUNKS.times do |chunk|
|
|
129
|
+
raw = run_applescript(poll_query_redirect_script).to_s
|
|
130
|
+
return handle_poll_result(raw) unless raw == 'timeout'
|
|
131
|
+
|
|
132
|
+
announce_sign_in_wait(chunk)
|
|
133
|
+
end
|
|
134
|
+
log('Safari OAuth: timed out waiting for the sign-in redirect')
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def handle_poll_result(raw)
|
|
139
|
+
if raw.empty?
|
|
140
|
+
log('Safari OAuth: unable to read Safari tab URLs')
|
|
125
141
|
return nil
|
|
126
142
|
end
|
|
127
143
|
|
|
@@ -129,19 +145,30 @@ module Teems
|
|
|
129
145
|
parse_oauth_params(raw)
|
|
130
146
|
end
|
|
131
147
|
|
|
132
|
-
|
|
148
|
+
def announce_sign_in_wait(chunk)
|
|
149
|
+
return notify('Waiting for you to finish signing in to Safari...') if chunk.zero?
|
|
150
|
+
|
|
151
|
+
log("Safari OAuth: still waiting for sign-in (#{(chunk + 1) * POLL_CHUNK_SECONDS}s)")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Poll every tab for ?code= — Safari's URL property includes query strings, and the
|
|
155
|
+
# redirect can land in a tab other than the one we opened.
|
|
133
156
|
def poll_query_redirect_script
|
|
134
157
|
<<~APPLESCRIPT
|
|
135
158
|
tell application "Safari"
|
|
136
|
-
repeat
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
159
|
+
repeat #{(POLL_CHUNK_SECONDS / POLL_TICK_SECONDS).to_i} times
|
|
160
|
+
repeat with w in windows
|
|
161
|
+
repeat with t in tabs of w
|
|
162
|
+
try
|
|
163
|
+
set pageURL to (URL of t) as text
|
|
164
|
+
if pageURL contains "teams.microsoft.com/go?" then
|
|
165
|
+
set codeStart to offset of "?" in pageURL
|
|
166
|
+
return text (codeStart + 1) thru -1 of pageURL
|
|
167
|
+
end if
|
|
168
|
+
end try
|
|
169
|
+
end repeat
|
|
170
|
+
end repeat
|
|
171
|
+
delay #{POLL_TICK_SECONDS}
|
|
145
172
|
end repeat
|
|
146
173
|
return "timeout"
|
|
147
174
|
end tell
|
|
@@ -192,20 +219,20 @@ module Teems
|
|
|
192
219
|
include SafariOAuthPolling
|
|
193
220
|
|
|
194
221
|
SKYPE_RESOURCE = 'https://api.spaces.skype.com'
|
|
222
|
+
# Used when no previous login is on disk; Entra ID resolves the tenant from the account.
|
|
223
|
+
DEFAULT_TENANT = 'common'
|
|
195
224
|
|
|
196
225
|
private
|
|
197
226
|
|
|
198
227
|
def try_safari_oauth
|
|
199
228
|
hint, tenant = stored_login_hint
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
log('Trying fast Safari OAuth flow...')
|
|
203
|
-
safari_code_flow({ tenant: tenant, hint: hint })
|
|
229
|
+
log('Trying Safari OAuth flow...')
|
|
230
|
+
safari_code_flow({ tenant: tenant || DEFAULT_TENANT, hint: hint })
|
|
204
231
|
rescue StandardError => e
|
|
205
232
|
log("Safari OAuth error: #{e.class}: #{e.message}")
|
|
206
233
|
nil
|
|
207
234
|
ensure
|
|
208
|
-
close_teams_tab
|
|
235
|
+
close_teams_tab
|
|
209
236
|
end
|
|
210
237
|
|
|
211
238
|
def safari_code_flow(context)
|
|
@@ -273,12 +300,19 @@ module Teems
|
|
|
273
300
|
# :reek:FeatureEnvy
|
|
274
301
|
def assemble_safari_result(context, graph, skype)
|
|
275
302
|
spaces_token = skype[:spaces_token]
|
|
276
|
-
|
|
303
|
+
auth_token = graph['access_token']
|
|
304
|
+
{ auth_token: auth_token,
|
|
277
305
|
skype_token: exchange_skype_via_http(spaces_token),
|
|
278
306
|
skype_spaces_token: spaces_token, chatsvc_token: nil,
|
|
279
307
|
refresh_token: skype[:refresh_token],
|
|
280
308
|
client_id: OAuthUrlBuilder::TEAMS_APP_ID,
|
|
281
|
-
tenant_id: context[:tenant] }
|
|
309
|
+
tenant_id: resolved_tenant(auth_token, context[:tenant]) }
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Prefer the tenant the token was actually issued for over the placeholder used to
|
|
313
|
+
# start the flow, so refreshes hit the tenant-specific endpoint.
|
|
314
|
+
def resolved_tenant(auth_token, requested)
|
|
315
|
+
decode_jwt(auth_token.to_s)&.fetch('tid', nil) || requested
|
|
282
316
|
end
|
|
283
317
|
end
|
|
284
318
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'open3'
|
|
4
3
|
require_relative 'token_extractor_scripts'
|
|
5
4
|
require_relative 'token_exchange_scripts'
|
|
6
5
|
require_relative 'headless_extract'
|
|
@@ -58,7 +57,7 @@ module Teems
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
def run_applescript(script)
|
|
61
|
-
output, _stderr, status =
|
|
60
|
+
output, _stderr, status = Support::Subprocess.capture3('osascript', '-e', script)
|
|
62
61
|
return applescript_failure(status) unless status.success?
|
|
63
62
|
|
|
64
63
|
output.strip
|
|
@@ -396,6 +395,7 @@ module Teems
|
|
|
396
395
|
end
|
|
397
396
|
|
|
398
397
|
def log(message) = @output&.debug(message)
|
|
398
|
+
def notify(message) = @output&.info(message)
|
|
399
399
|
end
|
|
400
400
|
end
|
|
401
401
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Teems
|
|
6
|
+
module Support
|
|
7
|
+
# Wrapper around Open3.capture3 for long-running child processes.
|
|
8
|
+
#
|
|
9
|
+
# Open3 reads stdout and stderr on background threads. When the process is signalled
|
|
10
|
+
# while those reads are in flight, the pipes close underneath them and each thread
|
|
11
|
+
# raises IOError, which Ruby prints as a backtrace before the main thread can exit
|
|
12
|
+
# cleanly. Waiting on Safari sign-in or the headless helper keeps us inside capture3
|
|
13
|
+
# for minutes at a time, so Ctrl-C there is routine rather than exceptional.
|
|
14
|
+
#
|
|
15
|
+
# Reader threads inherit Thread.report_on_exception when they are created, so muting
|
|
16
|
+
# it for the duration of the call silences exactly those threads and nothing else.
|
|
17
|
+
module Subprocess
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def capture3(*command)
|
|
21
|
+
previous = Thread.report_on_exception
|
|
22
|
+
Thread.report_on_exception = false
|
|
23
|
+
Open3.capture3(*command)
|
|
24
|
+
ensure
|
|
25
|
+
Thread.report_on_exception = previous
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/teems/version.rb
CHANGED
data/lib/teems.rb
CHANGED
|
@@ -78,6 +78,7 @@ module Teems
|
|
|
78
78
|
autoload :MessageFormatter, 'teems/formatters/message_formatter'
|
|
79
79
|
autoload :MarkdownFormatter, 'teems/formatters/markdown_formatter'
|
|
80
80
|
autoload :CalendarFormatter, 'teems/formatters/calendar_formatter'
|
|
81
|
+
autoload :TranscriptMarkdown, 'teems/formatters/transcript_markdown'
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
# CLI commands implementing user-facing functionality
|
|
@@ -95,6 +96,7 @@ module Teems
|
|
|
95
96
|
autoload :Ooo, 'teems/commands/ooo'
|
|
96
97
|
autoload :Org, 'teems/commands/org'
|
|
97
98
|
autoload :Meeting, 'teems/commands/meeting'
|
|
99
|
+
autoload :Transcripts, 'teems/commands/transcripts'
|
|
98
100
|
autoload :Status, 'teems/commands/status'
|
|
99
101
|
end
|
|
100
102
|
|
|
@@ -117,5 +119,6 @@ module Teems
|
|
|
117
119
|
autoload :ErrorLogger, 'teems/support/error_logger'
|
|
118
120
|
autoload :Timezone, 'teems/support/timezone'
|
|
119
121
|
autoload :TimeParsing, 'teems/support/time_parsing'
|
|
122
|
+
autoload :Subprocess, 'teems/support/subprocess'
|
|
120
123
|
end
|
|
121
124
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: teems
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eric Boehs
|
|
@@ -49,12 +49,14 @@ files:
|
|
|
49
49
|
- lib/teems/commands/org.rb
|
|
50
50
|
- lib/teems/commands/status.rb
|
|
51
51
|
- lib/teems/commands/sync.rb
|
|
52
|
+
- lib/teems/commands/transcripts.rb
|
|
52
53
|
- lib/teems/commands/who.rb
|
|
53
54
|
- lib/teems/formatters/calendar_formatter.rb
|
|
54
55
|
- lib/teems/formatters/format_utils.rb
|
|
55
56
|
- lib/teems/formatters/markdown_formatter.rb
|
|
56
57
|
- lib/teems/formatters/message_formatter.rb
|
|
57
58
|
- lib/teems/formatters/output.rb
|
|
59
|
+
- lib/teems/formatters/transcript_markdown.rb
|
|
58
60
|
- lib/teems/models/account.rb
|
|
59
61
|
- lib/teems/models/channel.rb
|
|
60
62
|
- lib/teems/models/chat.rb
|
|
@@ -83,6 +85,7 @@ files:
|
|
|
83
85
|
- lib/teems/services/token_store.rb
|
|
84
86
|
- lib/teems/support/error_logger.rb
|
|
85
87
|
- lib/teems/support/help_formatter.rb
|
|
88
|
+
- lib/teems/support/subprocess.rb
|
|
86
89
|
- lib/teems/support/time_parsing.rb
|
|
87
90
|
- lib/teems/support/timezone.rb
|
|
88
91
|
- lib/teems/support/xdg_paths.rb
|