Hi!
I've been playing around with Nim, and to get my head around core concepts I've rewritten my existing Telegram bot in Nim. I've used Telebot for this. The core update handler loop looks somewhat like this (abridged version):
import regex
import unicode
import httpclient
import telebot, asyncdispatch, logging, options
import timezones
import times
import json
import options
const OH_PATTERN = re(r"(?i)(\b[oо]+|\b[аa]+|\b[ы]+|\b[еe]+|\b[уy]+|\b[э]+)[xх]+\b")
const API_KEY = getEnv("BOT_TOKEN")
proc fromJson*(json: JsonNode): DateTime {.gcsafe.} =
let timezone = tz"Asia/Yekaterinburg"
let response = to(json, seq[ApiResponse])
let responseDate: DateTime = parse("1970-01-01", "yyyy-MM-dd")
for item in response:
if item.category.isSome() and item.date.isSome():
case item.category.get():
of "holiday":
date = parseDateTime(item.date.get(), timezone)
// ...some other cases...
else: discard
return responseDate
proc updateHandler(b: Telebot, u: Update): Future[bool] {.async.} =
if not u.message.isSome:
return true
var response = u.message.get
if not response.text.isSome:
return true
let text = response.text.get
var m: RegexMatch
if text.find(OH_PATTERN, m):
let vowel = m.groupFirstCapture(0, text)
discard await b.sendMessage(response.chat.id, generateOh(vowel))
elif text.toLower().contains("some request"):
let client = newHttpClient()
let content = client.getContent("https://example.com/some_api")
let jsonNode = parseJson(content)
let response = fromJson(jsonNode["items"])
discard await b.sendMessage(response.chat.id, $response)
// ...some other cases...
let bot = newTeleBot(API_KEY)
bot.onUpdate(updateHandler)
bot.poll(timeout=300)
So my main problem with this is the fact that updateHandler has to be gc-safe, which leads to some awkward coding for me, but I'm hoping that there's a better way to do things (which is why I'm here). Here are my questions:
I would be glad if someone could clarify these things for me and show what's the idiomatic way to solve these problems. Thanks!
3. The final question is about timezones.
The binary size bloat is a problem, but the timezones package does offer a few ways to avoid it:
To save any space with option 1 or 2 you would also need to add -d:timezonesNoEmbeed to the compilation to prevent any tzdata from being bundled into the binary. I planned to add an option to gzip the bundled tzdata which should reduce the size a lot, but I never got around to doing it. Maybe it's time now :)
Thanks for the work. Consider zstd instead of zip (much faster decompression, much better compression ratio, as free) if you do - gzip is legacy at this point.
(zstd also does its compression significantly more quickly, but that's not relevant to to this problem I think)
Thank you for the reply!
The first option looks exactly like what I need. However, do I need to install some additional libraries to make it work? I am on Pop! OS which is a derivative of Ubuntu and I have tzdata package installed (not sure if it's needed at all). But when I use loadPosixTz"Asia/Yekaterinburg" with import timezones/posixtimezones it crashes at runtime with the following stacktrace:
posixtimezones.nim(58) loadTzInternal
asyncfutures.nim(372) read
asyncfutures.nim(420) asyncCheckCallback
Error: unhandled exception: Invalid tz file [TzFileParsingError]
As for the fetchjsontimezones, I saw the description in github repo, but I was unable to use it. Running the usage example from repo yields this stacktrace:
fetchjsontimezones 2017c --out:tzdata.json --regions:"europe america"
Fetching and processing timezone data. This might take a while...
zic: Can't open /tmp/fetchtz/unpacked/america: No such file or directory
fatal.nim(49) sysFatal
Error: unhandled exception: fetchjsontimezones.nim(35, 18) `execCmd(fmt"zic -d {ZicDir} {files}") == 0` [AssertionDefect]
Author of https://github.com/treeform/chrono here.
[Chrono] Seems to bring with it a whole lot of things for manipulating dates which I don't really need, so I didn't want to use that.
Nim will compile non needed code out. So only the parts of chrono you use will end up in the executable. Next is the actual tz data. I have written the timezone stuff to also work on the JS backend. For this, like you, I need small file sizes. You can use the generate tool to generate a tiny subset of timezones for subset of years (see https://github.com/treeform/chrono#only-ship-the-timezones-you-use ). For my use case tz json file ends up only couple of kilobytes.
I can help you out with Chrono if you need me.
The first option looks exactly like what I need. However, do I need to install some additional libraries to make it work? I am on Pop! OS which is a derivative of Ubuntu and I have tzdata package installed (not sure if it's needed at all). But when I use loadPosixTz"Asia/Yekaterinburg" with import timezones/posixtimezones it crashes at runtime with the following stacktrace:
There was a bug in release mode that I was unaware of. It should now be fixed in the latest version
As for the fetchjsontimezones, I saw the description in github repo, but I was unable to use it. Running the usage example from repo yields this stacktrace:
The example in the repo contained an error: america is not a valid region, it's split into southamerica and northamerica. I've now updated the example but I should probably improve the error message as well
@cumulonimbus Thanks for the information, I'll keep it in mind
Thanks, it's working now!
@treeform thank you for offering help, but I went with using system tzdata as @GULPF suggested, I wanted to do that from the start, just didn't know it was an option.
Now only two questions remain, both kind of about the same thing regarding gc safety and accessing global variables