Пример #1
0
    def handle_edited_message(self, update: telegram.update.Update,
                              context: CallbackContext):
        # NOTE: work in progress
        # TODO: finish implement editing old Messages using edited_messages
        context.bot.send_chat_action(chat_id=update.effective_message.chat_id,
                                     action=ChatAction.TYPING)
        print(json.dumps(update.to_dict(), indent=2))
        message_identifier = get_message_identifier(update)
        msg = Message.objects.get(source_id=message_identifier)
        msg_text = clean_message_text(self.get_message_text(update))
        if msg_text == "":  # Do nothing if message was empty
            # TODO: delete original message in this case?
            return
        msg.text = msg_text
        msg.json = json.dumps(update.to_dict())
        msg.update_record()
        msg.save()
        res_msg = []
        if hasattr(msg, "record"):
            rec: Record = msg.record
            kw_str = rec.name
            words = [_("Edit:"), rec.type] + create_message(
                kw_str, rec) + [rec.time.strftime(" @%H:%M")]
            res_msg.append(" ".join(words))
        else:
            res_msg.append(_("Message.record doesn't exist.") + " 🧐")

        context.bot.send_message(chat_id=update.effective_chat.id,
                                 text="\n".join(res_msg))
Пример #2
0
    def handle_reply(self, update: telegram.update.Update,
                     context: CallbackContext):
        """
        Handle replies to earlier Messages. Doesn't create and save a Record object.

        :param update: telegram Update object
        :param context: telegram CallbackContext
        """

        res_msg = []
        updict = update.to_dict()
        print(json.dumps(updict, indent=2))
        msg = self.get_message(updict)
        reply_message_identifier = format_message_identifier(
            msg["reply_to_message"]["chat"]["id"],
            msg["reply_to_message"]["message_id"])
        try:
            reply_to_msg = Message.objects.get(
                source_id=reply_message_identifier)
            new_msg = self.create_logbook_message(update, save=False)
            new_msg.reply_message = reply_to_msg
            new_msg.save()
            res_msg.append(_("Thanks for the reply!"))
        except Message.DoesNotExist:
            res_msg.append(
                _("Replied message doesn't exist, perhaps it is sent by Bot or it has been deleted?"
                  ))
        context.bot.send_message(chat_id=update.effective_chat.id,
                                 text="\n".join(res_msg))
Пример #3
0
    def choice_cmd(self, update: telegram.update.Update,
                   context: CallbackContext):
        self.setenv()

        print(json.dumps(update.to_dict(), indent=2))
        msg = []
        count = 10
        words = update.message.text.split()
        words.pop(0)  # Remove /search
        if len(words) == 0:
            msg.append(_("Add search string after search command"))
            context.bot.send_message(chat_id=update.effective_chat.id,
                                     text="\n".join(msg))
            return
        query = Q()
        for w in words:
            query = query & Q(description__icontains=w)
        records = Record.objects.filter(query).order_by("-time")

        if records:
            reply_text = _(
                "Found some records. Choose one of these or cancel:")
            options = options_for_records(records, count)
            options.insert(0, [_("Cancel"), ""])
            reply_markup = create_buttons_edit(options)
            update.message.reply_text(reply_text, reply_markup=reply_markup)
            return
        else:
            msg.append(
                _("No records for search string %(kw_str)s found.") %
                {"kw_str": " ".join(words)})
        context.bot.send_message(chat_id=update.effective_chat.id,
                                 text="\n".join(msg),
                                 parse_mode=telegram.ParseMode.HTML)
