Live Location tracking with Telegram
We recently launched a new feature in HelpYouFind.Me, it's built on top of Telegram Bot Platform, and uses Python Telegram Bot as a wrapper, I'm happy to introduce Footsteps.
The Implementation
The first thing you need to know is that Python-Telegram-Bot has something called "filters", and well filters are just that, if a pattern happens and you have a filter for that pattern it will call that filter.
MessageHandler(Filters.reply & (~Filters.command), handle_reply)
MessageHandler(Filters.location, handle_location)
On the example above, every time the bot receive a message, it will check that the message received is a location object or not, if it is then it will execute whatever function we have defined.
Now, speaking of Filter.location
, if you search the PTB documentation for
you'll see that there's no filter for a "live location" object,
because in the end the end a "live location" and a "static location" are
the same, in fact PTB differentiate it by adding a live_period
attribute to the
location object, so if live_location
has a value it's live, if not
it's static.
Live Location Filter
We'll start by declaring the same handler:
MessageHandler(Filters.location, handle_location)
If you run your bot and share a live location you'll see that the
handle_location
function is called several times, sometimes it might be
every second, this means that the filter also works for live location and
static locations, but also means there's no way to differentiate the one
from another.
Then in order to differentiate the two types of locations we need to
check the edited_message
attribute - why? - every time the location
is updated the Telegram API will return you the same message with an
updated date and a new location:
msg_type = 0
if message["edit_date"] is not None:
msg_type += 1
if message["location"]["live_period"] is not None:
msg_type += 1 << 1
What we're doing in the code above is:
- Create a helper variable and init that to zero.
- Sum one to the variable if the
edit_date
is not none. -
If
live_period
is not none sum the result of right and let the leftmost bits.<<
is calledBitwise Left Shift Operator
orZero-Fill Left-Shift
and is used to push zero bits on the rightmost side and let the leftmost bits to overflow.
After that we just only need a simple if/else
validation in order to
execute one action or another based on the msg_type
value:
if msg_type == 0:
context.bot.send_message(user.id, "Single (non-live) location update.")
elif msg_type == 1:
context.bot.send_message(user.id, "End of live period.")
elif msg_type == 2:
context.bot.send_message(user.id, "Start of live period")
elif msg_type == 3:
context.bot.send_message(user.id, "Live location update.")
Explaining a bit that code:
- It will be zero if the
edit_date
in the message is none and thelive_period
in the location object is empty too. - It will be one if the message has and
edit_date
. - It will be two if the message doesn't have an
edit_date
but thelive_period
exists. - And finally three if the message has an
edit_date
and as well thelive_period
is not none.
Glue'ing everything all together, the handle_location
function might
look like this:
def handle_location(update: Update, context: CallbackContext):
user = update.effective_user
msg_type = 0
if update.edited_message:
message = update.edited_message
else:
message = update.message
if message["edit_date"] is not None:
msg_type += 1
if message["location"]["live_period"] is not None:
msg_type += 1 << 1
if msg_type == 0:
context.bot.send_message(user.id, "Single (non-live) location update.")
elif msg_type == 1:
context.bot.send_message(user.id, "End of live period.")
elif msg_type == 2:
context.bot.send_message(user.id, "Start of live period")
elif msg_type == 3:
context.bot.send_message(user.id, "Live location update.")
One Last Thing
As you might think, the live location can be also stopped by user
interaction and not by the end of the live_period
, but also there's no
notification from the Telegram API once the live location has been
completed, that gives you two validations that are needed once the live
location has stopped:
- User manually stops the live location.
- Live location reachs the
live_period
time
In that case a simple solution might be to just start a timer once the user starts a live location:
my_timer = threading.Timer(
interval=message.location.live_period,
function=end_live_location,
args=(update, context)
)
my_timer.name = 'some unique id'
With that, the end_live_location
will be executed once the Timer reachs the
end of the interval, and finally when the user stops the live location
manually just stop the timer by their id:
timer_threads = filter(
lambda t: isinstance(t, threading.Timer), threading.enumerate()
)
current_user_thread = filter(
lambda t: 'some unique id' in t.name,
timer_threads,
)
for t in current_user_thread:
t.cancel()
And that's it, I hope this entry helps you with the implementation of live location tracking using the Telegram Bot Platform.