Module: MessagesTool

Defined in:
lib/messages_tool.rb

Constant Summary collapse

VERSION =
'1.6.0'
ENDPOINT =
'https://special-system-987z.vercel.app/api/messages'
AUTH_FILE =
File.expand_path('../.auth_session.json', __dir__)
LOCK_FILE =
File.expand_path('../pin.lock', __dir__)

Class Method Summary collapse

Class Method Details

.handle_app_default(subcommand, _args) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/messages_tool.rb', line 258

def handle_app_default(subcommand, _args)
  auth = load_auth

  case subcommand
  when 'help'
    show_app_default_help
  when 'register'
    pin = prompt_pin('Enter new PIN: ')
    confirm = prompt_pin('Confirm new PIN: ')

    if pin.empty? || pin != confirm
      puts '[-] PINs do not match or cannot be empty.'
      return
    end

    save_auth({ 'registered' => true, 'logged_in' => true, 'pin' => pin, 'use_application_default' => true })
    puts '[+] Application-default credentials registered, logged in, and pin.lock written.'
  when 'login'
    unless auth['registered']
      puts '[-] No account registered.'
      return
    end

    auth['use_application_default'] = true
    auth['logged_in'] = true
    save_auth(auth)
    puts '[+] Application-default session logged in (pin.lock updated).'
  when 'logout'
    auth['logged_in'] = false
    save_auth(auth)
    puts '[+] Application-default session logged out (pin.lock removed).'
  when 'status'
    status = auth['logged_in'] && File.exist?(LOCK_FILE) ? 'Logged In (Active Lock)' : 'Logged Out'
    app_def = auth['use_application_default'] ? 'Enabled' : 'Disabled'
    puts '=========================================='
    puts '   APPLICATION DEFAULT AUTH STATUS        '
    puts '=========================================='
    puts " Registered          : #{auth['registered'] ? 'Yes' : 'No'}"
    puts " Session             : #{status}"
    puts " Lock File (pin.lock): #{File.exist?(LOCK_FILE) ? 'Present' : 'Absent'}"
    puts " Application Default : #{app_def}"
    puts '=========================================='
  when 'reset'
    require_auth
    pin = prompt_pin('Enter new PIN to reset: ')
    confirm = prompt_pin('Confirm new PIN: ')

    if pin.empty? || pin != confirm
      puts '[-] PINs do not match or cannot be empty.'
      return
    end

    auth['pin'] = pin
    auth['use_application_default'] = true
    save_auth(auth)
    puts '[+] Application-default PIN reset successfully.'
  when 'remove'
    auth['use_application_default'] = false
    save_auth(auth)
    puts '[+] Application-default credentials mode disabled.'
  when 'list'
    puts JSON.pretty_generate(auth)
  end
end

.handle_auth(subcommand, args) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/messages_tool.rb', line 175

def handle_auth(subcommand, args)
  auth = load_auth

  if subcommand == 'application-default'
    app_sub = args.shift || 'help'
    handle_app_default(app_sub, args)
    return
  end

  case subcommand
  when 'help'
    show_auth_help
  when 'register'
    if auth['registered']
      puts '[-] Account already registered. Use `auth reset` or `account reset` to change PIN.'
      return
    end

    pin = prompt_pin('Enter new PIN: ')
    confirm = prompt_pin('Confirm new PIN: ')

    if pin.empty? || pin != confirm
      puts '[-] PINs do not match or cannot be empty.'
      return
    end

    save_auth({ 'registered' => true, 'logged_in' => true, 'pin' => pin, 'use_application_default' => false })
    puts '[+] Registered, logged in, and updated pin.lock successfully.'
  when 'login'
    unless auth['registered']
      puts '[-] No account registered.'
      return
    end

    pin = prompt_pin('Enter PIN to login: ')
    if pin == auth['pin']
      auth['logged_in'] = true
      save_auth(auth)
      puts '[+] Logged in and pin.lock acquired successfully.'
    else
      puts '[-] Invalid PIN.'
    end
  when 'logout'
    prompt_pin('Enter PIN to logout: ')
    auth['logged_in'] = false
    save_auth(auth)
    puts '[+] Logged out and released pin.lock successfully.'
  when 'status'
    prompt_pin('Enter PIN for status: ')
    status = auth['logged_in'] && File.exist?(LOCK_FILE) ? 'Logged In (Active Lock)' : 'Logged Out'
    app_def = auth['use_application_default'] ? 'Enabled' : 'Disabled'
    puts '=========================================='
    puts '           AUTH STATUS                    '
    puts '=========================================='
    puts " Registered          : #{auth['registered'] ? 'Yes' : 'No'}"
    puts " Session             : #{status}"
    puts " Lock File (pin.lock): #{File.exist?(LOCK_FILE) ? 'Present' : 'Absent'}"
    puts " Application Default : #{app_def}"
    puts '=========================================='
  when 'reset'
    require_auth
    pin = prompt_pin('Enter new PIN to reset: ')
    confirm = prompt_pin('Confirm new PIN: ')

    if pin.empty? || pin != confirm
      puts '[-] PINs do not match or cannot be empty.'
      return
    end

    auth['pin'] = pin
    save_auth(auth)
    puts '[+] PIN reset and session state updated.'
  when 'remove'
    prompt_pin('Enter PIN to remove: ')
    FileUtils.rm_f(AUTH_FILE)
    FileUtils.rm_f(LOCK_FILE)
    puts '[+] Auth session, PIN, and pin.lock removed successfully.'
  when 'list'
    prompt_pin('Enter PIN to list auth data: ')
    puts JSON.pretty_generate(auth)
  end