Пример #4
0
    def set_media_group_id(self, update: telegram.update.Update,
                           msg_text: str) -> str:
        """
        If media_group_id is present, this Update is part of multi-file post.
        Store msg_text for future use.

        :param update: Telegram Update
        :param msg_text: text or caption from Update
        :return:
        """
        updict = update.to_dict()
        msg = self.get_message(updict)
        # First Update has media_group_id and text/caption present
        if "media_group_id" in msg:
            if msg_text != "":
                self.media_group_ids[msg["media_group_id"]] = {
                    "text": msg_text,
                    "time": time.time(),
                }
            elif msg["media_group_id"] in self.media_group_ids:
                msg_text = self.media_group_ids[msg["media_group_id"]]["text"]
                self.media_group_ids[
                    msg["media_group_id"]]["time"] = time.time()
        # Clean up over 60 seconds old media_groups
        for k in list(self.media_group_ids.keys()):
            if self.media_group_ids[k]["time"] + 60 < time.time():
                self.media_group_ids.pop(k)
        return msg_text
Пример #5
0
 def search_cmd(self, update: telegram.update.Update,
                context: CallbackContext):
     self.setenv()
     print(json.dumps(update.to_dict(), indent=2))
     msg = []
     count = 10
     words = update.message.text.split()
     words.pop(0)  # Remove /search
     if len(words) == 0:
         msg.append(_("Add search string after search command"))
         context.bot.send_message(chat_id=update.effective_chat.id,
                                  text="\n".join(msg))
         return
     search_str = words.pop(0)
     records = Record.objects.filter(
         description__icontains=search_str).order_by("-time")
     if records:
         last_dstr = ""
         for r in records[:count]:
             dstr = r.time.astimezone(pytz.timezone(
                 self.timezone)).strftime("%d.%m.%Y")
             tstr = r.time.astimezone(pytz.timezone(
                 self.timezone)).strftime("%H:%M")
             if last_dstr != dstr:
                 msg.append("<code>{}</code>".format(dstr))
                 last_dstr = dstr
             msg.append("<b>{}</b> {}".format(
                 tstr, " ".join(create_message(r.name, r))))
     else:
         msg.append(
             _("No records for search string %(kw_str)s found.") %
             {"kw_str": search_str})
     context.bot.send_message(chat_id=update.effective_chat.id,
                              text="\n".join(msg),
                              parse_mode=telegram.ParseMode.HTML)
Пример #6
0
    def handle_media_group_id(self,
                              update: telegram.update.Update) -> Optional[str]:
        """
        If media_group_id is present, this Update is part of multi-file post.
        It is not guaranteed that multiple Updates are received in order.

        :param update: Telegram Update
        :return:
        """
        cache_time = 600  # seconds
        updict = update.to_dict()
        msg = self.get_message(updict)
        mgid = msg.get("media_group_id")
        if mgid:
            if mgid not in self.media_group_ids:
                self.media_group_ids[mgid] = {
                    "time": time.time(),
                    "updates": [update],
                }
            else:
                self.media_group_ids[mgid]["updates"].append(update)
        # Clean up over `cache_time` seconds old media_groups
        for k in list(self.media_group_ids.keys()):
            if self.media_group_ids[k]["time"] + cache_time < time.time():
                self.media_group_ids.pop(k)
        return mgid
