For a time, I tried to list the features here like many projects do. Eventually, I gave up.

Common and straight-forward features are too trivial to worth listing.
For more unique and novel features, I cannot find standard terms to describe them.
The best way to experience amanobot is by reading this page and going through the
examples. Let’s go.

The chat field represents the conversation. Its type can be private,
group, supergroup, or channel (whose meanings should be obvious, I hope). Above,
•Alisson™• just sent a private message to the bot.

According to Bot API, the method getUpdates
returns an array of Update objects.
As you can see, an Update object is nothing more than a Python dictionary.
In amanobot, Bot API objects are represented as dictionary.

Note the update_id. It is an ever-increasing number. Next time you should use
getUpdates(offset=100000001) to avoid getting the same old messages over and over.
Giving an offset essentially acknowledges to the server that you have received
all update_ids lower than offset:

Sooner or later, your bot will want to send you messages. You should have
discovered your own user id from above interactions. I will use my real id on this example. Remember to substitute your own id:

When processing a message, a few pieces of information are so central that you
almost always have to extract them. Use amanobot.glance() to extract
“headline info”. Try this skeleton, a bot which echoes what you said:

importsysimporttimeimportamanobotfromamanobot.loopimportMessageLoopdefhandle(msg):content_type,chat_type,chat_id=amanobot.glance(msg)print(content_type,chat_type,chat_id)ifcontent_type=='text':bot.sendMessage(chat_id,msg['text'])TOKEN=sys.argv[1]# get token from command-linebot=amanobot.Bot(TOKEN)MessageLoop(bot,handle).run_as_thread()print('Listening ...')# Keep the program running.while1:time.sleep(10)

It is a good habit to always check content_type before further processing.
Do not assume every message is a text.

Besides sending messages back and forth, Bot API allows richer interactions
with custom keyboard and
inline keyboard.
Both can be specified with the parameter reply_markup in Bot.sendMessage().
The module amanobot.namedtuple provides namedtuple classes for easier
construction of these keyboards.

Pressing a button on a custom keyboard results in a
Message object sent to the bot,
which is no different from a regular chat message composed by typing.

Pressing a button on an inline keyboard results in a
CallbackQuery object sent
to the bot, which we have to distinguish from a Message object.

So far, the bot has been operating in a chat - private, group, or channel.

In a private chat, Alice talks to Bot. Simple enough.

In a group chat, Alice, Bot, and Charlie share the same group. As the humans
gossip in the group, Bot hears selected messages (depending on whether in
privacy mode or not) and may
chime in once in a while.

Imagine this. Alice wants to recommend a restaurant to Zach, but she can’t remember the location
right off her head. Inside the chat screen with Zach, Alice types
@Botwhereismyfavoriterestaurant, issuing an inline query to Bot, like
asking Bot a question. Bot gives back a list of answers; Alice can choose one of
them - as she taps on an answer, that answer is sent to Zach as a chat message.
In this case, Bot never takes part in the conversation. Instead, Bot acts as
an assistant, ready to give you talking materials. For every answer Alice chooses,
Bot gets notified with a chosen inline result.

To enable a bot to receive InlineQuery,
you have to send a /setinline command to BotFather.
An InlineQuery message gives the flavorinline_query.

To enable a bot to receive ChosenInlineResult,
you have to send a /setinlinefeedback command to BotFather.
A ChosenInlineResult message gives the flavorchosen_inline_result.

However, this has a small problem. As you types and pauses,
types and pauses, types and pauses … closely bunched inline queries arrive.
In fact, a new inline query often arrives before we finish processing a preceding one.
With only a single thread of execution, we can only process the closely bunched
inline queries sequentially. Ideally, whenever we see a new inline query coming from
the same user, it should override and cancel any preceding inline queries being processed
(that belong to the same user).

My solution is this. An Answerer takes an inline query, inspects its fromid
(the originating user id), and checks to see whether that user has an unfinished thread
processing a preceding inline query. If there is, the unfinished thread will be cancelled
before a new thread is spawned to process the latest inline query. In other words,
an Answerer ensures at most one active inline-query-processing thread per user.

Answerer also frees you from having to call Bot.answerInlineQuery() every time.
You supply it with a compute function. It takes that function’s returned value and calls
Bot.answerInlineQuery() to send the results. Being accessible by multiple threads,
the compute function must be thread-safe.

So far, we have been using a single line of execution to handle messages.
That is adequate for simple programs. For more sophisticated programs where states need
to be maintained across messages, a better approach is needed.

Consider this scenario. A bot wants to have an intelligent conversation with a lot of users,
and if we could only use a single line of execution to handle messages (like what we have done so far),
we would have to maintain some state variables about each conversation outside the message-handling
function(s). On receiving each message, we first have to check whether the user already has a conversation
started, and if so, what we have been talking about. To avoid such mundaneness, we need a structured way
to maintain “threads” of conversation.

Let’s look at my solution. Here, I implemented a bot that counts how many messages have been sent
by an individual user. If no message is received after 10 seconds, it starts over (timeout).
The counting is done per chat - that’s the important point.

importsysimporttimeimportamanobotfromamanobot.loopimportMessageLoopfromamanobot.delegateimportpave_event_space,per_chat_id,create_openclassMessageCounter(amanobot.helper.ChatHandler):def__init__(self,*args,**kwargs):super(MessageCounter,self).__init__(*args,**kwargs)self._count=0defon_chat_message(self,msg):self._count+=1self.sender.sendMessage(self._count)TOKEN=sys.argv[1]# get token from command-linebot=amanobot.DelegatorBot(TOKEN,[pave_event_space()(per_chat_id(),create_open,MessageCounter,timeout=10),])MessageLoop(bot).run_as_thread()while1:time.sleep(10)

A DelegatorBot is able to spawn delegates. Above, it is spawning one MessageCounterper chat id.

Also noteworthy is pave_event_space(). To kill itself after 10 seconds
of inactivity, the delegate schedules a timeout event. For events to work, we
need to prepare an event space.

Detailed explanation of the delegation mechanism (e.g. how and when a MessageCounter is created, and why)
is beyond the scope here. Please refer to DelegatorBot.

You may also want to answer inline query differently depending on user. When Alice asks Bot
“Where is my favorite restaurant?”, Bot should give a different answer than when Charlie asks
the same question.

Everything discussed so far assumes traditional Python. That is, network operations are blocking;
if you want to serve many users at the same time, some kind of threads are usually needed.
Another option is to use an asynchronous or event-driven framework, such as Twisted.

Python 3.5+ has its own asyncio module. Amanobot supports that, too.

Here is how to compile and install Python 3.6, if your O/S does not have it built in:

Because of that (and this is true of asynchronous Python in general), a lot of methods
will not work in the interactive Python interpreter like regular functions would.
They will have to be driven by an event loop.

Async version is under module amanobot.aio. I duplicate the message counter example
below in async style:

importsysimportasyncioimportamanobotfromamanobot.aio.loopimportMessageLoopfromamanobot.aio.delegateimportpave_event_space,per_chat_id,create_openclassMessageCounter(amanobot.aio.helper.ChatHandler):def__init__(self,*args,**kwargs):super(MessageCounter,self).__init__(*args,**kwargs)self._count=0asyncdefon_chat_message(self,msg):self._count+=1awaitself.sender.sendMessage(self._count)TOKEN=sys.argv[1]# get token from command-linebot=amanobot.aio.DelegatorBot(TOKEN,[pave_event_space()(per_chat_id(),create_open,MessageCounter,timeout=10),])loop=asyncio.get_event_loop()loop.create_task(MessageLoop(bot).run_forever())print('Listening ...')loop.run_forever()