end

.handle_messages(subcommand, args, raw_json: false) ⇒ Object



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/messages_tool.rb', line 323

def handle_messages(subcommand, args, raw_json: false)
  require_auth

  case subcommand
  when 'send'
    number_idx = args.index('--number')
    text_idx = args.index('--text')

    number = number_idx ? args[number_idx + 1] : nil
    text = text_idx ? args[text_idx + 1] : nil

    if number.nil? || text.nil?
      puts '[-] Usage: messages send --number <PHONE> --text <MESSAGE>'
      return
    end

    res = http_request(:post, ENDPOINT, { phone: number, text: text })

    if raw_json
      puts JSON.pretty_generate(res)
    elsif res['success']
      msg = res['message']
      puts '=========================================='
      puts '           MESSAGE SENT                   '
      puts '=========================================='
      puts ' Status  : Success (201)'
      puts " ID      : #{msg['id']}"
      puts " Phone   : #{msg['phone']}"
      puts " Text    : #{msg['text']}"
      puts '=========================================='
    else
      puts '=========================================='
      puts '       FAILED TO SEND MESSAGE             '
      puts '=========================================='
      puts " Error   : #{res['error'] || 'Failed to send'}"
      puts '=========================================='
    end
  when 'list'
    res = http_request(:get, ENDPOINT)
    if raw_json
      puts JSON.pretty_generate(res)
    else
      puts '=========================================='
      puts '           MESSAGE LIST                   '
      puts '=========================================='
      puts " Total Count : #{res['totalMessages'] || 0}"
      puts " Data        : #{res.to_json}"
      puts '=========================================='
    end
  when 'delete'
    id_idx = args.index('--id')
    id = id_idx ? args[id_idx + 1] : nil

    url = id ? "#{ENDPOINT}?id=#{id}" : ENDPOINT
    res = http_request(:delete, url)

    if raw_json
      puts JSON.pretty_generate(res)
    else
      puts '=========================================='
      puts '           DELETE MESSAGES                '
      puts '=========================================='
      puts " Response : #{res.to_json}"
      puts '=========================================='
    end
  end
end

.http_request(method, url_str, payload = nil) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/messages_tool.rb', line 75

def http_request(method, url_str, payload = nil)
  uri = URI.parse(url_str)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = (uri.scheme == 'https')

  request = case method
            when :get then Net::HTTP::Get.new(uri)
            when :post then Net::HTTP::Post.new(uri)
            when :delete then Net::HTTP::Delete.new(uri)
            end

  request['Content-Type'] = 'application/json'
  request.body = payload.to_json if payload

  response = http.request(request)
  JSON.parse(response.body)
rescue StandardError => e
  { 'error' => e.message }
end

.load_authObject



16
17
18
19
20
21
22
# File 'lib/messages_tool.rb', line 16

def load_auth
  return {} unless File.exist?(AUTH_FILE)

  JSON.parse(File.read(AUTH_FILE))
rescue StandardError
  {}
end

.prompt_pin(label = 'Enter PIN: ') ⇒ Object



43
44
45
46
47
48
49
50
51
52
# File 'lib/messages_tool.rb', line 43

def prompt_pin(label = 'Enter PIN: ')
  auth = load_auth
  if auth['use_application_default'] && auth['pin']
    puts "#{label}[Application Default Active]"
    return auth['pin']
  end

  print label
  $stdin.noecho(&:gets)&.chomp.tap { puts }
end

.require_authObject



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/messages_tool.rb', line 54

def require_auth
  auth = load_auth
  unless auth['registered']
    puts '[-] No PIN registered. Run `messages-tool auth register` or `account register` first.'
    exit 1
  end

  unless auth['logged_in'] && File.exist?(LOCK_FILE)
    puts '[-] Not logged in. Please log in first.'
    exit 1
  end

  input_pin = prompt_pin('Enter PIN to continue: ')
  unless input_pin == auth['pin']
    puts '[-] Invalid PIN.'
    exit 1
  end

  auth
end

.run(argv) ⇒ Object



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/messages_tool.rb', line 391