Пример #7
0
    def get_file_data(self, update: telegram.update.Update,
                      context: CallbackContext):
        """
        Get data of certain attachment types from Telegram API.
        "photo" is a special case, because it may contain several instances in a list.

        :param update: Telegram Update object
        :param context:
        :return:

        audio (telegram.Audio, optional)
        document (telegram.Document, optional)
        animation (telegram.Animation, optional)
        game (telegram.Game, optional)
        photo (List[telegram.PhotoSize], optional)
        sticker (telegram.Sticker, optional)
        video (telegram.Video, optional)
        voice (telegram.Voice, optional)
        video_note (telegram.VideoNote, optional)
        """
        _file = {}  # contains all available data of file attachment
        if update.message.photo:  # Loop over photo (List)
            logging.info("photo found")
            max_width = biggest = 0
            for i in range(len(update.message.photo)
                           ):  # Pick largest image and store only it
                if max_width < update.message.photo[i]["width"]:
                    max_width = update.message.photo[i]["width"]
                    biggest = i
            _file["type"] = "photo"
            _file["id"] = update.message.photo[biggest]["file_id"]
        for _ttype in ["audio", "document", "video", "voice",
                       "video_note"]:  # Other document types
            if update.message[_ttype]:
                doc = update.to_dict()["message"][
                    _ttype]  # shortcut to message
                logging.info(f"{_ttype} found")
                _file["type"] = _ttype
                _file["mime_type"] = doc["mime_type"]
                _file["id"] = doc["file_id"]
                if doc.get("file_name"):
                    _file["name"] = doc["file_name"]
                break
        if "id" in _file:
            url = f"https://api.telegram.org/bot{self.token}/getFile?file_id={_file['id']}"
            res = requests.get(url)
            filedata = res.json()
            if filedata["ok"]:
                file_path = filedata["result"]["file_path"]
                _file[
                    "url"] = f"https://api.telegram.org/file/bot{self.token}/{file_path}"
                if "name" not in _file:
                    _file["name"] = os.path.basename(file_path)
                if "mime_type" not in _file:
                    _file["mime_type"] = mimetypes.types_map.get(
                        os.path.splitext(_file["name"])[1], "")
                return _file
            else:
                logging.warning(filedata)
Пример #8
0
 def handle_edited_message(self, update: telegram.update.Update,
                           context: CallbackContext):
     context.bot.send_chat_action(chat_id=update.effective_message.chat_id,
                                  action=ChatAction.TYPING)
     print(json.dumps(update.to_dict(), indent=2))
     res_msg = ["Thank you for editing a message."]
     t = self.get_message_text(update)
     print(f"EDITED {t}")
     context.bot.send_message(chat_id=update.effective_chat.id,
                              text="\n".join(res_msg))
Пример #9
0
def get_message_identifier(update: telegram.update.Update) -> Optional[str]:
    """
    Find out chat id and message_id from message.

    :param update: Telegram Update
    :return: message identifier
    """
    updict = update.to_dict()
    for msg_key in ["message", "edited_message"]:
        if msg_key in updict:
            message = updict[msg_key]
            return format_message_identifier(message["chat"]["id"], message["message_id"])
Пример #10
0
 def show_cmd(self, update: telegram.update.Update,
              context: CallbackContext):
     self.setenv()
     print(json.dumps(update.to_dict(), indent=2))
     msg = []
     count = 10
     words = update.message.text.split()
     words.pop(0)  # Remove /show
     if len(words) == 0:
         categories = ", ".join([
             k[0]
             for k in Category.objects.values_list("type").order_by("type")
         ])
         msg.append(
             _("Add keyword after show command, e.g. one of %(categories)s")
             % {"categories": categories})
         context.bot.send_message(chat_id=update.effective_chat.id,
                                  text="\n".join(msg))
         return
     kw_str = words.pop(0)
     categories = Category.objects.filter(type__iexact=kw_str)
     if len(categories) == 1:
         records = Record.objects.filter(
             category=categories[0]).order_by("-time")
     else:
         records = Record.objects.filter(name=kw_str).order_by("-time")
     if len(words) > 0:
         try:
             count = int(words[0])
         except ValueError:
             pass
     if records:
         last_dstr = ""
         for r in records[:count]:
             dstr = r.time.astimezone(pytz.timezone(
                 self.timezone)).strftime("%d.%m.%Y")
             tstr = r.time.astimezone(pytz.timezone(
                 self.timezone)).strftime("%H:%M")
             if last_dstr != dstr:
                 msg.append("<code>{}</code>".format(dstr))
                 last_dstr = dstr
             msg.append("<b>{}</b> {}".format(
                 tstr, " ".join(create_message(r.name, r))))
     else:
         msg.append(
             _("No records for keyword %(kw_str)s found.") %
             {"kw_str": kw_str})
     context.bot.send_message(chat_id=update.effective_chat.id,
                              text="\n".join(msg),
                              parse_mode=telegram.ParseMode.HTML)
