DEV Community

Cover image for Developing Send-To-Kindle Telegram Bot
Michael Fomenko
Michael Fomenko

Posted on

4 3

Developing Send-To-Kindle Telegram Bot

Lately, I've been using the Kindle app on my tablet a lot and it was always a hustle to send my e-books to my tablet. You need to convert to a proper format, transfer it somehow... meh...

So I decided to create a Telegram bot that does all this for you. You send it a book - it appears on your Kindle. Any format supported. That's it - that simple!

I decided to use Go as a language because I wanted to practice it more.

Programmatically converting books to .mobi

It was the main challenge. I did not want to code a converter myself so I started googling other ways to do it. Also, I decided not to go with some online conversion tools with an API as I did not want to rely on them.
So after a while, I stumbled upon the fact that Calibre - popular software for managing e-books - has CLI tools. They had everything, that's perfect!

So I started coding.
The conversion itself looks like this. A simple method that accepts input and output paths for an e-book and passes them to the ebook-convert CLI Tool.

func convert(in, out string) error {
    cmd := exec.Command("ebook-convert", in, out)
    if err := cmd.Run(); err != nil {
        return err
    }
    if err := cmd.Wait(); err != nil {
        return err
    }
    if _, err := os.Stat(out); errors.Is(err, os.ErrNotExist) {
        return errConversion
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Telegram integration

Up next was telegram integration. That's easy.

Created bot credentials using BotFather
For integration with Telegram, I found a bot framework for Go - Telebot
The setup was pretty easy:

func (b *SendToKindleBot) Start() error {
    bot, err := tb.NewBot(tb.Settings{
        Token:  b.Token,
        Poller: &tb.LongPoller{Timeout: 10 * time.Second},
    })
    if err != nil {
        return ErrStartup
    }
    bot.Start()
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Sending via email

I created an email that my bot would use to send e-books.
Then don't forget to add it to Approved Personal Document Email List in your kindle settings.

And the method itself is pretty simple:

func (b *SendToKindleBot) sendFileViaEmail(path string) error {
    msg := email.NewMessage("", "")
    msg.From = mail.Address{Name: "From", Address: b.EmailFrom}
    msg.To = []string{b.EmailTo}

    if err := msg.Attach(path); err != nil {
        return err
    }

    auth := smtp.PlainAuth("", b.EmailFrom, b.Password, b.SMTPHost)
    addr := fmt.Sprintf("%s:%s", b.SMTPHost, b.SMTPPort)
    if err := email.Send(addr, auth, msg); err != nil {
        return err
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Putting things together

So next I wrote all the insides of the telegram bot with the main thing being - a handler that would receive a file and send it to my kindle.

func (b *SendToKindleBot) documentHandler(bot *tb.Bot) func(msg *tb.Message) {
    return func(msg *tb.Message) {
        doc := msg.Document
        nameParts := strings.Split(doc.FileName, ".")
        fileNameWithoutExtension := strings.Join(nameParts[:len(nameParts)-1], "")
        extension := nameParts[len(nameParts)-1]

        originalFilePath := tmpFilesPath + doc.FileName
        if err := bot.Download(&doc.File, originalFilePath); err != nil {
            log.Println("could not download file", err)
            respond(bot, msg, "Sorry. I could not download file")
        }
        defer removeSilently(originalFilePath)

        fileToSend := originalFilePath
        if needToConvert(extension) {
            outputFilePath := tmpFilesPath + fileNameWithoutExtension + ".mobi"
            if err := convert(originalFilePath, outputFilePath); err != nil {
                log.Println("could not convert file", err)
                respond(bot, msg, "Sorry. I could not convert file")
            }
            fileToSend = outputFilePath
            defer removeSilently(outputFilePath)
        }

        if err := b.sendFileViaEmail(fileToSend); err != nil {
            log.Println("could not send file", err)
            respond(bot, msg, "Sorry. I could not send file")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And registered it in the bot:

...
bot.Handle(tb.OnDocument, b.documentHandler(bot))
...
Enter fullscreen mode Exit fullscreen mode

Creating docker container

I wanted to put this app inside a docker container so I can deploy it easily in DigitalOcean and have it running 24/7.
This came to be a bit tricky. Installation of Calibre Tools was not straightforward. Still, after lots of tries, I came up with the following Dockerfile:

FROM amd64/ubuntu
COPY --from=golang:1.16.11-bullseye /usr/local/go/ /usr/local/go/
ENV PATH="/usr/local/go/bin:${PATH}"

RUN go env -w GOPROXY=direct GOFLAGS="-insecure"
ENV TZ=Europe/Minsk
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y git && \
    apt-get install wget && \
    apt-get install -y python
RUN apt-get install -y ffmpeg libsm6 libxext6
RUN wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin
RUN mkdir -p files
WORKDIR /app

COPY . .

RUN go build

RUN chmod +x ./send-to-kindle-telegram-bot
CMD ["./send-to-kindle-telegram-bot"]
Enter fullscreen mode Exit fullscreen mode

Conclusion

After a lot of testing, I finally got the app up and running on DigitalOcean.

Hope you find this bot useful!
Full code you can find on my GitHub: https://github.com/michaelfmnk/send-to-kindle-telegram-bot
I'm open to any suggestions, ideas, or collaboration :)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay