require "option_parser" NOTIFY_ICON = {{env("NOTIFY_ICON")}} NOTIFY_SEND_COMMAND = {{env("NOTIFY_SEND")}} WL_COPY_COMMAND = {{env("WL_COPY")}} YKMAN_COMMAND = {{env("YKMAN")}} SKIM_COMMAND = {{env("SKIM")}} WALKER_COMMAND = {{env("WALKER")}} class OTP private property dmenu_command : String = SKIM_COMMAND private property dmenu_args = [] of String def run parser = OptionParser.new do |opts| opts.banner = "Usage: #{PROGRAM_NAME} [options]" opts.on("-h", "--help", "Display this help message") do puts opts exit end opts.on("-w", "--walker", "Use walker as dmenu") do self.dmenu_command = WALKER_COMMAND self.dmenu_args << "--dmenu" << "-p" << "Select account:" end end parser.parse self.dmenu_args += ["-q", *ARGV] unless ARGV.empty? account = select_account code = code_for(account) copy(code) end private def notify_send(message, urgency = :normal) return if message.chomp.empty? Process.run(NOTIFY_SEND_COMMAND, ["-i", NOTIFY_ICON, "-u", urgency.to_s, "OTP", message.chomp]) end private def list_accounts error = IO::Memory.new output = IO::Memory.new status = Process.run(YKMAN_COMMAND, %w[oath accounts list], output: output, error: error) errors = error.rewind.gets_to_end.try(&.chomp) notify_send(errors, urgency: status.success? ? :normal : :critical) unless errors.empty? exit status.exit_code unless status.success? output.rewind.gets_to_end end private def select_account output = IO::Memory.new accounts = list_accounts puts accounts input = IO::Memory.new(accounts) Process.run(dmenu_command, dmenu_args, output: output, input: input) account = output.rewind.gets(chomp: true) return account if account notify_send("No account selected", urgency: :critical) exit 1 end private def code_for(account) output = IO::Memory.new Process.run(YKMAN_COMMAND, ["oath", "accounts", "code", "-s", account], output: output) do |process| spawn do while line = process.error.gets(chomp: true) notify_send(line) end end end exit $?.exit_code unless $?.success? code = output.rewind.gets(chomp: true) return code if code notify_send("No code generated", urgency: :critical) exit 1 end private def copy(code) input = IO::Memory.new(code) Process.run(WL_COPY_COMMAND, %w[-n], input: input) notify_send("Copied to clipboard") end end OTP.new.run