require "http" module Telepathy class Bot enum Control Done end enum ParseMode Normal, Markdown, HTML end def initialize(@api_token : String) @request_base = "https://api.telegram.org/bot#{@api_token}" @this_user = uninitialized User? @this_user = get_me @last_update_id = uninitialized Int64? @last_update_id = nil @command_hooks = {} of String => Update, Array(String) -> Void @message_hooks = [] of Update -> Void @poll_start_hooks = [] of -> Void @poll_end_hooks = [] of -> Void @poll_channel = Channel(Int64?|Control).new @update_channel = Channel(Array(Update)|Control).new @poll_running = false end def get_me response = HTTP::Client.get(@request_base + "/getMe", headers: HTTP::Headers{"User-agent" => "Telepathy"}) return Response(User).from_json(response.body).result end def get_updates(timeout = 0) update_data = {} of String => Int64 | Int32 | String update_data["timeout"] = timeout @last_update_id.try { |id| update_data["offset"] = id } response = HTTP::Client.get(@request_base + "/getUpdates", headers: HTTP::Headers{"User-agent" => "Telepathy", "Content-type" => "application/json" }, body: update_data.to_json) return Response(Array(Update)).from_json(response.body).result end def send_message(chat_id : String | Int64, text : String, parse_mode : ParseMode = ParseMode::Normal, disable_web_preview : Bool = false, disable_notification : Bool = false, reply_to_message_id : Int64? = nil) message_data = { "chat_id" => chat_id, "text" => text } of String => Int64 | String | Bool message_data["disable_web_preview"] = true if disable_web_preview message_data["disable_notification"] = true if disable_notification if parse_mode == ParseMode::Markdown message_data["parse_mode"] = "Markdown" elsif parse_mode == ParseMode::HTML message_data["parse_mode"] = "HTML" end reply_to_message_id.try { |id| message_data["reply_to_message_id"] = id } HTTP::Client.get(@request_base + "/sendMessage", headers: HTTP::Headers{"User-agent" => "Telepathy", "Content-type" => "application/json" }, body: message_data.to_json) end def send_photo(chat_id : String | Int64, photo : String | File, caption : String? = nil, disable_notification : Bool = false, reply_to_message_id : Int64? = nil) IO.pipe do |reader, writer| channel = Channel(String).new(1) spawn do HTTP::FormData.build(writer) do |formdata| channel.send(formdata.content_type) case chat_id when String formdata.field("chat_id", chat_id.as(String)) when Int64 formdata.field("chat_id", chat_id.as(Int64).to_s) end formdata.field("disable_notification", disable_notification.to_s) caption.try { |caption| formdata.field("caption", caption) } reply_to_message_id.try { |id| formdata.field("reply_to_message_id", id) } case photo when String formdata.field("photo", photo.as(String)) when File photo_file = photo.as(File) formdata.file("photo", photo_file, HTTP::FormData::FileMetadata.new(filename: File.basename(photo_file.path))) end end writer.close end response = HTTP::Client.get(@request_base + "/sendPhoto", headers: HTTP::Headers{"User-agent" => "Telepathy", "Content-type" => channel.receive }, body: reader) end end def command(command_name, &block: Update, Array(String) -> Void) @command_hooks[command_name] = block end def message(&block: Update -> Void) @message_hooks.push(block) end def poll_start(&block: -> Void) @poll_start_hooks.push(block); end def poll_end(&block: -> Void) @poll_end_hooks.push(block); end private def process_updates(updates) updates.each do |update| if message = update.message @message_hooks.each &.call(update) if entity = message.entities.try { |it| it.first? } text = message.text.as String if entity.offset == 0 && entity.type == "bot_command" divider_index = (text.index /\s|@/) || text.size first_space_index = (text.index /\s/) || text.size command = text[1...divider_index] remaining = text[first_space_index..text.size] params = remaining.empty? ? ([] of String) : (remaining[1...remaining.size].split ' ') @command_hooks[command]?.try { |command| command.call(update, params) } end end end end end private def spawn_workers spawn do loop do action = @poll_channel.receive case action when Int64? @last_update_id = action.nil? ? @last_update_id : action + 1 @update_channel.send get_updates 10 when Control break end end end spawn do loop do item = @update_channel.receive case item when Array(Update) process_updates(item) @poll_channel.send item.last?.try &.update_id when Control @poll_channel.send item break end end end end def poll if !@poll_running @poll_running = true spawn_workers @poll_channel.send nil @poll_start_hooks.each &.call end end def end_poll if @poll_running @poll_running = false @poll_channel.send Control::Done @poll_end_hooks.each &.call end end end end