teems 0.1.0
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 +7 -0
- data/CHANGELOG.md +24 -0
- data/LICENSE +21 -0
- data/README.md +136 -0
- data/bin/teems +7 -0
- data/lib/teems/api/calendar.rb +94 -0
- data/lib/teems/api/channels.rb +26 -0
- data/lib/teems/api/chats.rb +29 -0
- data/lib/teems/api/client.rb +40 -0
- data/lib/teems/api/files.rb +12 -0
- data/lib/teems/api/messages.rb +58 -0
- data/lib/teems/api/users.rb +88 -0
- data/lib/teems/api/users_mailbox.rb +16 -0
- data/lib/teems/api/users_presence.rb +43 -0
- data/lib/teems/cli.rb +133 -0
- data/lib/teems/commands/activity.rb +222 -0
- data/lib/teems/commands/auth.rb +268 -0
- data/lib/teems/commands/base.rb +146 -0
- data/lib/teems/commands/cal.rb +891 -0
- data/lib/teems/commands/channels.rb +115 -0
- data/lib/teems/commands/chats.rb +159 -0
- data/lib/teems/commands/help.rb +107 -0
- data/lib/teems/commands/messages.rb +281 -0
- data/lib/teems/commands/ooo.rb +385 -0
- data/lib/teems/commands/org.rb +232 -0
- data/lib/teems/commands/status.rb +224 -0
- data/lib/teems/commands/sync.rb +390 -0
- data/lib/teems/commands/who.rb +377 -0
- data/lib/teems/formatters/calendar_formatter.rb +227 -0
- data/lib/teems/formatters/format_utils.rb +56 -0
- data/lib/teems/formatters/markdown_formatter.rb +113 -0
- data/lib/teems/formatters/message_formatter.rb +67 -0
- data/lib/teems/formatters/output.rb +105 -0
- data/lib/teems/models/account.rb +59 -0
- data/lib/teems/models/channel.rb +31 -0
- data/lib/teems/models/chat.rb +111 -0
- data/lib/teems/models/duration.rb +46 -0
- data/lib/teems/models/event.rb +124 -0
- data/lib/teems/models/message.rb +125 -0
- data/lib/teems/models/parsing.rb +56 -0
- data/lib/teems/models/user.rb +25 -0
- data/lib/teems/models/user_profile.rb +45 -0
- data/lib/teems/runner.rb +81 -0
- data/lib/teems/services/api_client.rb +217 -0
- data/lib/teems/services/cache_store.rb +32 -0
- data/lib/teems/services/configuration.rb +56 -0
- data/lib/teems/services/file_downloader.rb +39 -0
- data/lib/teems/services/headless_extract.rb +192 -0
- data/lib/teems/services/safari_oauth.rb +285 -0
- data/lib/teems/services/sync_dir_naming.rb +42 -0
- data/lib/teems/services/sync_engine.rb +194 -0
- data/lib/teems/services/sync_store.rb +193 -0
- data/lib/teems/services/teams_url_parser.rb +78 -0
- data/lib/teems/services/token_exchange_scripts.rb +56 -0
- data/lib/teems/services/token_extractor.rb +401 -0
- data/lib/teems/services/token_extractor_scripts.rb +116 -0
- data/lib/teems/services/token_refresher.rb +169 -0
- data/lib/teems/services/token_store.rb +116 -0
- data/lib/teems/support/error_logger.rb +35 -0
- data/lib/teems/support/help_formatter.rb +80 -0
- data/lib/teems/support/timezone.rb +44 -0
- data/lib/teems/support/xdg_paths.rb +62 -0
- data/lib/teems/version.rb +5 -0
- data/lib/teems.rb +117 -0
- data/support/token_helper.swift +485 -0
- metadata +110 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teems
|
|
4
|
+
module Commands
|
|
5
|
+
# Output helpers for CLI commands: logging, printing, JSON
|
|
6
|
+
module CommandOutput
|
|
7
|
+
protected
|
|
8
|
+
|
|
9
|
+
def success(msg) = @options[:quiet] || output.success(msg)
|
|
10
|
+
def info(msg) = @options[:quiet] || output.info(msg)
|
|
11
|
+
def warn(message) = output.warn(message)
|
|
12
|
+
def error(message) = output.error(message) || 1
|
|
13
|
+
def debug(msg) = @options[:verbose] && output.debug(msg)
|
|
14
|
+
def puts(message = '') = @options[:quiet] || output.puts(message)
|
|
15
|
+
def print(msg) = @options[:quiet] || output.print(msg)
|
|
16
|
+
def output_json(data) = output.puts(JSON.pretty_generate(data))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Option validation helpers for CLI commands
|
|
20
|
+
module CommandValidation
|
|
21
|
+
protected
|
|
22
|
+
|
|
23
|
+
def check_unknown_options
|
|
24
|
+
return nil if @unknown_options.empty?
|
|
25
|
+
|
|
26
|
+
error("Unknown option: #{@unknown_options.first}")
|
|
27
|
+
error('Run with --help for available options.')
|
|
28
|
+
1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def unknown_options? = @unknown_options.any?
|
|
32
|
+
def show_help? = @options[:help]
|
|
33
|
+
|
|
34
|
+
def show_help
|
|
35
|
+
output.puts help_text
|
|
36
|
+
0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def validate_options
|
|
40
|
+
return show_help if show_help?
|
|
41
|
+
return check_unknown_options if unknown_options?
|
|
42
|
+
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Base class for all CLI commands with option parsing and output helpers
|
|
48
|
+
class Base
|
|
49
|
+
include CommandOutput
|
|
50
|
+
include CommandValidation
|
|
51
|
+
|
|
52
|
+
attr_reader :runner, :options, :positional_args
|
|
53
|
+
|
|
54
|
+
def initialize(args, runner:)
|
|
55
|
+
@runner = runner
|
|
56
|
+
@options = default_options
|
|
57
|
+
@unknown_options = []
|
|
58
|
+
@positional_args = []
|
|
59
|
+
parse_options(args)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def execute
|
|
63
|
+
raise NotImplementedError, 'Subclass must implement #execute'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
protected
|
|
67
|
+
|
|
68
|
+
# Convenience accessors
|
|
69
|
+
def output = runner.output
|
|
70
|
+
def config = runner.config
|
|
71
|
+
def cache_store = runner.cache_store
|
|
72
|
+
def token_store = runner.token_store
|
|
73
|
+
def api_client = runner.api_client
|
|
74
|
+
|
|
75
|
+
def default_options
|
|
76
|
+
{ verbose: false, quiet: false, json: false, limit: 20 }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def parse_options(args)
|
|
80
|
+
pending = args.dup
|
|
81
|
+
while pending.any?
|
|
82
|
+
arg = pending.shift
|
|
83
|
+
if arg.start_with?('-')
|
|
84
|
+
parse_single_option(arg, pending)
|
|
85
|
+
else
|
|
86
|
+
@positional_args << arg
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
@positional_args
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
COMMON_OPTIONS = {
|
|
93
|
+
'-v' => :verbose, '--verbose' => :verbose,
|
|
94
|
+
'-q' => :quiet, '--quiet' => :quiet,
|
|
95
|
+
'--json' => :json, '-h' => :help, '--help' => :help
|
|
96
|
+
}.freeze
|
|
97
|
+
|
|
98
|
+
LIMIT_FLAGS = %w[-n --limit].freeze
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def parse_single_option(arg, pending)
|
|
103
|
+
return @options[:limit] = pending.shift.to_i if LIMIT_FLAGS.include?(arg)
|
|
104
|
+
return @options[COMMON_OPTIONS[arg]] = true if COMMON_OPTIONS.key?(arg)
|
|
105
|
+
|
|
106
|
+
handle_option(arg, pending)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
protected
|
|
110
|
+
|
|
111
|
+
# Override in subclass to handle command-specific options
|
|
112
|
+
def handle_option(arg, _pending)
|
|
113
|
+
@unknown_options << arg
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def help_text
|
|
117
|
+
'No help available for this command.'
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def require_auth
|
|
121
|
+
return nil if runner.configured?
|
|
122
|
+
|
|
123
|
+
error('Not authenticated. Run: teems auth login')
|
|
124
|
+
1
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Execute a block with automatic token refresh on 401
|
|
128
|
+
# Usage: with_token_refresh { runner.messages_api.chat_messages(chat_id: id) }
|
|
129
|
+
def with_token_refresh
|
|
130
|
+
yield
|
|
131
|
+
rescue ApiError => e
|
|
132
|
+
raise unless e.unauthorized? || e.message.include?('expired')
|
|
133
|
+
|
|
134
|
+
attempt_token_refresh
|
|
135
|
+
yield
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def attempt_token_refresh
|
|
139
|
+
debug('Token expired, attempting refresh...')
|
|
140
|
+
raise ApiError, 'Token refresh failed' unless runner.refresh_tokens
|
|
141
|
+
|
|
142
|
+
debug('Token refreshed, retrying request...')
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|