From 7a5afa2f9cf9f3f2b3346b2d176960ab9af8e65e Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 14 Jul 2019 02:31:37 -0500 Subject: [PATCH 01/25] work in progress --- bot_config.yaml.example | 8 +++++++- commands.rb | 19 ++++++++++++++++++- run.rb | 24 +++++++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/bot_config.yaml.example b/bot_config.yaml.example index 93ff3b4..6f394cd 100644 --- a/bot_config.yaml.example +++ b/bot_config.yaml.example @@ -9,4 +9,10 @@ allowed_sources: - 'private' - 'group' - 'supergroup' - +probes: + srv1: + loc: 'Office' + srv2: + loc: 'Area 50.1' + srv3: + loc: 'Dungeon' diff --git a/commands.rb b/commands.rb index 4d83a4f..2a76bdd 100644 --- a/commands.rb +++ b/commands.rb @@ -36,7 +36,24 @@ end def process_command_check(message, command, adm) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) - reply = `cat fakeoutput.txt` + #reply = `scripts/tempf.py` + #some interactive code to select a probe + select_loc = "Aisle 12-3" + #end interactive code + #probe = @probes[0] #set manually for now + reply = "" + hostdata = host_lookup(select_loc) + puts "var:".blue+" hostdata".blue.bold+"::".bold + hostdata.to_s + if ! hostdata.empty? + hostdata.each do |k,v| + puts "match:".blue+" host".blue.bold+"::".bold + k.to_s + reply += k.to_s+"\n" + end + else + reply = "No matching locations!" + puts "hostdata is empty!" + end + puts reply else reply = "I am not authorized to provide this information here." end diff --git a/run.rb b/run.rb index 2c487cb..57a64af 100755 --- a/run.rb +++ b/run.rb @@ -20,6 +20,7 @@ token = @conf['token'] admin = @conf['admin'] @auth_chat = @conf['authorized_chats'] @allowed_sources = @conf['allowed_sources'] +@probes = @conf['probes'] ### Begin sanity check ### STDOUT.sync = true @@ -104,6 +105,14 @@ if errcount > 0 else print "Environment appears sane.\n\n".green.bold end +if @probes.nil? + errcount += 1 + print "FAIL!\n\n".red.bold + puts "Error(#{errcount.to_s}): No temperature probes configured! Bot will serve no purpose. Continuing anyway....\n\n" +else + print "OK\n".green.bold +end + STDOUT.sync = false ### End sanity check ### puts "Starting [#{@botname}]...\n\n" @@ -114,6 +123,13 @@ puts "Temporary direcotry: #{@tmpdir}" puts "Start time: " + timestamp + "\n\n\n\n" STDOUT.flush +def host_lookup(select_loc) + #@probes.select { |k,v| v['loc'] == select_loc }.each do |host,loc| + # return host + #end + return @probes.select { |k,v| v['loc'] == select_loc } +end + def handle_message(message) if ! message.reply_to_message.nil? then #drop message. Someone's replying to a message @@ -186,7 +202,7 @@ def handle_message(message) reply = "Sorry, #{command} is not a valid command." end # Verbose output: - puts timestamp + ": Sending #{reply.inspect}\n\n" + #print timestamp + ": Sending #{reply.inspect} ..... " STDOUT.flush return reply end @@ -198,8 +214,14 @@ Telegram::Bot::Client.run(token) do |bot| #puts "DEBUG: #{validation}" if validation reply = handle_message(message) + print timestamp + ": Sending ..... " if ! message.text.nil? bot.api.send_message(chat_id: message.chat.id, text: "#{reply}") + print "OK\n".green.bold + puts "Sent: #{reply.inspect}\n\n" + else + print 'FAIL'.red.bold + print " (Message is NULL)\n\n" end STDOUT.flush else From c5063e493304f377a581c094962066bd5c569bce Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 25 Aug 2019 21:13:36 -0500 Subject: [PATCH 02/25] Fixed probe check position --- run.rb | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/run.rb b/run.rb index 57a64af..e2a8709 100755 --- a/run.rb +++ b/run.rb @@ -99,12 +99,7 @@ if @auth_chat.nil? else print "OK\n".green.bold end -puts "Errors found: #{errcount.to_s}\n\n" -if errcount > 0 - print "Environment is grinning and holding a spatula. Please review your configuration.\n\n".red.bold -else - print "Environment appears sane.\n\n".green.bold -end +print "Checking probes list .................... " if @probes.nil? errcount += 1 print "FAIL!\n\n".red.bold @@ -112,6 +107,12 @@ if @probes.nil? else print "OK\n".green.bold end +puts "Errors found: #{errcount.to_s}\n\n" +if errcount > 0 + print "Environment is grinning and holding a spatula. Please review your configuration.\n\n".red.bold +else + print "Environment appears sane.\n\n".green.bold +end STDOUT.sync = false ### End sanity check ### @@ -158,6 +159,17 @@ def handle_message(message) #Format command command = message.text.split(" ")[0].split("@")[0].downcase #Strip command from arguments and @tags + ## Constant collection model (may be scrapped) + #last_collection = 0 + #loop do + # collection_time = Time.now + # if collection_time - last_collection >= 10 + # puts timestamp + ": [#{collection_time}] Updating records //Not really" + # last_collection = collection_time + # STDOUT.flush + # end + #end + reply = 'Empty String' adm = @conf['admin'] puts Time.now.strftime("%F %H:%M:%S").yellow + ": Received command from " + "#{message.from.username}".cyan.bold + " [" + "#{message.from.id}".cyan + "]: " + "#{command}".magenta.bold From fc862ade079228954865ca1e9c388ff463765dfc Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 25 Aug 2019 21:59:27 -0500 Subject: [PATCH 03/25] No value added; preparing massive rewrite --- commands.rb | 32 ++++++++++++++++++++++++++++++-- run.rb | 10 +++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/commands.rb b/commands.rb index 2a76bdd..2c42a9c 100644 --- a/commands.rb +++ b/commands.rb @@ -35,10 +35,39 @@ def process_command_patchnotes(message, command, adm) end def process_command_check(message, command, adm) + if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) + #begin interactive code + options = [ ] + @probes.each do |k,v| + button_text = v + options.insert(-1, Telegram::Bot::Types::InlineKeyboardButton.new(text: button_text, callback_data: "#{k}")) + end + #end interactive code + #probe = @probes[0] #set manually for now + reply = "N/A" + #hostdata = host_lookup(select_loc) + #puts "var:".blue+" hostdata".blue.bold+"::".bold + hostdata.to_s + #if ! hostdata.empty? + # hostdata.each do |k,v| + # puts "match:".blue+" host".blue.bold+"::".bold + k.to_s + # reply += k.to_s+"\n" + # end + #else + # reply = "No matching locations!" + # puts "hostdata is empty!" + #end + puts reply + else + reply = "I am not authorized to provide this information here." + end + return reply +end + +def process_command_test(message, command, adm) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) #reply = `scripts/tempf.py` #some interactive code to select a probe - select_loc = "Aisle 12-3" + select_loc = "Server Room: Intake" #end interactive code #probe = @probes[0] #set manually for now reply = "" @@ -59,4 +88,3 @@ def process_command_check(message, command, adm) end return reply end - diff --git a/run.rb b/run.rb index e2a8709..33785a3 100755 --- a/run.rb +++ b/run.rb @@ -207,6 +207,8 @@ def handle_message(message) reply = "User ID: #{message.from.id}\nChat ID: #{message.chat.id}" when '/check' reply = process_command_check(message, command, adm) + when '/test' + reply = process_command_test(message, command, adm) when '/pp', '/debug' pp message reply = "Confirmation: Message debug information sent to console." @@ -225,7 +227,13 @@ Telegram::Bot::Client.run(token) do |bot| validation = validate_incoming_data(message) #puts "DEBUG: #{validation}" if validation - reply = handle_message(message) + #reply = handle_message(message) + #case message + # when Telegram::Bot::Types::Message + reply = handle_message(message) #entrypoint for all messages + # when Telegram::Bot::Types::CallbackQuery + # reply = handle_callback_query(message) #entrypoint for all callback queries + #end print timestamp + ": Sending ..... " if ! message.text.nil? bot.api.send_message(chat_id: message.chat.id, text: "#{reply}") From 7e130f09e40ed4597659b9f7457c18e92dbffbd6 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Mon, 26 Aug 2019 00:45:06 -0500 Subject: [PATCH 04/25] Rewrite phase 1 --- callbacks.rb | 4 + commands.rb | 57 ++++- run.rb | 235 +++++++++++++------ patchnotes.txt => static_text/patchnotes.txt | 0 4 files changed, 216 insertions(+), 80 deletions(-) create mode 100644 callbacks.rb rename patchnotes.txt => static_text/patchnotes.txt (100%) diff --git a/callbacks.rb b/callbacks.rb new file mode 100644 index 0000000..a2f467b --- /dev/null +++ b/callbacks.rb @@ -0,0 +1,4 @@ +def process_callback_zone(message) + host = message.data.split("|")[1] + send_message(message.message.chat.id, "You have selected #{host}!") +end diff --git a/commands.rb b/commands.rb index 2c42a9c..67c076e 100644 --- a/commands.rb +++ b/commands.rb @@ -25,13 +25,53 @@ def is_chat_authorized?(message, auth_chat) end end +def process_command_start(message, command, adm) + reply = "I am #{@botname}, and I am here to provide temperature information from various sensors. Currently I can retrieve information " + + "from temperature sensors as well as report when temperatures are out of a specified range.\n\n" + + "Commands available:\n/start (Shows this message)\n/check (Show temperatures, currently non-functional)\n/whoami or /chatinfo (Provides IDs for internal use)\n/whereareyou or /location (Provides hostname for bot server)\n\n" + + "Check again later to see if any new functions have been added, or use /patchnotes to learn about recent updates.\n" + + "You can also view the source code at the following location:\nhttps://git.skyfall.tech/skyfall/heatbot" + if message_from_admin?(message, adm) + msg_from_admin = true + end + if is_chat_authorized?(message, @auth_chat) + chat_authorized = true + end + if ! msg_from_admin && ! chat_authorized + if message.from.id == message.chat.id + reply = reply + "\n\nWARNING: I am not authorized to work with you directly. My functionality is limited." + else + reply = reply + "\n\nWARNING: I am not authorized to participate with this group. My functionality is limited." + end + elsif msg_from_admin && ! chat_authorized + reply = reply + "\n\nWARNING: Although you are an administrator, I have not been authorized to participate in this group. My functionality is limited." + end + send_message(message.chat.id, reply) +end + def process_command_patchnotes(message, command, adm) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) - reply = `cat patchnotes.txt` + reply = File.read(static_text/patchnotes.txt) else reply = "I am not authorized to provide this information here." end - return reply + send_message(message.chat.id, reply) +end + +def process_command_location(message, command, adm) + if message_from_admin?(message, adm) || is_chat_authorized?(message, @auth_chat) + #reply = "I am currently located at:\n\nHost: \\\`#{`head -n1 /etc/hostname`}\\\`ExtIP: \\\`#{`curl icanhazip.com 2>/dev/null`}\\\`" + reply = "I am currently located at:\n\nHost: #{`head -n1 /etc/hostname`}ExtIP: #{`curl icanhazip.com 2>/dev/null`}" + else + reply = "I am not authorized to provide this information here." + end + #send_message_markdown(message.chat.id, reply) + send_message(message.chat.id, reply) +end + +def process_command_chatinfo(message) + reply = "User ID: #{message.from.id}\nChat ID: #{message.chat.id}" + send_message(message.chat.id, reply) end def process_command_check(message, command, adm) @@ -39,12 +79,15 @@ def process_command_check(message, command, adm) #begin interactive code options = [ ] @probes.each do |k,v| - button_text = v - options.insert(-1, Telegram::Bot::Types::InlineKeyboardButton.new(text: button_text, callback_data: "#{k}")) + puts " Option: #{k} is #{v["loc"].to_s}" + button_text = v["loc"].to_s + options.insert(-1, Telegram::Bot::Types::InlineKeyboardButton.new(text: button_text, callback_data: "ZONE|#{k}")) end + + message_text = "Which area would you like to check?\n" + send_question(message.chat.id, message_text, options) #end interactive code #probe = @probes[0] #set manually for now - reply = "N/A" #hostdata = host_lookup(select_loc) #puts "var:".blue+" hostdata".blue.bold+"::".bold + hostdata.to_s #if ! hostdata.empty? @@ -56,11 +99,9 @@ def process_command_check(message, command, adm) # reply = "No matching locations!" # puts "hostdata is empty!" #end - puts reply else - reply = "I am not authorized to provide this information here." + send_message(message.chat.id,"I am not authorized to provide this information here.") end - return reply end def process_command_test(message, command, adm) diff --git a/run.rb b/run.rb index 33785a3..10cf42f 100755 --- a/run.rb +++ b/run.rb @@ -6,6 +6,7 @@ require 'telegram/bot' require 'pp' require 'time' require_relative 'commands.rb' +require_relative 'callbacks.rb' require_relative 'colors.rb' def timestamp @@ -16,7 +17,7 @@ end @conf = YAML.load_file("bot_config.yaml") @botname = @conf['botname'] @tmpdir = @conf['tmpdir'] -token = @conf['token'] +@token = @conf['token'] admin = @conf['admin'] @auth_chat = @conf['authorized_chats'] @allowed_sources = @conf['allowed_sources'] @@ -27,9 +28,9 @@ STDOUT.sync = true errcount = 0 puts "Checking if environment is sane...\n\n" print "Checking bot token ...................... " -if token.nil? +if @token.nil? print "FAIL!\n\n".red.bold - puts "No bot token defined in bot_config.yaml!\n" + "THIS IS REQUIRED!".red.bold + " Bot initialization failed; exiting..." + puts "No bot @token defined in bot_config.yaml!\n" + "THIS IS REQUIRED!".red.bold + " Bot initialization failed; exiting..." exit(1) else print "OK\n".green.bold @@ -119,11 +120,22 @@ STDOUT.sync = false puts "Starting [#{@botname}]...\n\n" puts "Authorized administrator IDs: #{admin}" puts "Authorized chat IDs: #{@auth_chat}" -puts "Bot token: #{token}" +puts "Bot @token: #{@token}" puts "Temporary direcotry: #{@tmpdir}" puts "Start time: " + timestamp + "\n\n\n\n" STDOUT.flush +## Constant collection model (may be scrapped) +#last_collection = 0 +#loop do +# collection_time = Time.now +# if collection_time - last_collection >= 10 +# puts timestamp + ": [#{collection_time}] Updating records //Not really" +# last_collection = collection_time +# STDOUT.flush +# end +#end + def host_lookup(select_loc) #@probes.select { |k,v| v['loc'] == select_loc }.each do |host,loc| # return host @@ -131,21 +143,83 @@ def host_lookup(select_loc) return @probes.select { |k,v| v['loc'] == select_loc } end +def ack_callback(message, display_message = true) + #Delete message and notify user that we got the request + begin + Telegram::Bot::Client.run(@token) do |bot| + if display_message == true + bot.api.editMessageText(chat_id: message.message.chat.id, message_id: message.message.message_id, text: "#{message.from.username}Request received. Please wait...", reply_markup: "") #Removes buttons. Changes text + bot.api.answerCallbackQuery(callback_query_id: message.id, show_alert: false, text: "Request received. Please wait...") #Sends a pop-up notification + else + bot.api.deleteMessage(chat_id: message.message.chat.id, message_id: message.message.message_id) #Deletes message and buttons + end + end + rescue + puts "Error handling callback query. Error: " + $!.message + end + STDOUT.flush +end + +def delete_message(message) + #Deletes a message referred to by message_id + begin + Telegram::Bot::Client.run(@token) do |bot| + bot.api.deleteMessage(chat_id: message.message.chat.id, message_id: message.message.message_id) #Deletes message and buttons + end + rescue + puts "Error deleting message. Error: " + $!.message + end + STDOUT.flush +end + +def send_message(chatid, message_text, imageurl = nil) + if imageurl != nil + #Send message with text as html link to image + Telegram::Bot::Client.run(@token) {|bot| bot.api.send_message(chat_id: chatid, text: "#{message_text}.", parse_mode: "HTML") } + puts timestamp + ": Sent: #{message_text.inspect}\n\n" + else + #Send a plain-text message + Telegram::Bot::Client.run(@token) {|bot| bot.api.send_message(chat_id: chatid, text: message_text) } + puts timestamp + ": Sent: #{message_text.inspect}\n\n" + end + STDOUT.flush +end + +def send_message_markdown(chatid, message_text) + #Send a plain-text message + Telegram::Bot::Client.run(@token) {|bot| bot.api.send_message(chat_id: chatid, text: "```#{message_text}```", parse_mode: 'Markdown') } + puts timestamp + ": Sent: #{message_text.inspect}\n\n" + STDOUT.flush +end + +def send_question(chatid, question_text, answers = [ ]) + if ! answers.empty? then + begin + keyboard = Telegram::Bot::Types::InlineKeyboardMarkup.new(inline_keyboard: answers) + Telegram::Bot::Client.run(@token) {|bot| bot.api.send_message(chat_id: chatid, text: question_text, reply_markup: keyboard) } + rescue + puts timestamp + ": " + "ERROR".red.bold + ": " + $!.message + end + else + puts timestamp + "send_question called without any possible answers provided" + end + STDOUT.flush +end + def handle_message(message) if ! message.reply_to_message.nil? then - #drop message. Someone's replying to a message - #sent by our bot + #drop message. Someone's replying to a message sent by our bot message.text = nil return end if message.text.nil? # Find out if user(s) joined the group. If so, welcome them - #if ! message.new_chat_members.nil? - # handle_user_join(message) - #else - # #Handle non-messages and non-joins here - #end + if ! message.new_chat_members.nil? + handle_user_join(message) + else + #Handle non-messages and non-joins here + end return #so that we don't try to process this as a command (below) end @@ -159,91 +233,108 @@ def handle_message(message) #Format command command = message.text.split(" ")[0].split("@")[0].downcase #Strip command from arguments and @tags - ## Constant collection model (may be scrapped) - #last_collection = 0 - #loop do - # collection_time = Time.now - # if collection_time - last_collection >= 10 - # puts timestamp + ": [#{collection_time}] Updating records //Not really" - # last_collection = collection_time - # STDOUT.flush - # end - #end - reply = 'Empty String' adm = @conf['admin'] - puts Time.now.strftime("%F %H:%M:%S").yellow + ": Received command from " + "#{message.from.username}".cyan.bold + " [" + "#{message.from.id}".cyan + "]: " + "#{command}".magenta.bold + puts timestamp + ": Received command from " + "#{message.from.username}".cyan.bold + " [" + "#{message.from.id}".cyan + "]: " + "#{command}".magenta.bold case command when '/start' - reply = "I am #{@botname}, and I am here to provide temperature information from various sensors. Currently I can retrieve information " + - "from temperature sensors as well as report when temperatures are out of a specified range.\n\n" + - "Commands available:\n/start (Shows this message)\n/check (Show temperatures, currently non-functional)\n/whoami or /chatinfo (Provides IDs for internal use)\n/whereareyou or /location (Provides hostname for bot server)\n\n" + - "Check again later to see if any new functions have been added, or use /patchnotes to learn about recent updates.\n" + - "You can also view the source code at the following location:\nhttps://git.skyfall.tech/skyfall/heatbot" - if message_from_admin?(message, adm) - msg_from_admin = true - end - if is_chat_authorized?(message, @auth_chat) - chat_authorized = true - end - if ! msg_from_admin && ! chat_authorized - if message.from.id == message.chat.id - reply = reply + "\n\nWARNING: I am not authorized to work with you directly. My functionality is limited." - else - reply = reply + "\n\nWARNING: I am not authorized to participate with this group. My functionality is limited." - end - elsif msg_from_admin && ! chat_authorized - reply = reply + "\n\nWARNING: Although you are an administrator, I have not been authorized to participate in this group. My functionality is limited." - end - when '/patch', '/patchnotes' - reply = process_command_patchnotes(message, command, adm) + process_command_start(message, command, adm) + when '/patch', '/patchnotes', '/version', '/versioninfo' + process_command_patchnotes(message, command, adm) when '/location', '/whereareyou' - if message_from_admin?(message, adm) || is_chat_authorized?(message, @auth_chat) - reply = "I am currently located at:\n\nHost: #{`head -n1 /etc/hostname`}ExtIP: #{`curl icanhazip.com 2>/dev/null`}" - else - reply = "I am not authorized to provide this information here." - end + process_command_location(message, command, adm) when '/whoami', '/chatinfo' - reply = "User ID: #{message.from.id}\nChat ID: #{message.chat.id}" + process_command_chatinfo(message) when '/check' - reply = process_command_check(message, command, adm) + process_command_check(message, command, adm) when '/test' - reply = process_command_test(message, command, adm) + process_command_test(message, command, adm) when '/pp', '/debug' pp message - reply = "Confirmation: Message debug information sent to console." + send_message(message.chat.id"Confirmation: Message debug information sent to console.") else - reply = "Sorry, #{command} is not a valid command." + send_message(message.chat.id,"Sorry, #{command} is not a valid command.") end + rescue => e + handle_exception(e, message, true) # Verbose output: #print timestamp + ": Sending #{reply.inspect} ..... " STDOUT.flush return reply end -Telegram::Bot::Client.run(token) do |bot| +def handle_callback_query(message) + #callbacks that start with a "!" ("!DMS|tt123456") can ONLY be submitted + #by an admin. Ignore if normal user presses + + #Get "DLM" from "DLM|abc123" + command = message.data.split("|")[0].upcase + + if command.start_with?('!') then + #verify an admin pressed this button + if ! message_from_admin?(message) + Telegram::Bot::Client.run(@token) {|bot| bot.api.answerCallbackQuery(callback_query_id: message.id, show_alert: false, text: "Requires admin approval")} + return + end + command = command.split("!")[1] + elsif command.start_with?('#') then + #is this an ignored command + return + end + + ack_callback(message) #Change the movie/show selection message to "Request received..." + + case command + when "ZONE" #Get temp info from location + process_callback_zone(message) + end + + delete_message(message) #Delete the "Request received..." message +rescue => e + handle_exception(e, message, true) +end + + +def handle_exception(e, message, notify_users) + puts "=" * 60 + puts "EXCEPTION OCCURRED!".red.bold + puts "=" * 60 + puts "PRINTING INSPECT...".yellow.bold + puts e.inspect + puts "=" * 60 + puts "PRINTING BACKTRACE...".yellow.bold + puts e.backtrace + puts "=" * 60 + if notify_users == true then + #is this a callback query or a message + case message + when Telegram::Bot::Types::Message + send_message(message.chat.id, "I have run into an issue while processing a command.\n\nPlease notify an administrator.") + when Telegram::Bot::Types::CallbackQuery + send_message(message.message.chat.id, "I have run into an issue while processing a request.\n\nPlease notify an administrator.") + end + end +end + +Telegram::Bot::Client.run(@token) do |bot| bot.listen do |message| #pp message validation = validate_incoming_data(message) #puts "DEBUG: #{validation}" if validation - #reply = handle_message(message) - #case message - # when Telegram::Bot::Types::Message - reply = handle_message(message) #entrypoint for all messages - # when Telegram::Bot::Types::CallbackQuery - # reply = handle_callback_query(message) #entrypoint for all callback queries - #end - print timestamp + ": Sending ..... " - if ! message.text.nil? - bot.api.send_message(chat_id: message.chat.id, text: "#{reply}") - print "OK\n".green.bold - puts "Sent: #{reply.inspect}\n\n" - else - print 'FAIL'.red.bold - print " (Message is NULL)\n\n" + #Change message.from.username to something we can call the user. This makes referring to the user in replies much easier. + if ! message.from.username.nil? #Username -> @Username + #message.from.username = "@" + message.from.username + elsif ! message.from.first_name.nil? #Username -> John + message.from.username = message.from.first_name + end + + case message + when Telegram::Bot::Types::Message + handle_message(message) #entrypoint for all messages + when Telegram::Bot::Types::CallbackQuery + handle_callback_query(message) #entrypoint for all callback queries end - STDOUT.flush else puts "Received bad data! [#{message.chat.type}]" puts validation diff --git a/patchnotes.txt b/static_text/patchnotes.txt similarity index 100% rename from patchnotes.txt rename to static_text/patchnotes.txt From fd63e182b18b4f056722b0acba72890ac34cbe92 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sat, 7 Sep 2019 16:41:08 -0500 Subject: [PATCH 05/25] removed extraneous symbol from sanity report --- run.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.rb b/run.rb index 10cf42f..8c948ca 100755 --- a/run.rb +++ b/run.rb @@ -120,7 +120,7 @@ STDOUT.sync = false puts "Starting [#{@botname}]...\n\n" puts "Authorized administrator IDs: #{admin}" puts "Authorized chat IDs: #{@auth_chat}" -puts "Bot @token: #{@token}" +puts "Bot token: #{@token}" puts "Temporary direcotry: #{@tmpdir}" puts "Start time: " + timestamp + "\n\n\n\n" STDOUT.flush From 78bd688a0be05884dc5796183054fd46c2b1439d Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 12 Sep 2019 19:54:40 -0500 Subject: [PATCH 06/25] Added units configuration --- bot_config.yaml.example | 1 + 1 file changed, 1 insertion(+) diff --git a/bot_config.yaml.example b/bot_config.yaml.example index 6f394cd..4f8f611 100644 --- a/bot_config.yaml.example +++ b/bot_config.yaml.example @@ -9,6 +9,7 @@ allowed_sources: - 'private' - 'group' - 'supergroup' +units: 'F' probes: srv1: loc: 'Office' From e8f44866c348c0f4c180e6b5962d4fc94ad65790 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 12 Sep 2019 19:55:41 -0500 Subject: [PATCH 07/25] Added /c alias and sanity check for temperature unit conf --- run.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/run.rb b/run.rb index 8c948ca..87120c9 100755 --- a/run.rb +++ b/run.rb @@ -22,6 +22,7 @@ admin = @conf['admin'] @auth_chat = @conf['authorized_chats'] @allowed_sources = @conf['allowed_sources'] @probes = @conf['probes'] +@tunit = @conf['units'] ### Begin sanity check ### STDOUT.sync = true @@ -100,6 +101,14 @@ if @auth_chat.nil? else print "OK\n".green.bold end +print "Checking temperature units .............. " +if @tunit.nil? + errcount += 1 + print "FAIL!\n\n".red.bold + puts "Error(#{errcount.to_s}): No temperature probes configured! Bot will serve no purpose. Continuing anyway....\n\n" +else + print "OK\n".green.bold +end print "Checking probes list .................... " if @probes.nil? errcount += 1 @@ -122,6 +131,11 @@ puts "Authorized administrator IDs: #{admin}" puts "Authorized chat IDs: #{@auth_chat}" puts "Bot token: #{@token}" puts "Temporary direcotry: #{@tmpdir}" +puts "Temperature unit: #{@tunit}" +puts "Probes loaded:" +@probes.each do |k,v| + puts " [#{k}] #{v["loc"].to_s}" +end puts "Start time: " + timestamp + "\n\n\n\n" STDOUT.flush @@ -148,7 +162,7 @@ def ack_callback(message, display_message = true) begin Telegram::Bot::Client.run(@token) do |bot| if display_message == true - bot.api.editMessageText(chat_id: message.message.chat.id, message_id: message.message.message_id, text: "#{message.from.username}Request received. Please wait...", reply_markup: "") #Removes buttons. Changes text + bot.api.editMessageText(chat_id: message.message.chat.id, message_id: message.message.message_id, text: "Request received. Please wait...", reply_markup: "") #Removes buttons. Changes text bot.api.answerCallbackQuery(callback_query_id: message.id, show_alert: false, text: "Request received. Please wait...") #Sends a pop-up notification else bot.api.deleteMessage(chat_id: message.message.chat.id, message_id: message.message.message_id) #Deletes message and buttons @@ -245,7 +259,7 @@ def handle_message(message) process_command_location(message, command, adm) when '/whoami', '/chatinfo' process_command_chatinfo(message) - when '/check' + when '/check', '/c' process_command_check(message, command, adm) when '/test' process_command_test(message, command, adm) From 6ec2ecb4d3b69b8f907953bafe1f2be97edb4b4c Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 12 Sep 2019 19:56:25 -0500 Subject: [PATCH 08/25] Added function to connect to sensor hosts and collect/send data --- callbacks.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/callbacks.rb b/callbacks.rb index a2f467b..00a2f82 100644 --- a/callbacks.rb +++ b/callbacks.rb @@ -1,4 +1,7 @@ def process_callback_zone(message) host = message.data.split("|")[1] - send_message(message.message.chat.id, "You have selected #{host}!") + tdata = `ssh #{host} heatbot_gettemp` + zone = @probes[host].values.first + puts "Selected: #{zone}" + send_message(message.message.chat.id, "#{zone}: #{tdata}°#{@tunit}") end From 07b7f46dd70849f470ccdcbcee4571f702ae709c Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 12 Sep 2019 20:13:54 -0500 Subject: [PATCH 09/25] Added full report function --- commands.rb | 17 +++++++++++++++++ run.rb | 2 ++ 2 files changed, 19 insertions(+) diff --git a/commands.rb b/commands.rb index 67c076e..18efa1b 100644 --- a/commands.rb +++ b/commands.rb @@ -104,6 +104,23 @@ def process_command_check(message, command, adm) end end +def process_command_report(message, command, adm) + if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) + report = "Full Report:\n\n" + @probes.each do |k,v| + zone = v["loc"].to_s + host = k + print "#{host}: #{zone} " + tdata = `ssh #{host} heatbot_gettemp` + print "[#{tdata}°#{@tunit}]\n" + report = report + "#{zone}: #{tdata}°#{@tunit}\n" + end + send_message(message.chat.id,report) + else + send_message(message.chat.id,"I am not authorized to provide this information here.") + end +end + def process_command_test(message, command, adm) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) #reply = `scripts/tempf.py` diff --git a/run.rb b/run.rb index 87120c9..ddfaa3f 100755 --- a/run.rb +++ b/run.rb @@ -261,6 +261,8 @@ def handle_message(message) process_command_chatinfo(message) when '/check', '/c' process_command_check(message, command, adm) + when '/report', '/r' + process_command_report(message, command, adm) when '/test' process_command_test(message, command, adm) when '/pp', '/debug' From ae764682030b39d52aad368120714bd13c8992d9 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 15 Sep 2019 16:28:33 -0500 Subject: [PATCH 10/25] Added error checking to probe processing --- callbacks.rb | 6 ++++-- commands.rb | 29 ++++++++++++---------------- run.rb | 53 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/callbacks.rb b/callbacks.rb index 00a2f82..6ff5854 100644 --- a/callbacks.rb +++ b/callbacks.rb @@ -1,7 +1,9 @@ def process_callback_zone(message) host = message.data.split("|")[1] - tdata = `ssh #{host} heatbot_gettemp` zone = @probes[host].values.first puts "Selected: #{zone}" - send_message(message.message.chat.id, "#{zone}: #{tdata}°#{@tunit}") + tdata = process_tdata(host) + #puts "Selected: #{zone} [#{tdata}]" + send_message(message.message.chat.id, "#{zone}: #{tdata}") + STDOUT.flush end diff --git a/commands.rb b/commands.rb index 18efa1b..4129ef8 100644 --- a/commands.rb +++ b/commands.rb @@ -28,7 +28,7 @@ end def process_command_start(message, command, adm) reply = "I am #{@botname}, and I am here to provide temperature information from various sensors. Currently I can retrieve information " + "from temperature sensors as well as report when temperatures are out of a specified range.\n\n" + - "Commands available:\n/start (Shows this message)\n/check (Show temperatures, currently non-functional)\n/whoami or /chatinfo (Provides IDs for internal use)\n/whereareyou or /location (Provides hostname for bot server)\n\n" + + "Commands available:\n/start (Shows this message)\n/check or /c (Show temperatures)\n/report or /r (Show all temperatures)\n/whoami or /chatinfo (Provides IDs for internal use)\n/whereareyou or /location (Provides hostname for bot server)\n\n" + "Check again later to see if any new functions have been added, or use /patchnotes to learn about recent updates.\n" + "You can also view the source code at the following location:\nhttps://git.skyfall.tech/skyfall/heatbot" if message_from_admin?(message, adm) @@ -87,18 +87,6 @@ def process_command_check(message, command, adm) message_text = "Which area would you like to check?\n" send_question(message.chat.id, message_text, options) #end interactive code - #probe = @probes[0] #set manually for now - #hostdata = host_lookup(select_loc) - #puts "var:".blue+" hostdata".blue.bold+"::".bold + hostdata.to_s - #if ! hostdata.empty? - # hostdata.each do |k,v| - # puts "match:".blue+" host".blue.bold+"::".bold + k.to_s - # reply += k.to_s+"\n" - # end - #else - # reply = "No matching locations!" - # puts "hostdata is empty!" - #end else send_message(message.chat.id,"I am not authorized to provide this information here.") end @@ -106,16 +94,23 @@ end def process_command_report(message, command, adm) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) + confirmation = send_message(message.chat.id,"Generating full report... Please wait.") report = "Full Report:\n\n" @probes.each do |k,v| zone = v["loc"].to_s host = k - print "#{host}: #{zone} " - tdata = `ssh #{host} heatbot_gettemp` - print "[#{tdata}°#{@tunit}]\n" - report = report + "#{zone}: #{tdata}°#{@tunit}\n" + #print "#{host}: #{zone} " + tdata = process_tdata(host) + #tdata = `ssh #{host} heatbot_gettemp` + #print "[#{tdata}°#{@tunit}]\n" + #report = report + "#{zone}: #{tdata}°#{@tunit}\n" + #print "[#{tdata}]\n" + report = report + "#{zone}: #{tdata}\n" + STDOUT.flush end send_message(message.chat.id,report) + #delete_message(confirmation) + #delete_confirmation(confirmation) else send_message(message.chat.id,"I am not authorized to provide this information here.") end diff --git a/run.rb b/run.rb index ddfaa3f..283938e 100755 --- a/run.rb +++ b/run.rb @@ -105,7 +105,8 @@ print "Checking temperature units .............. " if @tunit.nil? errcount += 1 print "FAIL!\n\n".red.bold - puts "Error(#{errcount.to_s}): No temperature probes configured! Bot will serve no purpose. Continuing anyway....\n\n" + puts "Error(#{errcount.to_s}): Temperature units not configured! Defaulting to Rankine. Continuing...\n\n" + @tunit = 'R' else print "OK\n".green.bold end @@ -113,7 +114,7 @@ print "Checking probes list .................... " if @probes.nil? errcount += 1 print "FAIL!\n\n".red.bold - puts "Error(#{errcount.to_s}): No temperature probes configured! Bot will serve no purpose. Continuing anyway....\n\n" + puts "Error(#{errcount.to_s}): No temperature probes configured! Bot will serve no purpose. Continuing anyway...\n\n" else print "OK\n".green.bold end @@ -157,6 +158,29 @@ def host_lookup(select_loc) return @probes.select { |k,v| v['loc'] == select_loc } end +class String + def is_integer? + /\A[-+]?\d+\z/ === self + #!!(self =~ /\A[-+]?[0-9]+\z/) + end +end +def process_tdata(host) + print "Processing #{host}: " + if system("nmap #{host} -p 22 2>&1 | grep 22 | grep open >/dev/null") + tdata = `ssh -oBatchMode=yes #{host} heatbot_gettemp` + if tdata.is_integer? + print tdata + "°#{@tunit}\n" + return tdata + "°#{@tunit}" + else + puts "Unexpected output from [".red.bold + host.bold + "]: ".red.bold + tdata + return "CHECK PROBE" + end + else + print "OFFLINE\n" + return "OFFLINE" + end +end + def ack_callback(message, display_message = true) #Delete message and notify user that we got the request begin @@ -186,15 +210,32 @@ def delete_message(message) STDOUT.flush end +def delete_confirmation(message) + #Deletes a message referred to by message_id + begin + Telegram::Bot::Client.run(@token) do |bot| + bot.api.deleteMessage(chat_id: message.chat.id, message_id: message.message_id) + end + rescue + puts "Error deleting message. Error: " + $!.message + end + STDOUT.flush +end + def send_message(chatid, message_text, imageurl = nil) if imageurl != nil #Send message with text as html link to image - Telegram::Bot::Client.run(@token) {|bot| bot.api.send_message(chat_id: chatid, text: "#{message_text}.", parse_mode: "HTML") } - puts timestamp + ": Sent: #{message_text.inspect}\n\n" + message = Telegram::Bot::Client.run(@token) {|bot| message = bot.api.send_message(chat_id: chatid, text: "#{message_text}.", parse_mode: "HTML") } + #puts timestamp + ": Sent: #{message_text.inspect}\n\n" + return message else #Send a plain-text message - Telegram::Bot::Client.run(@token) {|bot| bot.api.send_message(chat_id: chatid, text: message_text) } + message = Telegram::Bot::Client.run(@token) {|bot| bot.api.send_message(chat_id: chatid, text: message_text) } puts timestamp + ": Sent: #{message_text.inspect}\n\n" + #puts message + #message = message["results"] + #puts message + return message end STDOUT.flush end @@ -298,7 +339,7 @@ def handle_callback_query(message) return end - ack_callback(message) #Change the movie/show selection message to "Request received..." + ack_callback(message) #Change the selection message to "Request received..." case command when "ZONE" #Get temp info from location From 479cc338337c22349d4b52fae2e59ff8708f331c Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 15 Sep 2019 17:01:08 -0500 Subject: [PATCH 11/25] Added simple checks (Easter egg) --- commands.rb | 15 ++++++++++----- run.rb | 29 ++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/commands.rb b/commands.rb index 4129ef8..c2c0f28 100644 --- a/commands.rb +++ b/commands.rb @@ -74,7 +74,7 @@ def process_command_chatinfo(message) send_message(message.chat.id, reply) end -def process_command_check(message, command, adm) +def process_command_check(message, command, adm, simple=false) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) #begin interactive code options = [ ] @@ -92,15 +92,20 @@ def process_command_check(message, command, adm) end end -def process_command_report(message, command, adm) +def process_command_report(message, command, adm, simple=false) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) - confirmation = send_message(message.chat.id,"Generating full report... Please wait.") - report = "Full Report:\n\n" + if !simple + confirmation = send_message(message.chat.id,"Generating full report... Please wait.") + report = "Full Report:\n\n" + else + confirmation = send_message(message.chat.id,"Generating simple report... Please wait.") + report = "Simple Report:\n\n" + end @probes.each do |k,v| zone = v["loc"].to_s host = k #print "#{host}: #{zone} " - tdata = process_tdata(host) + tdata = process_tdata(host, simple) #tdata = `ssh #{host} heatbot_gettemp` #print "[#{tdata}°#{@tunit}]\n" #report = report + "#{zone}: #{tdata}°#{@tunit}\n" diff --git a/run.rb b/run.rb index 283938e..defbe34 100755 --- a/run.rb +++ b/run.rb @@ -164,19 +164,37 @@ class String #!!(self =~ /\A[-+]?[0-9]+\z/) end end -def process_tdata(host) +def process_tdata(host, simple=false) print "Processing #{host}: " + STDOUT.flush if system("nmap #{host} -p 22 2>&1 | grep 22 | grep open >/dev/null") tdata = `ssh -oBatchMode=yes #{host} heatbot_gettemp` if tdata.is_integer? - print tdata + "°#{@tunit}\n" - return tdata + "°#{@tunit}" + print tdata + "°#{@tunit}" + if simple #This is mostly just an Easter egg + if tdata.to_i > 75 + sdata = "Hot" + elsif tdata.to_i < 50 + sdata = "Cold" + else + sdata = "Fair" + end + print " [#{sdata}]\n" + STDOUT.flush + return sdata + else + print "\n" + STDOUT.flush + return tdata + "°#{@tunit}" + end else puts "Unexpected output from [".red.bold + host.bold + "]: ".red.bold + tdata + STDOUT.flush return "CHECK PROBE" end else print "OFFLINE\n" + STDOUT.flush return "OFFLINE" end end @@ -304,6 +322,11 @@ def handle_message(message) process_command_check(message, command, adm) when '/report', '/r' process_command_report(message, command, adm) + #when '/simple', '/s', '/sc' + # process_command_check(message, command, adm, true) + #when '/sreport', '/simpler', '/simplereport', '/sr' + when '/simple', '/s', '/sreport', '/simpler', '/simplereport', '/sr' + process_command_report(message, command, adm, true) when '/test' process_command_test(message, command, adm) when '/pp', '/debug' From d3bb25758b280e3dad24295784ee3ee4c47535a7 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 15 Sep 2019 17:34:33 -0500 Subject: [PATCH 12/25] Replaced absolute values from function with global variables --- run.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/run.rb b/run.rb index defbe34..eb0b253 100755 --- a/run.rb +++ b/run.rb @@ -24,6 +24,10 @@ admin = @conf['admin'] @probes = @conf['probes'] @tunit = @conf['units'] +## Non-configurable +@simplehot = 75 +@simplecold = 50 + ### Begin sanity check ### STDOUT.sync = true errcount = 0 @@ -172,9 +176,9 @@ def process_tdata(host, simple=false) if tdata.is_integer? print tdata + "°#{@tunit}" if simple #This is mostly just an Easter egg - if tdata.to_i > 75 + if tdata.to_i > @simplehot sdata = "Hot" - elsif tdata.to_i < 50 + elsif tdata.to_i < @simplecold sdata = "Cold" else sdata = "Fair" From 7dd5cb45216b724b62440fec4a90a8f2b1bdf32c Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 15 Sep 2019 17:48:12 -0500 Subject: [PATCH 13/25] Added system utilities sanity check --- run.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/run.rb b/run.rb index eb0b253..b975458 100755 --- a/run.rb +++ b/run.rb @@ -32,6 +32,18 @@ admin = @conf['admin'] STDOUT.sync = true errcount = 0 puts "Checking if environment is sane...\n\n" +print "Checking system utilities ............... " +if !system("which nmap 2>&1 >/dev/null") + print "FAIL!\n\n".red.bold + puts "nmap".yellow.bold + " not found. This utility requires the 'nmap' command (for testing connectivity to temperature probes)" + exit(1) +elsif !system("which curl 2>&1 >/dev/null") + print "FAIL!\n\n".red.bold + puts "curl".yellow.bold + " not found. This utility requires the 'curl' command (for providing IP information to users upon request)" + exit(1) +else + print "OK\n".green.bold +end print "Checking bot token ...................... " if @token.nil? print "FAIL!\n\n".red.bold From ccba14ad63e41e60a1ee4db99d6a377be41fc13e Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 15 Sep 2019 17:50:34 -0500 Subject: [PATCH 14/25] Updated system utilities sanity check --- run.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run.rb b/run.rb index b975458..cc3fe0c 100755 --- a/run.rb +++ b/run.rb @@ -36,10 +36,12 @@ print "Checking system utilities ............... " if !system("which nmap 2>&1 >/dev/null") print "FAIL!\n\n".red.bold puts "nmap".yellow.bold + " not found. This utility requires the 'nmap' command (for testing connectivity to temperature probes)" + puts "THIS IS REQUIRED!".red.bold + " Bot initialization failed; exiting..." exit(1) elsif !system("which curl 2>&1 >/dev/null") print "FAIL!\n\n".red.bold puts "curl".yellow.bold + " not found. This utility requires the 'curl' command (for providing IP information to users upon request)" + puts "THIS IS REQUIRED!".red.bold + " Bot initialization failed; exiting..." exit(1) else print "OK\n".green.bold From 6f1b7357dee2d95675dcc338170506675be5dab3 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 15 Sep 2019 19:22:12 -0500 Subject: [PATCH 15/25] Added unit conversion support and associated sanity checks --- run.rb | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/run.rb b/run.rb index cc3fe0c..0e4bb8d 100755 --- a/run.rb +++ b/run.rb @@ -25,8 +25,10 @@ admin = @conf['admin'] @tunit = @conf['units'] ## Non-configurable -@simplehot = 75 -@simplecold = 50 +#@simplehot = 75 +#@simplecold = 50 +@simplehot = 24 +@simplecold = 10 ### Begin sanity check ### STDOUT.sync = true @@ -126,7 +128,15 @@ if @tunit.nil? puts "Error(#{errcount.to_s}): Temperature units not configured! Defaulting to Rankine. Continuing...\n\n" @tunit = 'R' else - print "OK\n".green.bold + case @tunit + when "C", "F", "K", "R" + print "OK\n".green.bold + else + errcount += 1 + print "FAIL!\n\n".red.bold + puts "Error(#{errcount.to_s}): Temperature unit '#{@tunit}' not recognized! Defaulting to Rankine. Continuing...\n\n" + @tunit = 'R' + end end print "Checking probes list .................... " if @probes.nil? @@ -179,7 +189,9 @@ end class String def is_integer? /\A[-+]?\d+\z/ === self - #!!(self =~ /\A[-+]?[0-9]+\z/) + end + def is_float? + /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/ === self end end def process_tdata(host, simple=false) @@ -187,8 +199,23 @@ def process_tdata(host, simple=false) STDOUT.flush if system("nmap #{host} -p 22 2>&1 | grep 22 | grep open >/dev/null") tdata = `ssh -oBatchMode=yes #{host} heatbot_gettemp` - if tdata.is_integer? - print tdata + "°#{@tunit}" + #if tdata.is_integer? + if tdata.is_float? + tdf = tdata.to_f + case @tunit + when "C" + #Do nothing; expected input is Celsius + when "F" + tdf = (tdf * 1.8) + 32 + when "K" + tdf = tdf + 273.15 + when "R" + tdf = (tdf * 1.8) + 491.67 + else + puts "#{@tunit} not valid temperature unit! Submitting unmodified output!" + end + ctdata = tdf.round.to_s + print ctdata + "°#{@tunit}" + " (" + "Raw: ".bold + "#{tdata})" if simple #This is mostly just an Easter egg if tdata.to_i > @simplehot sdata = "Hot" @@ -203,7 +230,7 @@ def process_tdata(host, simple=false) else print "\n" STDOUT.flush - return tdata + "°#{@tunit}" + return ctdata + "°#{@tunit}" end else puts "Unexpected output from [".red.bold + host.bold + "]: ".red.bold + tdata From 74fec40e64d69715472804127e15d632e2f0b8ad Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sun, 15 Sep 2019 19:26:46 -0500 Subject: [PATCH 16/25] Cleanup: Removed test command --- commands.rb | 32 -------------------------------- run.rb | 2 -- 2 files changed, 34 deletions(-) diff --git a/commands.rb b/commands.rb index c2c0f28..2ad294f 100644 --- a/commands.rb +++ b/commands.rb @@ -60,7 +60,6 @@ end def process_command_location(message, command, adm) if message_from_admin?(message, adm) || is_chat_authorized?(message, @auth_chat) - #reply = "I am currently located at:\n\nHost: \\\`#{`head -n1 /etc/hostname`}\\\`ExtIP: \\\`#{`curl icanhazip.com 2>/dev/null`}\\\`" reply = "I am currently located at:\n\nHost: #{`head -n1 /etc/hostname`}ExtIP: #{`curl icanhazip.com 2>/dev/null`}" else reply = "I am not authorized to provide this information here." @@ -104,12 +103,7 @@ def process_command_report(message, command, adm, simple=false) @probes.each do |k,v| zone = v["loc"].to_s host = k - #print "#{host}: #{zone} " tdata = process_tdata(host, simple) - #tdata = `ssh #{host} heatbot_gettemp` - #print "[#{tdata}°#{@tunit}]\n" - #report = report + "#{zone}: #{tdata}°#{@tunit}\n" - #print "[#{tdata}]\n" report = report + "#{zone}: #{tdata}\n" STDOUT.flush end @@ -120,29 +114,3 @@ def process_command_report(message, command, adm, simple=false) send_message(message.chat.id,"I am not authorized to provide this information here.") end end - -def process_command_test(message, command, adm) - if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) - #reply = `scripts/tempf.py` - #some interactive code to select a probe - select_loc = "Server Room: Intake" - #end interactive code - #probe = @probes[0] #set manually for now - reply = "" - hostdata = host_lookup(select_loc) - puts "var:".blue+" hostdata".blue.bold+"::".bold + hostdata.to_s - if ! hostdata.empty? - hostdata.each do |k,v| - puts "match:".blue+" host".blue.bold+"::".bold + k.to_s - reply += k.to_s+"\n" - end - else - reply = "No matching locations!" - puts "hostdata is empty!" - end - puts reply - else - reply = "I am not authorized to provide this information here." - end - return reply -end diff --git a/run.rb b/run.rb index 0e4bb8d..aa21548 100755 --- a/run.rb +++ b/run.rb @@ -372,8 +372,6 @@ def handle_message(message) #when '/sreport', '/simpler', '/simplereport', '/sr' when '/simple', '/s', '/sreport', '/simpler', '/simplereport', '/sr' process_command_report(message, command, adm, true) - when '/test' - process_command_test(message, command, adm) when '/pp', '/debug' pp message send_message(message.chat.id"Confirmation: Message debug information sent to console.") From 96ffc16155d65a54d73f3eb52549a8ab43e945dc Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Mon, 16 Sep 2019 14:43:20 -0500 Subject: [PATCH 17/25] Tolerance to bad casing; added 'Ra' as acceptable unit --- run.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/run.rb b/run.rb index aa21548..b1f3f93 100755 --- a/run.rb +++ b/run.rb @@ -128,8 +128,9 @@ if @tunit.nil? puts "Error(#{errcount.to_s}): Temperature units not configured! Defaulting to Rankine. Continuing...\n\n" @tunit = 'R' else - case @tunit - when "C", "F", "K", "R" + case @tunit.upcase + when "C", "F", "K", "R", "RA" + @tunit = @tunit.capitalize print "OK\n".green.bold else errcount += 1 @@ -202,14 +203,14 @@ def process_tdata(host, simple=false) #if tdata.is_integer? if tdata.is_float? tdf = tdata.to_f - case @tunit + case @tunit.upcase when "C" #Do nothing; expected input is Celsius when "F" tdf = (tdf * 1.8) + 32 when "K" tdf = tdf + 273.15 - when "R" + when "R", "RA" tdf = (tdf * 1.8) + 491.67 else puts "#{@tunit} not valid temperature unit! Submitting unmodified output!" From ab06b8474f477d82422ef067fd5c76012dc29110 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Mon, 16 Sep 2019 20:48:57 -0500 Subject: [PATCH 18/25] Fixed temperature input with newline characters --- run.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.rb b/run.rb index b1f3f93..35ba9be 100755 --- a/run.rb +++ b/run.rb @@ -199,7 +199,7 @@ def process_tdata(host, simple=false) print "Processing #{host}: " STDOUT.flush if system("nmap #{host} -p 22 2>&1 | grep 22 | grep open >/dev/null") - tdata = `ssh -oBatchMode=yes #{host} heatbot_gettemp` + tdata = `ssh -oBatchMode=yes #{host} heatbot_gettemp`.chomp #if tdata.is_integer? if tdata.is_float? tdf = tdata.to_f From bf7edc78146c6ddddf829590adfdbd81dc19666d Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Tue, 17 Sep 2019 18:50:12 -0500 Subject: [PATCH 19/25] Added non-standard SSH port support --- callbacks.rb | 9 +++++++-- commands.rb | 3 ++- run.rb | 12 +++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/callbacks.rb b/callbacks.rb index 6ff5854..efa5ab9 100644 --- a/callbacks.rb +++ b/callbacks.rb @@ -1,8 +1,13 @@ def process_callback_zone(message) host = message.data.split("|")[1] - zone = @probes[host].values.first + #zone = @probes[host].values.first + zone = @probes[host].select { |k,v| k == 'loc' } + zone = zone["loc"] puts "Selected: #{zone}" - tdata = process_tdata(host) + port = @probes[host].select { |k,v| k == 'port' } + port = port["port"] + #print " Port: #{port}\n" + tdata = process_tdata(host, port) #puts "Selected: #{zone} [#{tdata}]" send_message(message.message.chat.id, "#{zone}: #{tdata}") STDOUT.flush diff --git a/commands.rb b/commands.rb index 2ad294f..b401f68 100644 --- a/commands.rb +++ b/commands.rb @@ -102,8 +102,9 @@ def process_command_report(message, command, adm, simple=false) end @probes.each do |k,v| zone = v["loc"].to_s + port = v["port"].to_s host = k - tdata = process_tdata(host, simple) + tdata = process_tdata(host, port, simple) report = report + "#{zone}: #{tdata}\n" STDOUT.flush end diff --git a/run.rb b/run.rb index 35ba9be..925f221 100755 --- a/run.rb +++ b/run.rb @@ -164,7 +164,8 @@ puts "Temporary direcotry: #{@tmpdir}" puts "Temperature unit: #{@tunit}" puts "Probes loaded:" @probes.each do |k,v| - puts " [#{k}] #{v["loc"].to_s}" + #puts " [#{k}] #{v["loc"].to_s}" + puts " [" + "#{k}: ".bold + v['port'].to_s.green.bold + "] #{v['loc'].to_s}" end puts "Start time: " + timestamp + "\n\n\n\n" STDOUT.flush @@ -181,9 +182,6 @@ STDOUT.flush #end def host_lookup(select_loc) - #@probes.select { |k,v| v['loc'] == select_loc }.each do |host,loc| - # return host - #end return @probes.select { |k,v| v['loc'] == select_loc } end @@ -195,11 +193,11 @@ class String /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/ === self end end -def process_tdata(host, simple=false) +def process_tdata(host, port, simple=false) print "Processing #{host}: " STDOUT.flush - if system("nmap #{host} -p 22 2>&1 | grep 22 | grep open >/dev/null") - tdata = `ssh -oBatchMode=yes #{host} heatbot_gettemp`.chomp + if system("nmap #{host} -p #{port} 2>&1 | grep #{port} | grep open >/dev/null") + tdata = `ssh -p #{port} -oBatchMode=yes #{host} heatbot_gettemp`.chomp #if tdata.is_integer? if tdata.is_float? tdf = tdata.to_f From 7365a15886cb8dc8f15179fdc72596c954fec842 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Tue, 17 Sep 2019 20:34:55 -0500 Subject: [PATCH 20/25] Updated example config for SSH port --- bot_config.yaml.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot_config.yaml.example b/bot_config.yaml.example index 4f8f611..8dc2a6b 100644 --- a/bot_config.yaml.example +++ b/bot_config.yaml.example @@ -13,7 +13,10 @@ units: 'F' probes: srv1: loc: 'Office' + port: 22 srv2: loc: 'Area 50.1' + port: 22 srv3: loc: 'Dungeon' + port: 22 From 8ab721d8011b15937aa89f69eafcd67ad537b232 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Wed, 18 Sep 2019 20:26:32 -0500 Subject: [PATCH 21/25] Added probe scripts --- probe_scripts/heatbot_gettemp | 14 ++++++++++++++ probe_scripts/heatbot_testrandom | 8 ++++++++ 2 files changed, 22 insertions(+) create mode 100755 probe_scripts/heatbot_gettemp create mode 100755 probe_scripts/heatbot_testrandom diff --git a/probe_scripts/heatbot_gettemp b/probe_scripts/heatbot_gettemp new file mode 100755 index 0000000..4d079a6 --- /dev/null +++ b/probe_scripts/heatbot_gettemp @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +#Modify this to point to your temperature probe +dir=/sys/bus/w1/devices/[some-hexadecimals] + +# DO NOT MODIFY + +# Sanity check +which bc 2>&1 >/dev/null || { echo "PROBE ERROR: 'bc' not found. Please install bc." >&2; exit 1; } + +# Retrieve temperature in Celsius +raw=$(grep 't=' $dir/w1_slave | awk -F'=' '{print $2}') +echo "scale=3; ${raw}/1000" | bc -l && exit 0 +exit 1 diff --git a/probe_scripts/heatbot_testrandom b/probe_scripts/heatbot_testrandom new file mode 100755 index 0000000..ce3f9b5 --- /dev/null +++ b/probe_scripts/heatbot_testrandom @@ -0,0 +1,8 @@ +#/usr/bin/env sh + +# Symlink this script to heatbot_gettemp if you need to test a probe's connection +# without retrieving real temperature numbers. This is useful in setup such as before +# the sensor hardware is available to use. + +echo $((-20 + $RANDOM % 60)).$((10 + $RANDOM % 89)) || exit 1 +exit 0 From 327a5349209d888441138d3e6f06790097061a22 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sat, 21 Sep 2019 15:12:30 -0500 Subject: [PATCH 22/25] update /start, fix /patch, add ip_provider global --- commands.rb | 9 +++++---- run.rb | 3 +-- static_text/patchnotes.txt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/commands.rb b/commands.rb index b401f68..5552dd1 100644 --- a/commands.rb +++ b/commands.rb @@ -27,8 +27,9 @@ end def process_command_start(message, command, adm) reply = "I am #{@botname}, and I am here to provide temperature information from various sensors. Currently I can retrieve information " + - "from temperature sensors as well as report when temperatures are out of a specified range.\n\n" + - "Commands available:\n/start (Shows this message)\n/check or /c (Show temperatures)\n/report or /r (Show all temperatures)\n/whoami or /chatinfo (Provides IDs for internal use)\n/whereareyou or /location (Provides hostname for bot server)\n\n" + + "from temperature sensors. In a future version, I will also report when temperatures are out of a specified range through regular checks.\n\n" + + "Commands available:\n/start (Shows this message)\n/check or /c (Show a specific temperature interactively)\n/report or /r (Show all temperatures)\n" + + "/simple or /s (Show a simplified report)\n\nExtra functions:\n/whoami or /chatinfo (Provides IDs for internal use)\n/whereareyou or /location (Provides hostname for bot server)\n\n" + "Check again later to see if any new functions have been added, or use /patchnotes to learn about recent updates.\n" + "You can also view the source code at the following location:\nhttps://git.skyfall.tech/skyfall/heatbot" if message_from_admin?(message, adm) @@ -51,7 +52,7 @@ end def process_command_patchnotes(message, command, adm) if is_chat_authorized?(message, @auth_chat) || message_from_admin?(message, adm) - reply = File.read(static_text/patchnotes.txt) + reply = File.read("static_text/patchnotes.txt") else reply = "I am not authorized to provide this information here." end @@ -60,7 +61,7 @@ end def process_command_location(message, command, adm) if message_from_admin?(message, adm) || is_chat_authorized?(message, @auth_chat) - reply = "I am currently located at:\n\nHost: #{`head -n1 /etc/hostname`}ExtIP: #{`curl icanhazip.com 2>/dev/null`}" + reply = "I am currently located at:\n\nHost: #{`head -n1 /etc/hostname`}ExtIP: #{`curl #{@ip_provider} 2>/dev/null`}" else reply = "I am not authorized to provide this information here." end diff --git a/run.rb b/run.rb index 925f221..fa6c463 100755 --- a/run.rb +++ b/run.rb @@ -25,10 +25,9 @@ admin = @conf['admin'] @tunit = @conf['units'] ## Non-configurable -#@simplehot = 75 -#@simplecold = 50 @simplehot = 24 @simplecold = 10 +@ip_provider = "ip.skyfall.tech" ### Begin sanity check ### STDOUT.sync = true diff --git a/static_text/patchnotes.txt b/static_text/patchnotes.txt index a23f5e2..0b43997 100644 --- a/static_text/patchnotes.txt +++ b/static_text/patchnotes.txt @@ -2,4 +2,4 @@ SkyfallTech HeatBot Patch Notes: v0.1.00 -+ Starting from a fork of the Skyfall EGS-Bot v0.4.00 ++ Initial Release From 8d1a6135babe3f77da461c2e1927faffc71b549d Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sat, 21 Sep 2019 16:21:06 -0500 Subject: [PATCH 23/25] Wrote README --- README.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/README.md b/README.md index bbc3dcd..48eb5c5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,106 @@ #HeatBot +A Ruby application to provide remote temperature probe data over Telegram, to groups or to individuals. +------------------------------------------------------------------------------- +##Prerequisites +You will need the following packages on your **primary bot server**: +- nmap +- curl +- ruby +As well as these Ruby gems: +- telegram-bot-ruby +- yaml +- time + +For each **remote temperature probe**: +- bc +- w1-gpio (kernel module) +- w1-therm (kernel module) + +##Configuration +####bot_config.yaml +After cloning this git repo, the first thing you will want to do is copy `bot_config.yaml`.example to `bot_config.yaml` and edit the values. Below are the values with a description: + +**botname:** The name of your bot. This is cosmetic, and does not alter any functions. +**tmpdir:** Temporary directory the bot will use when needed. +**token:** Private Bot token. Can be obtained from @botfather on Telegram. +**admin:** This is an array of admin users. Currently, there are no admin-only functions built into the bot at this time. +**authorized_chats:** These are the chat IDs (or user IDs for individual, non-group interaction) in which the bot is allowed to respond with any temperature or self-identifying information. User information will still be provided publicly by design, so you can use /chatinfo to find your own information you need to add to be authorized. +**allowed_sources:** These are the types of messages to allow. By default, all message types are allowed. It is recommended to leave this alone, but the option is there to restrict it further. +**units:** The default temperature unit. Heatbot supports Fahrenheit (F), Celsius (C), Rankine (R or Ra), and Kelvin (K). +**probes:** This is a "hash of hashes" that will identify each remote temperature probe, first by hostname to be used for the SSH connection (shq-example01), then `loc:` is the human-readable location (Server Room), and `port:` is the SSH port, default 22, which can be altered in case a non-standard SSH port is used for a particular probe. + +####HeatBot systemd Service +In the repository, you will find `eample/services/heatbot.service`. Copy this into `/etc/systemd/system/` and edit the copy. Primarily, you will want to edit the username to the desired user to run the service (default is `ivo`, user must already exist), and be sure the working directory is pointing to your repository location. Please not that symlinks are perfectly valid for this purpose. Once in place, as with any other service, run: +``` +systemctl daemon-reload +systemctl enable heatbot.service #Optional +``` +At this time, the bot has not yet been started. By default, the bot will log to journald when run as a service. The bot's output is colour-coded, and by normal `journalctl` usage does not show up as intended. You can still read the logs this way, but it will be much easier if you use the `--output cat` flag with it. To be sure the bot is configured correctly, let's watch the log output during the first start: +``` +systemctl start heatbot.service & journalctl --output cat -fu heatbot +``` +Pay careful attention to the sanity check portion, and if anything comes up with a red FAIL instead of a green OK, address the issue and restart the service and check again. + +####SSH Confgiuration (Recommendations) +There are many ways you can configure your SSH connections that will work. The bare minimum to function is that the following command must be valid to be run from the primary bot server, and return in Celsius the probe temperature: +``` +ssh -p [port] [host] heatbot_gettemp +``` +The way in which we have achieved this conveniently is by use of the bot server's `/etc/hosts` file and the bot service user's `~/.ssh/config` file. Let's take a look at an example set up. Let's say we are using a `bot_config.yaml` with the following `probes:` hash: +``` +probes: + probe01: + loc: 'Server Room' + port: 22 + probe02: + loc: 'Garage' + port: 22 +``` +Let's also say that these hosts, probe01 and probe02, use the respective IP addresses 10.0.0.1 and 10.0.0.2 and both probes have SSH set up for the user `heatbot`. The user that is running the service in our example is `ivo`. Under this topology, we would configure the bot server as such: +######/etc/hosts +``` +10.0.0.1 probe01 +10.0.0.2 probe02 +``` +######~ivo/.ssh/config +``` +Host probe01 + User heatbot + PasswordAuthentication no + PubKeyAuthentication yes + +Host probe02 + User heatbot + PasswordAuthentication no + PubKeyAuthentication yes +``` + +Next, you will want to be check your configuration by making sure your bot user can run the following commands, and succesffully retrieves the expected Celsius temperature value. **Please note:** __You will not be able to retrieve this info until the below__ Probe Configuration __section is completed.__ +``` +ssh -p 22 probe01 heatbot_gettemp +ssh -p 22 probe02 heatbot_gettemp +``` +If all is set up well, you will simply receive a decimal number and nothing else. + +####Probe Configuration +There are a large number of ways a remote probe can be configured, even outside of the intended operation. The included probe script is based on obtaining temperature readings from a DS18B20 probe connected to a Raspberry Pi. Let's assume this is the setup, and that you have basic working knowledge of setting up SSH keys and users. The following checklist should be sufficient to complete the setup: + +- Add the following line to `/boot/config.txt`: +``` +dtoverlay=w1-gpio,gpiopin=4 +``` +- Clone the git repo to each temperature probe host. +- Copy `probe_scripts/heatbot_gettemp` to a `$PATH` that is readable by the `heatbot` user, or whichever user you decide to use to interact with the probe. `/usr/bin/` is a good default location that is sure to be readily read and executed. +- Edit the copy, changing the configurable line to show the address of your temperature probe. This address will look like `28-[somehexnumbers]` as a symlink under `/sys/bus/w1/devices/` on the probe. +- Setup `~heatbot/.ssh/authorized_keys` to allow passwordless SSH from the primary bot server's service user. + +If all is in order, you should be abel to run locally on the probe the `heatbot_gettemp` command from the heatbot user, which will return a decimal number. +A secondary test would be to attempt the test from the above section, and test running the script remotely from the primary bot server, using, for example: +``` +ssh -p 22 probe01 heatbot_gettemp +``` + +## Setup Complete +If anything appears to be missing or needs further clarification, drop us a note and we can get the documentation updated. + From a2612cd8014f1636b6cf2ac799be41185c34ba6a Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sat, 21 Sep 2019 16:22:15 -0500 Subject: [PATCH 24/25] Updated README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 48eb5c5..b1c6995 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ #HeatBot -A Ruby application to provide remote temperature probe data over Telegram, to groups or to individuals. +A Ruby application to provide remote temperature probe data over Telegram + ------------------------------------------------------------------------------- + ##Prerequisites You will need the following packages on your **primary bot server**: - nmap From 2cb7ab983034d58d6fbb62a76c95e9391ca65e0d Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Sat, 21 Sep 2019 16:22:56 -0500 Subject: [PATCH 25/25] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1c6995..55e25a0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ #HeatBot -A Ruby application to provide remote temperature probe data over Telegram +A Ruby application to provide remote temperature probe data over Telegram, to groups or to individuals -------------------------------------------------------------------------------