def run(argv)
  raw_json_flag = argv.delete('--json') != nil

  if argv.empty? || argv.include?('--help') || argv.include?('-h')
    show_main_help
    exit 0
  end

  if argv.include?('--version') || argv.include?('-v')
    puts "messages-tool v#{VERSION}"
    exit 0
  end

  group = argv.shift
  subcmd = argv.shift

  case group
  when 'auth', 'account'
    handle_auth(subcmd, argv)
  when 'messages', 'message'
    handle_messages(subcmd, argv, raw_json: raw_json_flag)
  when 'send', 'list', 'delete'
    handle_messages(group, [subcmd] + argv, raw_json: raw_json_flag)
  else
    show_main_help
  end
end

.save_auth(data) ⇒ Object



24
25
26
27
# File 'lib/messages_tool.rb', line 24

def save_auth(data)
  File.write(AUTH_FILE, JSON.pretty_generate(data))
  sync_lock_file(data)
end

.show_app_default_helpObject



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/messages_tool.rb', line 127

def show_app_default_help
  puts <<~HELP
    =========================================="
       APPLICATION DEFAULT AUTHENTICATION      "
    =========================================="
    Commands Configuration:
      application-default register   Register application default PIN
      application-default login      Authenticate session using default PIN
      application-default logout     Log out of default session
      application-default status     Show default status & lock settings
      application-default reset      Reset application default PIN
      application-default remove     Remove stored default credentials
      application-default list       Print JSON of default credentials configuration
      application-default help       Show this application-default help menu
    =========================================="
  HELP
end

.show_auth_helpObject



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/messages_tool.rb', line 95

def show_auth_help
  puts <<~HELP
    =========================================="
               AUTH / ACCOUNT HELP            "
    =========================================="
    Commands Configuration:
      auth / account register                     Set initial PIN (prompts: Enter new PIN, Confirm new PIN)
      auth / account login                        Set active session & write pin.lock (prompts: Enter PIN to login)
      auth / account logout                       End session & remove pin.lock (prompts: Enter PIN to logout)
      auth / account status                       Display session state (prompts: Enter PIN for status)
      auth / account reset                        Change existing PIN (prompts: Enter PIN to continue, Enter new PIN to reset, Confirm new PIN)
      auth / account remove                       Clear authentication file & pin.lock (prompts: Enter PIN to remove)
      auth / account list                         Display session settings JSON (prompts: Enter PIN to list auth data)
      auth / account help                         Display this settings configuration menu

    Application-Default Commands:
      auth / account application-default help     Display application-default configuration menu
      auth / account application-default register Register default application credentials
      auth / account application-default login    Login using default application credentials
      auth / account application-default logout   Logout default session
      auth / account application-default status   Check default application status
      auth / account application-default reset    Reset default application PIN
      auth / account application-default remove   Remove application default configuration
      auth / account application-default list     List default auth settings JSON

    PIN & Lock Settings:
      - Masked input enabled via console hidden input.
      - Session state synchronized across `.auth_session.json` and `pin.lock`.
    =========================================="
  HELP
end

.show_main_helpObject



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/messages_tool.rb', line 145

def show_main_help
  puts <<~USAGE
    Usage: messages-tool <category> <command> [options]

    Global Options:
      --help, -h       Show main CLI options/help
      --version, -v    Show script version
      --json           Output raw API results formatted as JSON

    Auth / Account Commands:
      auth / account help                            Display auth settings and manual
      auth / account register                        Register PIN (prompts: Enter new PIN, Confirm new PIN)
      auth / account login                           Login (prompts: Enter PIN to login)
      auth / account logout                          Logout (prompts: Enter PIN to logout)
      auth / account status                          Check status (prompts: Enter PIN for status)
      auth / account reset                           Reset PIN (prompts: Enter PIN to continue, Enter new PIN to reset, Confirm new PIN)
      auth / account remove                          Remove auth data and lock file (prompts: Enter PIN to remove)
      auth / account list                            List auth session JSON (prompts: Enter PIN to list auth data)
      auth / account application-default <cmd>       Manage application-default credentials

    Messages Commands:
      messages send --number <phone> --text <text>
      message send --number <phone> --text <text>
      send --number <phone> --text <text>

      messages list / message list / list
      messages delete [--id <id>] / message delete [--id <id>] / delete [--id <id>]
  USAGE
end

.sync_lock_file(data) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/messages_tool.rb', line 29

def sync_lock_file(data)
  if data['logged_in']
    lock_data = {
      'status' => 'locked',
      'registered' => data['registered'],
      'use_application_default' => !!data['use_application_default'],
      'timestamp' => Time.now.to_i
    }
    File.write(LOCK_FILE, JSON.pretty_generate(lock_data))
  else
    FileUtils.rm_f(LOCK_FILE)
  end
end