diff --git a/README.md b/README.md index 6651345..709d0cb 100644 --- a/README.md +++ b/README.md @@ -12,55 +12,43 @@ Usage ## echo bot ```nim -import telebot, asyncdispatch, options +import telebot, asyncdispatch, logging, options const API_KEY = slurp("secret.key") -proc handleUpdate(bot: TeleBot): UpdateCallback = - proc cb(e: Update) {.async.} = - var response = e.message.get - if response.text.isSome: - let - text = response.text.get - var message = newMessage(response.chat.id, text) - message.disableNotification = true - message.replyToMessageId = response.messageId - message.parseMode = "markdown" - discard bot.send(message) - result = cb - -let - bot = newTeleBot(API_KEY) - handler = handleUpdate(bot) -bot.onUpdate(handler) +proc updateHandler(b: Telebot, u: Update) {.async.} = + var response = u.message.get + if response.text.isSome: + let + text = response.text.get + var message = newMessage(response.chat.id, text) + message.disableNotification = true + message.replyToMessageId = response.messageId + message.parseMode = "markdown" + discard await b.send(message) + +let bot = newTeleBot(API_KEY) +bot.onUpdate(updateHandler) bot.poll(300) - ``` ## send local photo ```nim import telebot, asyncdispatch, options, logging -var L = newConsoleLogger(fmtStr="$levelname, [$time] ") -addHandler(L) - const API_KEY = slurp("secret.key") -proc handleUpdate(bot: TeleBot): UpdateCallback = - proc cb(e: Update) {.async.} = - var response = e.message.get - if response.text.isSome: - let - text = response.text.get - var message = newPhoto(response.chat.id, "file:///path/to/photo.jpg") - discard await bot.send(message) - result = cb +proc updateHandler(bot: TeleBot, update: Update): UpdateCallback = + var response = update.message.get + if response.text.isSome: + let + text = response.text.get + var message = newPhoto(response.chat.id, "file:///path/to/photo.jpg") + discard await bot.send(message) let bot = newTeleBot(API_KEY) - handler = handleUpdate(bot) - -bot.onUpdate(handler) +bot.onUpdate(updateHandler) bot.poll(300) ``` For more information please refer to official [Telegram Bot API](https://core.telegram.org/bots/api) page diff --git a/examples/echo_bot.nim b/examples/echo_bot.nim index a791ab9..2a51d92 100644 --- a/examples/echo_bot.nim +++ b/examples/echo_bot.nim @@ -5,35 +5,28 @@ addHandler(L) const API_KEY = slurp("secret.key") -proc handleUpdate(bot: TeleBot): UpdateCallback = - proc cb(e: Update) {.async.} = - var response = e.message.get - if response.text.isSome: - let - text = response.text.get - var message = newMessage(response.chat.id, text) - message.disableNotification = true - message.replyToMessageId = response.messageId - message.parseMode = "markdown" - discard await bot.send(message) - result = cb - -proc greatingHandler(bot: Telebot): CommandCallback = - proc cb(e: Command) {.async.} = - var message = newMessage(e.message.chat.id, "hello " & e.message.fromUser.get().firstName) +proc updateHandler(b: Telebot, u: Update) {.async.} = + var response = u.message.get + if response.text.isSome: + let + text = response.text.get + var message = newMessage(response.chat.id, text) message.disableNotification = true - message.replyToMessageId = e.message.messageId + message.replyToMessageId = response.messageId message.parseMode = "markdown" - discard bot.send(message) + discard await b.send(message) + - result = cb +proc greatingHandler(b: Telebot, c: Command) {.async.} = + var message = newMessage(c.message.chat.id, "hello " & c.message.fromUser.get().firstName) + message.disableNotification = true + message.replyToMessageId = c.message.messageId + message.parseMode = "markdown" + discard b.send(message) when isMainModule: - let - bot = newTeleBot(API_KEY) - handler = handleUpdate(bot) - greatingCb = greatingHandler(bot) + let bot = newTeleBot(API_KEY) - bot.onUpdate(handler) - bot.onCommand("hello", greatingCb) + bot.onUpdate(updateHandler) + bot.onCommand("hello", greatingHandler) bot.poll(300) diff --git a/examples/file_receive_bot/file_receive_bot.nim b/examples/file_receive_bot/file_receive_bot.nim index ad0fe90..55438a1 100644 --- a/examples/file_receive_bot/file_receive_bot.nim +++ b/examples/file_receive_bot/file_receive_bot.nim @@ -1,38 +1,35 @@ # This Bot receives any Document file, responds on chat with file metadata and file contents. import telebot, asyncdispatch, options, strformat, httpclient, json -const API_KEY = "" # Add your API Key here. +const API_KEY = slurp("secret.key") -proc handleUpdate(bot: TeleBot): UpdateCallback = +proc updateHandler(bot: TeleBot, e: Update) {.async.} = let url_getfile = fmt"https://api.telegram.org/bot{API_KEY}/getFile?file_id=" api_file = fmt"https://api.telegram.org/file/bot{API_KEY}/" - proc cb(e: Update) {.async.} = - var response = e.message.get - if response.document.isSome: - let - document = response.document.get - file_name = document.file_name.get - mime_type = document.mime_type.get - file_id = document.file_id - file_size = document.file_size.get - responz = await newAsyncHttpClient().get(url_getfile & file_id) # file_id > file_path - responz_body = await responz.body - file_path = parseJson(responz_body)["result"]["file_path"].getStr() - responx = await newAsyncHttpClient().get(api_file & file_path) # file_path > file - file_content = await responx.body - msg0_text = fmt"file_name: {file_name}, mime_type: {mime_type}, file_id: {file_id}, file_size: {file_size}, file_path: {file_path}" - var - message0 = newMessage(response.chat.id, msg0_text) # metadata - message1 = newMessage(response.chat.id, file_content) # file contents - discard await bot.send(message0) - discard await bot.send(message1) - result = cb + var response = e.message.get + if response.document.isSome: + let + document = response.document.get + file_name = document.file_name.get + mime_type = document.mime_type.get + file_id = document.file_id + file_size = document.file_size.get + responz = await newAsyncHttpClient().get(url_getfile & file_id) # file_id > file_path + responz_body = await responz.body + file_path = parseJson(responz_body)["result"]["file_path"].getStr() + responx = await newAsyncHttpClient().get(api_file & file_path) # file_path > file + file_content = await responx.body + msg0_text = fmt"file_name: {file_name}, mime_type: {mime_type}, file_id: {file_id}, file_size: {file_size}, file_path: {file_path}" + var + message0 = newMessage(response.chat.id, msg0_text) # metadata + message1 = newMessage(response.chat.id, file_content) # file contents + discard await bot.send(message0) + discard await bot.send(message1) -let - bot = newTeleBot(API_KEY) - handler = handleUpdate(bot) -bot.onUpdate(handler) +let bot = newTeleBot(API_KEY) + +bot.onUpdate(updateHandler) bot.poll(666) diff --git a/examples/file_send_bot/file_send_bot.nim b/examples/file_send_bot/file_send_bot.nim index f60e1aa..d9735f2 100644 --- a/examples/file_send_bot/file_send_bot.nim +++ b/examples/file_send_bot/file_send_bot.nim @@ -1,17 +1,16 @@ # This Bot Sends itself as Document file, responds on chat with source file to download. import telebot, asyncdispatch, options, os -const API_KEY = "" # Add your API Key here. +const API_KEY = slurp("secret.key") -proc handleUpdate(bot: TeleBot): UpdateCallback = +proc updateHandler(bot: TeleBot, e: Update) {.async.} = let this_file = "file://" & getCurrentDir() & "/file_send_bot.nim" - proc cb(e: Update) {.async.} = - var document = newDocument(e.message.get.chat.id, this_file) - document.caption = this_file - discard await bot.send(document) - result = cb + + var document = newDocument(e.message.get.chat.id, this_file) + document.caption = this_file + discard await bot.send(document) let bot = newTeleBot(API_KEY) -bot.onUpdate(handleUpdate(bot)) +bot.onUpdate(updateHandler) bot.poll(666) diff --git a/examples/geo_location_bot/geo_bot.nim b/examples/geo_location_bot/geo_bot.nim index c8e2f8e..f201366 100644 --- a/examples/geo_location_bot/geo_bot.nim +++ b/examples/geo_location_bot/geo_bot.nim @@ -1,13 +1,11 @@ import telebot, asyncdispatch, options -const API_KEY = "" # Add your API Key here. +const API_KEY = slurp("secret.key") -proc geoHandler(bot: TeleBot): CommandCallback = - proc cb(e: Command) {.async.} = - let message = newLocation(e.message.chat.id, longitude=42.0, latitude=42.0) - discard await bot.send(message) - result = cb +proc geoHandler(bot: TeleBot, e: Command) {.async.} = + let message = newLocation(e.message.chat.id, longitude=42.0, latitude=42.0) + discard await bot.send(message) let bot = newTeleBot(API_KEY) -bot.onCommand("geo", geoHandler(bot)) # Use /geo on Telegram chat to trigger. +bot.onCommand("geo", geoHandler) # Use /geo on Telegram chat to trigger. bot.poll(666) diff --git a/examples/image_bot.nim b/examples/image_bot.nim index 5ac3f9e..bd1a599 100644 --- a/examples/image_bot.nim +++ b/examples/image_bot.nim @@ -5,12 +5,12 @@ addHandler(L) from cgi import encodeUrl const API_KEY = slurp("secret.key") -proc fetchResults(query: string): Future[seq[InlineQueryResultPhoto]] {.async.} = - result = @[] - let url = "https://api.reddit.com/r/unixporn/search?limit=6&type=link&q=" & encodeUrl(query) +proc queryHandler(b: TeleBot, q: InlineQuery) {.async.} = + let url = "https://api.reddit.com/r/unixporn/search?limit=6&type=link&q=" & encodeUrl(q.query) var client = newAsyncHttpClient() response = await client.get(url) + photos: seq[InlineQueryResultPhoto] = @[] if response.code == Http200: var data = parseJson(await response.body) for child in data["data"]["children"].items: @@ -22,20 +22,12 @@ proc fetchResults(query: string): Future[seq[InlineQueryResultPhoto]] {.async.} photo.photoUrl = child["data"]["url"].str photo.thumbUrl = child["data"]["thumbnail"].str photo.title = some($child["data"]["title"]) - result.add(photo) -var updates: seq[Update] -proc main() {.async.} = - let bot = newTeleBot(API_KEY, "nim_telebot") + photos.add(photo) - while true: - updates = await bot.getUpdates(timeout=300) - for update in updates: - if update.inlineQuery.isNone: - continue - var - query = update.inlineQuery.get - results = await fetchResults(query.query) - discard await bot.answerInlineQuery(query.id, results) -asyncCheck main() -runForever() + discard await b.answerInlineQuery(q.id, photos) + +when isMainModule: + let bot = newTeleBot(API_KEY) + bot.onInlineQuery(queryHandler) + bot.poll(300) \ No newline at end of file diff --git a/examples/inline_keyboard.nim b/examples/inline_keyboard.nim index 20a621b..049e2ed 100644 --- a/examples/inline_keyboard.nim +++ b/examples/inline_keyboard.nim @@ -8,32 +8,27 @@ const API_KEY = slurp("secret.key") var updates: seq[Update] -proc handleUpdate(bot: TeleBot): UpdateCallback = - proc cb(e: Update) {.async.} = - var response = e.message.get - if response.text.isSome: - let - text = response.text.get - var - message = newMessage(response.chat.id, text) - google = initInlineKeyboardButton("Google") - bing = initInlineKeyboardButton("Bing") - ddg = initInlineKeyboardButton("DuckDuckGo") - searx = initInlineKeyboardButton("searx.me") - - google.url = some("https://www.google.com/search?q=" & text) - bing.url = some("https://www.bing.com/search?q=" & text) - ddg.url = some("https://duckduckgo.com/?q=" & text) - searx.url = some("https://searx.me/?q=" & text) - - message.replyMarkup = newInlineKeyboardMarkup(@[google, bing], @[ddg, searx]) - discard await bot.send(message) - result = cb +proc updateHandler(bot: TeleBot, e: Update) {.async.} = + var response = e.message.get + if response.text.isSome: + let + text = response.text.get + var + message = newMessage(response.chat.id, text) + google = initInlineKeyboardButton("Google") + bing = initInlineKeyboardButton("Bing") + ddg = initInlineKeyboardButton("DuckDuckGo") + searx = initInlineKeyboardButton("searx.me") + + google.url = some("https://www.google.com/search?q=" & text) + bing.url = some("https://www.bing.com/search?q=" & text) + ddg.url = some("https://duckduckgo.com/?q=" & text) + searx.url = some("https://searx.me/?q=" & text) + + message.replyMarkup = newInlineKeyboardMarkup(@[google, bing], @[ddg, searx]) + discard await bot.send(message) when isMainModule: - let - bot = newTeleBot(API_KEY) - handler = handleUpdate(bot) - - bot.onUpdate(handler) + let bot = newTeleBot(API_KEY) + bot.onUpdate(updateHandler) bot.poll(300) diff --git a/examples/photo_send_bot/photo_send_bot.nim b/examples/photo_send_bot/photo_send_bot.nim index 7bac76d..1ad01e2 100644 --- a/examples/photo_send_bot/photo_send_bot.nim +++ b/examples/photo_send_bot/photo_send_bot.nim @@ -1,20 +1,16 @@ import telebot, asyncdispatch, options, logging, os -const API_KEY = "" # Add your API Key here. +const API_KEY = slurp("secret.key") addHandler(newConsoleLogger(fmtStr="$levelname, [$time] ")) -proc handleUpdate(bot: TeleBot): UpdateCallback = - proc cb(e: Update) {.async.} = - let message = newPhoto(e.message.get.chat.id, "file://" & getCurrentDir() & "/sample.jpg") - discard await bot.send(message) - result = cb +proc updateHandler(bot: TeleBot, e: Update) {.async.} = + let message = newPhoto(e.message.get.chat.id, "file://" & getCurrentDir() & "/sample.jpg") + discard await bot.send(message) -let - bot = newTeleBot(API_KEY) - handler = handleUpdate(bot) +let bot = newTeleBot(API_KEY) -bot.onUpdate(handler) +bot.onUpdate(updateHandler) bot.poll(500) diff --git a/telebot.nim b/telebot.nim index 6d2ef28..d7d7f54 100644 --- a/telebot.nim +++ b/telebot.nim @@ -1,4 +1,4 @@ -import asyncevents except off +import tables import telebot/[types, keyboard, webhook, inputmedia] export types, webhook, keyboard, inputmedia @@ -8,8 +8,9 @@ proc newTeleBot*(token: string): TeleBot = new(result) result.token = token result.lastUpdateId = 0 - result.updateEmitter = initAsyncEventEmitter[Update, string]() - result.commandEmitter = initAsyncEventEmitter[Command, string]() + result.updateCallbacks = @[] + result.commandCallbacks = newTable[string, seq[CommandCallback]]() + result.inlineQueryCallbacks = @[] include telebot/api include telebot/events diff --git a/telebot.nimble b/telebot.nimble index af21f44..692253f 100644 --- a/telebot.nimble +++ b/telebot.nimble @@ -1,8 +1,7 @@ -version = "0.4.0" +version = "0.4.1" author = "Huy Doan" description = "Async Telegram Bot API Client" license = "MIT" skipDirs = @["examples"] requires "nim >= 0.17.0" -requires "asyncevents >= 0.3.0" diff --git a/telebot/api.nim b/telebot/api.nim index eca9a80..602fd17 100644 --- a/telebot/api.nim +++ b/telebot/api.nim @@ -523,15 +523,23 @@ proc getUpdates*(b: TeleBot, offset, limit, timeout = 0, allowedUpdates: seq[str var update = unmarshal(item, Update) if update.updateId > b.lastUpdateId: b.lastUpdateId = update.updateId - await b.updateEmitter.emit("update", update) - if update.hasCommand(): + if update.inlineQuery.isSome: + for cb in b.inlineQueryCallbacks: + await cb(b, update.inlineQuery.get) + elif update.hasCommand(): var userCommands = update.getCommands() - for key in userCommands.keys(): - var cmd: Command - cmd.message = update.message.get() - cmd.params = userCommands[key] - await b.commandEmitter.emit(key, cmd) + for command in userCommands.keys(): + if b.commandCallbacks.hasKey(command): + var cmd: Command + cmd.message = update.message.get() + cmd.params = userCommands[command] + + for cb in b.commandCallbacks[command]: + await cb(b, cmd) + else: + for cb in b.updateCallbacks: + await cb(b, update) proc answerInlineQuery*[T](b: TeleBot, id: string, results: seq[T], cacheTime = 0, isPersonal = false, nextOffset = "", switchPmText = "", switchPmParameter = ""): Future[bool] {.async.} = diff --git a/telebot/events.nim b/telebot/events.nim index a6938b3..ef361f7 100644 --- a/telebot/events.nim +++ b/telebot/events.nim @@ -1,7 +1,10 @@ -import asyncevents +proc onUpdate*(b: TeleBot, cb: UpdateCallback) = + b.updateCallbacks.add(cb) -proc onUpdate*(b: TeleBot, p: EventProc[Update]) = - b.updateEmitter.on("update", p) +proc onCommand*(b: TeleBot, command: string, cb: CommandCallback) = + if not b.commandCallbacks.hasKey(command): + b.commandCallbacks[command] = @[] + b.commandCallbacks[command].add(cb) -proc onCommand*(b: TeleBot, command: string, p: EventProc[Command]) = - b.commandEmitter.on(command, p) +proc onInlineQuery*(b: TeleBot, cb: InlineQueryCallback) = + b.inlineQueryCallbacks.add(cb) \ No newline at end of file diff --git a/telebot/types.nim b/telebot/types.nim index e7431da..471f54c 100644 --- a/telebot/types.nim +++ b/telebot/types.nim @@ -1,16 +1,18 @@ -import options, tables, asyncevents +import asyncdispatch, options, tables type TelegramObject* = object of RootObj + UpdateCallback* = proc(bot: Telebot, update: Update): Future[void] + CommandCallback* = proc(bot: Telebot, command: Command): Future[void] + InlineQueryCallback* = proc(bot: Telebot, inlineQuery: InlineQuery): Future[void] + TeleBot* = ref object of TelegramObject token*: string lastUpdateId*: BiggestInt - updateEmitter*: AsyncEventEmitter[Update, string] - commandEmitter*: AsyncEventEmitter[Command, string] - - UpdateCallback* = EventProc[Update] - CommandCallback* = EventProc[Command] + updateCallbacks*: seq[UpdateCallBack] + commandCallbacks*: TableRef[string, seq[CommandCallback]] + inlineQueryCallbacks*: seq[InlineQueryCallback] Command* = object of TelegramObject message*: Message