Пример #11
0
    def get_message_text(self, update: telegram.update.Update):
        """
        Find out text or caption from message.

        :param update: Telegram Update
        :return: message text
        """
        msg = self.get_message(update.to_dict())
        if "text" in msg:
            msg_text = msg["text"]
        elif "caption" in msg:
            msg_text = msg["caption"]
        else:
            msg_text = ""
        return msg_text
Пример #12
0
 def set_cmd(self, update: telegram.update.Update,
             context: CallbackContext):
     self.setenv()
     print(json.dumps(update.to_dict(), indent=2))
     words = update.message.text.split()
     words.pop(0)  # Remove /set
     if len(words) == 0:
         msg_lines = [
             _("Add some of these after /set:"),
             "- tz, timezone",
             "- lang, language",
         ]
         context.bot.send_message(chat_id=update.effective_chat.id,
                                  text="\n".join(msg_lines))
         return
     if len(words) == 1:
         msg_lines = [
             _("Empty command"),
             _("You should add value for command too"),
         ]
         context.bot.send_message(chat_id=update.effective_chat.id,
                                  text="\n".join(msg_lines))
         return
     cmd = words[0]
     val = words[1]
     if cmd in ["tz", "timezone"]:
         tz = [s for s in pytz.all_timezones if val.lower() in s.lower()]
         if len(tz) == 1:
             self.timezone = tz[0]
             msg = _("Set timezone to %(tz)s") % {"tz": self.timezone}
             context.bot.send_message(chat_id=update.effective_chat.id,
                                      text=msg)
         else:
             msg = _("Found many") + ":\n" + "\n- ".join(tz)
             context.bot.send_message(chat_id=update.effective_chat.id,
                                      text=msg)
     if cmd in ["lang", "language"]:
         self.language = val
         self.setenv()
         msg = _("Language set to %(lang)s") % {"lang": val}
         context.bot.send_message(chat_id=update.effective_chat.id,
                                  text=msg)
     else:
         msg = _("Unknown setting")
         context.bot.send_message(chat_id=update.effective_chat.id,
                                  text=msg)
Пример #13
0
    def get_user(self, update: telegram.update.Update) -> User:
        """
        Return Django User object corresponding Telegram user.
        If User doesn't exist, it will be created. Also missing user Profile will be created.

        :param update: telegram Update object
        :return: User object
        """
        msg = self.get_message(update.to_dict())
        username = msg["from"]["username"]
        user, created = User.objects.get_or_create(username=username)
        if hasattr(
                user, "profile"
        ) is False:  # Create a Profile for User, if it doesn't exist
            Profile.objects.create(user=user, timezone=self.timezone)
        if created:
            logging.info(f"Created new user {username}")
        return user
Пример #14
0
 def create_logbook_message(self,
                            update: telegram.update.Update,
                            save: bool = True) -> Message:
     message_identifier = get_message_identifier(update)
     updict = update.to_dict()
     msg_text = self.get_message_text(update)
     msg_text = clean_message_text(self.get_message_text(update))
     timestamp = update.message.date
     msg = Message(
         text=msg_text,
         source="TG",
         source_id=message_identifier,
         user=self.get_user(update),
         time=timestamp,
         json=json.dumps(updict),
     )
     if save:
         msg.save()
     return msg
Пример #15
0
    def handle_location(self, update: telegram.update.Update,
                        context: CallbackContext):
        """
        Handle location updates. Doesn't create and save Message or Record objects.

        :param update: telegram Update object
        :param context: telegram CallbackContext
        """
        res_msg = []
        updict = update.to_dict()
        print(json.dumps(updict, indent=2))
        msg = self.get_message(updict)
        loc = msg["location"]
        trk_src, created = Tracksource.objects.get_or_create(
            name="Logbook Telegram Bot", slug="logbookbot")
        if "edit_date" in msg:
            epoch = msg["edit_date"]
        else:
            epoch = msg["date"]
            res_msg.append(
                _("Activated location tracking. Location updates will be silently saved into database."
                  ))
        ts = pytz.UTC.localize(datetime.datetime.utcfromtimestamp(epoch))
        trk_pnt = Trackpoint.objects.create(
            user=self.get_user(update),
            tracksource=trk_src,
            lat=loc["latitude"],
            lon=loc["longitude"],
            hacc=loc.get("horizontal_accuracy"),
            course=loc.get("heading"),
            time=ts,
            geometry=Point(loc["longitude"], loc["latitude"]),
        )
        if res_msg:
            context.bot.send_message(chat_id=update.effective_chat.id,
                                     text="\n".join(res_msg))
Пример #16
0
    def handle_message(self, update: telegram.update.Update,
                       context: CallbackContext):
        context.bot.send_chat_action(chat_id=update.effective_message.chat_id,
                                     action=ChatAction.TYPING)
        lb_message = self.create_logbook_message(update)
        res_msg = []
        updict = update.to_dict()
        message = self.get_message(updict)
        msg_text = lb_message.text
        print(json.dumps(updict, indent=2))
        logging.info(f"Message time: {lb_message.time}, text: {msg_text}")
        media_group_id = self.handle_media_group_id(update)
        if msg_text == "" and media_group_id is None:  # Do nothing if message was empty
            logging.info("Ignore empty message")
            return
        # If this is not the first multi file update, there should be existing Message stored
        if media_group_id in self.media_group_ids:
            if "msg" in self.media_group_ids[media_group_id]:
                msg = self.media_group_ids[message["media_group_id"]]["msg"]
                res_msg += self.save_remaining_updates(msg, media_group_id,
                                                       context)
                context.bot.send_message(chat_id=update.effective_chat.id,
                                         text="\n".join(res_msg))
                return
            if msg_text == "":
                return
        if media_group_id in self.media_group_ids:
            self.media_group_ids[media_group_id]["msg"] = lb_message
        else:  # Save file
            rf = self.download_files(lb_message, update, context)
            if rf:
                res_msg.append(f"Saved attachment to {rf.file.name}.")

        rec: Record = lb_message.update_record()
        if rec is None and msg_text != "":
            kw_str = sanitize_keyword(msg_text.split()[0])
            kws = Category.objects.filter(keywords__contains=[kw_str])
            if kws.count() == 1:  # keyword found, requested latest records?
                reply_text = _(
                    "Category '%(kw_str)s' found. Choose one of these or cancel:"
                ) % {
                    "kw_str": kw_str
                }
                records = Record.objects.filter(name=kw_str).order_by("-time")
                options = options_for_records(records, 5)
                options.insert(0, [_("Cancel"), ""])
                reply_markup = create_buttons_edit(options)
            else:
                reply_text = _(
                    "Category '%(kw_str)s' not found. Choose one of these or cancel:"
                ) % {
                    "kw_str": kw_str
                }
                options = [[k.type] for k in Category.objects.order_by("type")]
                for o in options:
                    o.append(f"newkw:{o[0]}:{kw_str}")
                options.insert(0, [_("Cancel"), "Cancel query"])
                reply_markup = create_buttons(options)
            update.message.reply_text(reply_text, reply_markup=reply_markup)
            return
        else:
            kw_str = sanitize_keyword(msg_text.split()[0])
            time_str = rec.time.astimezone(pytz.timezone(
                self.timezone)).strftime(" @%H:%M")
            res_msg.append(" ".join(create_message(kw_str, rec)) + time_str)
            if rec.abv is not None and rec.volume is not None:
                res_msg.append("BAC after latest alcohol: {:.2f}‰".format(
                    calculate_bac()))
        res_msg += self.save_remaining_updates(lb_message, media_group_id,
                                               context)
        context.bot.send_message(chat_id=update.effective_chat.id,
                                 text="\n".join(res_msg))