Security of apps based on Telegram api?

I am using a telegram api based app named “TurboTel Pro”. I am happy with its different look and settings but I am concerned about the data it would take of me.

Can someone tell me what data does these api based apps take? I read in FAQ of “iMe” (a telegram api based app) that they don’t collect or they are not able to collect or see data from telegram servers but they also connect to third party servers to run some of their services.

Are they saying right? How much can I believe in them? What about third party service providers used in app? Is my data safe from third parties?

I also read that after verson 5.0 of telegram bots, api based app developers can connect their app to other servers also. Am I gonna loose some of my data in hands of other new-non-trusted companies?

Security of apps built on Telegram api?

I am using a telegram api based app named “TurboTel Pro”. I am happy with its different look and settings but I am concerned about the data it would take of me.

Can someone tell me what data does these api based apps take? I read in FAQ of “iMe” (a telegram api based app) that they don’t collect or they are not able to collect or see data from telegram servers but they also connect to third party servers to run some of their services.

Are they saying right? How much can I believe in them? What about third party service providers used in app? Is my data safe from third parties?

I also read that after verson 5.0 of telegram bots, api based app developers can connect their app to other servers also. Am I gonna loose some of my data in hands of other new-non-trusted companies?

❓ASK – Looking for a BOT for share my referral links into Telegram channel | Proxies123.com

Hi am Caleb, and this what I can say from my personal experience.
If you want to make online, there are no shortcuts to that.Because most of the websites claiming that you can make money in a short time, 75% of them are scammer and fraudsters, believe me because I have been a victim several times……
I have lost thousands of dollars online until a friend introduced petronpay to me and believe when I say, They are one of the best investment companies you can find online.
You earn 300% profit of your investment in the space of ten months if you decide not to refer anyone, But if you choose to refer, that is where the Real cash is because you can earn TEN Times 10x your initial investments in the shortest time possible due to their bonuses and other incentives. THEY HAVE BEEN A BLESSING TO ME SINCE I JOINED
Feel free to ask me any questions you got.
gmail- [email protected] .com

 

Python Telegram Quiz Bot – Code Review Stack Exchange

To improve my Python knowledge I started a small coding project: A telegram chat/quiz bot that is based on official questions about basketball rules. The bot reads them from one or multiple .csv files.

The bot has two modes:

  • Exercise mode: Questions are asked until the user selects to see the results. After each answer of the user, the correct answer and reason is displayed.
  • Test mode: The user is asked 21 questions. After all answers are given the bot shows the result and displays wrong answers together with the correct answer and reasoning.

Questions may have an image associated with them, but currently teh question text is currently included in the image itself.

I tried to seperate game logic and chat interaction as good as possible, but failed somewhat.

I’m glad for any feedback.
Thanks in advance!

Known Limitations

  • Currently no logging, but feedback on that point is still appreciated.
  • The layout of the .csv files is not easily changable, as they are from an external source.

Code

bot.py

import csv
import datetime
import logging
import os
import random
from logging import debug, info
from pathlib import Path

import telegram
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import (CallbackContext, CallbackQueryHandler,
                          CommandHandler, ConversationHandler, Filters,
                          MessageHandler, Updater)

from game import Game
from question import Question
from question_catalogue import QuestionCatalogue

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    level=logging.INFO)
__location__ = os.path.realpath(
    os.path.join(os.getcwd(), os.path.dirname(__file__)))
# read bot api token from file
TOKEN_FILE = os.path.join(__location__, 'token.txt')
TOKEN = Path(TOKEN_FILE).read_text().strip()

TEST_QUESTION_COUNT = 21
MAX_ERRORS = 6
MAX_ERRORS_PERCENTAGE = MAX_ERRORS / TEST_QUESTION_COUNT


def start(update: Update, context: CallbackContext) -> None:
    """Called when starting the bot or when finishing a game. Shows short "How To".

    Args:
        update (Update): Telegram Update
        context (CallbackContext): Telegram Context
    """
    global catalogue
    context.bot.send_message(
        text="🏀 *Willkommen zum Basketball Regelquiz Bot*n"
        + f"Dieser Bot basiert auf dem aktuellen Regel- und Kampfrichterfragenkatalog des DBB "
        + f"({catalogue.catalogue_name(True)}) "
        + f"mit insgesamt {catalogue.question_count()} Fragen. n"
        + "Hier erklären wir dir kurz wie's geht: n"
        + "Nutze den _Übungsmodus_ um dir einzelne Fragen anzeigen zu lassen. "
        + "Nach jeder Frage siehst du direkt die richtige Antwort und Begründung. n"
        + f"Beim _Regeltest_ bekommst du {TEST_QUESTION_COUNT} Fragen gestellt, von denen du maximal {MAX_ERRORS} falsch beantworten darfst.",
        chat_id=update.effective_chat.id,
        parse_mode='MarkdownV2',
        reply_markup=telegram.ReplyKeyboardRemove()
    )

    keyboard = (
        (InlineKeyboardButton("📚 Übungsmodus", callback_data='exercise')),
        (InlineKeyboardButton("📝 Regeltest", callback_data='test')),
    )
    reply_markup = InlineKeyboardMarkup(keyboard)

    update.message.reply_text(
        'Bitte wähle den gewünschten Modus aus:', reply_markup=reply_markup)


def ask_question(update: Update, context: CallbackContext):
    """Displays the next question from the game and displays the ReplyKeyboard.

    Args:
        update (Update): Telegram Update
        context (CallbackContext): Telegram Context
    """
    try:
        context.user_data("game")
    except KeyError:
        help_command(update, context)
        return
    try:
        question_data = context.user_data("game").show_question()
    except IndexError:
        # no more questions available
        context.bot.send_message(chat_id=update.effective_chat.id,
                                 text="Du hast alle verfügbaren Fragen in diesem Durchlauf bereits beantwortet.",
                                 parse_mode=telegram.ParseMode.MARKDOWN_V2)
        show_result(update, context)
        return

    reply_markup = telegram.ReplyKeyboardMarkup((('Ja'), ('Nein')), True)

    context.bot.send_message(chat_id=update.effective_chat.id,
                             text=question_data(0),
                             parse_mode=telegram.ParseMode.MARKDOWN_V2,
                             reply_markup=reply_markup)

    if question_data(1) != None:
        # question is image question
        context.bot.sendPhoto(chat_id=update.effective_chat.id,
                              photo=open(question_data(1), 'rb'),
                              parse_mode=telegram.ParseMode.MARKDOWN_V2)


def show_result(update: Update, context: CallbackContext):
    """Shows result of game to user including the evaluation. Presents options to play again

    Args:
        update (Update): Telegram Update
        context (CallbackContext): Telegram Context
    """
    try:
        context.user_data("game")
    except KeyError:
        help_command(update, context)
        return
    if not context.user_data("game").finished():
        # only show results if game is finished
        return

    result = context.user_data("game").result()
    result_texts = result(0)
    questions = result(1)

    first = True
    reply_markup = telegram.ReplyKeyboardMarkup((('/start')), True)

    for text in result_texts:
        context.bot.send_message(chat_id=update.effective_chat.id,
                                 text=text,
                                 parse_mode=telegram.ParseMode.MARKDOWN_V2,
                                 reply_markup=reply_markup)
        if first:
            reply_markup = None
    # show wrong answers and their reason
    for question in questions:
        if question.image_question():
            # question is image question, show question number before image
            context.bot.send_message(chat_id=update.effective_chat.id,
                                     text=question.question_header(),
                                     parse_mode=telegram.ParseMode.MARKDOWN_V2)
            # reason as caption below image
            context.bot.sendPhoto(chat_id=update.effective_chat.id,
                                  photo=open(question.path, 'rb'),
                                  caption=question.reason_string(),
                                  parse_mode=telegram.ParseMode.MARKDOWN_V2)
        else:
            # one message containing all
            context.bot.send_message(chat_id=update.effective_chat.id,
                                     text=question.full_question_string(),
                                     parse_mode=telegram.ParseMode.MARKDOWN_V2)

    context.bot.send_message(chat_id=update.effective_chat.id,
                             text="🔁 Benutze /start um ein neues Quiz zu starten.",
                             parse_mode=telegram.ParseMode.MARKDOWN_V2,
                             reply_markup=reply_markup)


def button(update: Update, context: CallbackContext) -> None:
    """Handles inline keyboard presses.

    Args:
                update (Update): Telegram Update
        context (CallbackContext): Telegram Context
    """
    query = update.callback_query

    # CallbackQueries need to be answered, even if no notification to the user is needed
    # Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
    query.answer()

    global catalogue

    if query.data == "exercise":
        mode = query.data
        query.edit_message_text(text=f"📚 Übungsmodus ausgewählt")
        context.user_data("game") = Game(query.data, catalogue)
        ask_question(update, context)
    elif query.data == "test":
        mode = query.data
        query.edit_message_text(text=f"📝 Testmodus ausgewählt")
        context.user_data("game") = Game(
            query.data, catalogue, TEST_QUESTION_COUNT, MAX_ERRORS_PERCENTAGE)
        ask_question(update, context)
    elif query.data == "next":
        query.edit_message_text(text=f"⏭️ Nächste Frage:")
        ask_question(update, context)
    elif query.data == "result":
        query.edit_message_text(text=f"📚 Übungsmodus beendet")
        show_result(update, context)


def exercise_options_handler(update: Update, context: CallbackContext) -> None:
    """Removes inline keyboard and proceeds with next question.

    Args:
        update (Update): Telegram Update
        context (CallbackContext): Telegram Context
    """
    if context.match.group(0).lower() == "nächste frage":
        ask_question(update, context)
    else:
        show_result(update, context)


def answer_handler(update: Update, context: CallbackContext) -> None:
    """Handles user answers based on chat message. Shows inline keyboards for next options or shows result.

    Args:
        update (Update): Telegram Update
        context (CallbackContext): Telegram Context
    """
    try:
        context.user_data("game")
    except KeyError:
        help_command(update, context)
        return

    if context.match.group(0).lower() == "ja":
        context.user_data("game").add_answer(True)
    else:
        context.user_data("game").add_answer(False)

    if context.user_data("game").mode == "exercise":
        # show answer validation
        for text in context.user_data("game").check_answer():
            context.bot.send_message(chat_id=update.effective_chat.id,
                                     text=text,
                                     parse_mode=telegram.ParseMode.MARKDOWN_V2,
                                     reply_markup=telegram.ReplyKeyboardMarkup(
                                         (('Nächste Frage'), ('Auswertung')), True
                                     ))

    if context.user_data("game").mode == "exercise":
        keyboard = (
            (InlineKeyboardButton("⏭️ Nächste Frage",
                                  callback_data='next')),
            (InlineKeyboardButton("📊 Auswertung", callback_data='result')),
        )
        reply_markup = InlineKeyboardMarkup(keyboard)
        update.message.reply_text(
            'Wie möchtest du fortfahren:', reply_markup=reply_markup)
    else:
        if not context.user_data("game").finished():
            ask_question(update, context)
        else:
            show_result(update, context)


def help_command(update: Update, context: CallbackContext) -> None:
    """Displays help message if a command is unknown

    Args:
        update (Update): Telegram Update
        context (CallbackContext): Telegram Context
    """
    update.message.reply_text("Benutze /start um den Bot zu starten.")


def main():
    """Creates question catalogue and starts bot
    """
    global catalogue

    catalogue = QuestionCatalogue(2020, 2)
    catalogue.add_questions_from_file(
        os.path.join(__location__, '../resources/rules.csv'))
    catalogue.add_questions_from_file(
        os.path.join(__location__, '../resources/kampf.csv'))
    catalogue.add_questions_from_file(
        os.path.join(__location__, '../resources/image-questions.csv'), True)

    updater = Updater(
        token=TOKEN, use_context=True)
    dispatcher = updater.dispatcher

    updater.dispatcher.add_handler(CommandHandler('start', start))
    updater.dispatcher.add_handler(CallbackQueryHandler(button))
    updater.dispatcher.add_handler(MessageHandler(
        Filters.regex("^(?:ja|Ja|nein|Nein)$"), answer_handler))
    updater.dispatcher.add_handler(MessageHandler(
        Filters.regex("^(?:Nächste Frage|Auswertung)$"), exercise_options_handler))
    updater.dispatcher.add_handler(CommandHandler('help', help_command))

    updater.start_polling()

    updater.idle()


if __name__ == '__main__':
    main()


game.py

import datetime

import telegram

from question import Question
from question_catalogue import QuestionCatalogue
from quiz import Quiz

# settings for default test
TEST_QUESTION_COUNT = 21
MAX_ERRORS = 6
MAX_ERRORS_PERCENTAGE = MAX_ERRORS / TEST_QUESTION_COUNT


class Game:

    def __init__(self, mode: str, question_catalogue: QuestionCatalogue, questions_count: int = TEST_QUESTION_COUNT, max_error_percentage: float = MAX_ERRORS_PERCENTAGE):
        """Creates a new game with the given question catalogue and starts the timer for this game.

        Args:
            mode (str): Either "test" or "exercise"
            question_catalogue (QuestionCatalogue): The questions to be used in this game
            questions_count (int, optional): The number of questions to ask. Ignored if mode is "exercise". Defaults to TEST_QUESTION_COUNT.
            max_error_percentage (float, optional): Maximum percentage of wrongly answered questions to pass. Defaults to MAX_ERRORS_PERCENTAGE.
        Raises:
            ValueError: If the mode is not either "test" or "exercise".
        """
        self.mode = mode
        self.question_catalogue = question_catalogue
        if self.mode == "test":
            self.quiz = Quiz(self.question_catalogue, questions_count)
        elif self.mode == "exercise":
            self.quiz = Quiz(self.question_catalogue)
        else:
            raise ValueError
        self.timer = datetime.datetime.now().replace(microsecond=0)
        self.max_error_percentage = max_error_percentage

    def finished(self) -> bool:
        """Check whether this game is finished.

        Returns:
            bool: True if finished, False otherwise.
        """
        return self.quiz.finished()

    def time_elapsed(self) -> datetime.timedelta:
        """Calculates time passed from start/creation of the game until now.

        Returns:
            timedelta: Time passed since start of the game
        """
        return datetime.datetime.now().replace(microsecond=0) - self.timer

    def show_question(self) -> str:
        """Returns the next question formatted as markdown string containing question number and the question text.

        Returns:
            str: The formatted question string
        """
        question = self.quiz.get_next_question()

        text = (f"❓ *Frage {self.quiz.get_current_question_number()}/{self.quiz.question_count()} "
                f"({question.full_number(True)})*:n "
                f"{telegram.utils.helpers.escape_markdown(question.question_text, 2)}")
        return (text, question.path)

    def add_answer(self, answer: bool) -> None:
        """Adds the users answer for the current question.

        Args:
            answer (bool): The users answer
        """
        self.quiz.add_answer(answer)

    def check_answer(self) -> (str):
        """Stores users answer,  returns feedback and the correct reasoning. Only used in exercise mode.

        Returns:
            (str): Array of markdown formatted strings
        """
        if self.mode == "exercise":
            message = ()
            message.append(self.quiz.check_answer())
            message.append(self.quiz.get_current_question().reason_string())
            return message

    def result(self) -> (str):
        """Returns feedback and evaluation of the current game. Does nothing when in test mode and the game is not finished yet.
            Contains all wrongly answered questions and the correct reasoning in test mode.
        Returns:
            (str): Array of markdown formatted strings containing error percentages, evaluation and time elapsed.
        """
        if self.mode == "test" and (not self.quiz.finished()):
            return

        result = ()
        questions = ()
        result.append((f"📊 *Auswertung* n"
                       f"_Fehler_: {self.quiz.get_wrong_answer_count()} von {self.quiz.question_count()}"
                       f"({self.quiz.get_wrong_percentage_string(True)}) n"
                       f"_Ergebnis_: {self.quiz.result(self.max_error_percentage)} n"
                       f"_Benötigte Zeit_: {self.time_elapsed()}⏲️"))

        if self.mode == "test":
            result.append("Folgende Fragen wurden _falsch_ beantwortet:")
            if self.quiz.get_wrong_answers():
                # add wrong questions and their reason
                questions = (question('question')
                             for question in self.quiz.get_wrong_answers())
            else:
                # no wrong answer, congratulations!
                result.append("_🎉 keine 🎉_")
        return (result, questions)


quiz.py

import random

import telegram

from question import Question
from question_catalogue import QuestionCatalogue


class Quiz:

    def __init__(self, question_catalogue: QuestionCatalogue, question_count: int = 0):
        """Creates a new quiz based on the given question catalogue

        Args:
            question_catalogue (QuestionCatalogue): Questions to randomly choose from.
            question_count (int, optional): Populate the quiz with question_count questions. Must be greater or equal to 0.
                If 0 is given, "exercise mode" is used.
                This mode always appends new questions to the quiz when the next question is asked. Defaults to 0.

        Raises:
            ValueError: If the question_count is negative.
        """
        self.question_catalogue = question_catalogue
        if question_count < 0:
            raise ValueError
        # do not select more questions than available
        self.questions = ({"question": question, "answer": None} for question in random.sample(
            self.question_catalogue.questions, min(question_count, question_catalogue.question_count())))
        self.current_question_index = -1

    def question_count(self) -> int:
        """Current number of questions int this quiz.

        Returns:
            int: Number of questions
        """
        return len(self.questions)

    def get_current_question(self) -> Question:
        """Returns the active question. This is usually the first unanswered question.

        Returns:
            Question: The current active question
        """
        return self.questions(self.current_question_index)('question')

    def check_answer(self, question: Question = None) -> str:
        """Checks and stores the answer for the current active question or a specified question.

        Args:
            question (Question, optional): Question to check the answer for. If None is given, the current question is assumed. Defaults to None.

        Returns:
            str: String containing feedback if the question was answered correctly.
        """
        index = self.current_question_index
        if question != None:
            index = self.questions.index(
                {"question": question, "answer": None})
        if self.questions(index)('answer') == self.questions(index)('question').answer:
            return "✅ Richtig! 🎉"
        else:
            return "❌ Leider falsch 😔"

    def get_current_question_number(self) -> int:
        """Returns the number of the question in this quiz. 1-based indexed.

        Returns:
            int: The 1-based index of the current active question
        """
        return self.current_question_index + 1

    def finished(self) -> bool:
        """Returns whether this quiz is finished (there are no unanswered questions) or not.

        Returns:
            bool: True if quiz is finished. False otherwise.
        """
        # finished if there are no unanswered questions
        return len(list(filter(lambda el: (el('answer') == None), self.questions))) == 0

    def add_unique_question(self) -> None:
        """Appends a unique question from the catalogue to the quiz.
        Raises:
            IndexError: If the quiz already contains all questions from the catalogue
        """
        current_questions = set(x('question') for x in self.questions)
        # calculate difference of the two lists to avoid adding duplicates
        available_questions = list(
            set(self.question_catalogue.questions) - current_questions)
        if available_questions:
            self.questions.append(
                {"question": random.choice(available_questions), "answer": None})

    def get_next_question(self) -> Question:
        """Returns the next question of the quiz.
        Sets the active question accordingly.
        If the quiz is already finished (usually in exercise mode) a new question is appended and then returned.

        Returns:
            Question: The new current active question.
        Raises:
            IndexError: If the quiz already contains all questions from the catalogue
        """
        if self.finished():
            self.add_unique_question()

        if self.current_question_index == -1 or self.questions(self.current_question_index)('answer') != None:
            # only go to next question when user answered the current one
            self.current_question_index += 1
        return self.questions(self.current_question_index)('question')

    def add_answer(self, answer: bool, question: Question = None) -> None:
        """Stores an answer for the given question. 

        Args:
            answer (bool): The user submitted answer to the question
            question (Question, optional): The question the answer was submitted for. If None is given, the current active question is assumed. Defaults to None.
        """
        index = self.current_question_index
        if question != None:
            index = self.questions.index(
                {"question": question, "answer": None})
        if self.questions(index)('answer') == None:
            self.questions(index)('answer') = answer

    def get_wrong_answers(self) -> list:
        """Returns all wrongly answered questions as a list.

        Returns:
            list: All questions where the users answer does not match the correct answer
        """
        return list(filter(lambda el: (el('answer') != el('question').answer), self.questions))

    def get_wrong_answer_count(self) -> int:
        """Returns the number of wrongly answered questions

        Returns:
            int: Number of questions where the users answer does not match the correct answer 
        """
        return len(self.get_wrong_answers())

    def get_wrong_percentage(self) -> float:
        """Computes the percentage of wrong answers based on total question count in this quiz

        Returns:
            float: The percentage of wrongly answered questions
        """
        return self.get_wrong_answer_count() / self.question_count()

    def get_wrong_percentage_string(self, escape_markdown: bool = False) -> str:
        """Formates the percentage as string with two decimals precision, e.g. "66.67%".

        Args:
            escape_markdown (bool, optional): Escape the returned string for use in markdown. Defaults to False.

        Returns:
            str: Formatted percentage string
        """
        percentage = f"{round(self.get_wrong_percentage() * 100, 2)}%"
        if escape_markdown:
            return telegram.utils.helpers.escape_markdown(percentage, 2)
        else:
            return percentage

    def result(self, max_error_percentage: float) -> str:
        """Returns the result (passed or failed) of the quiz as string depending on the allowed error percentage.

        Args:
            max_error_percentage (float): The maximum percentage of wrongly answered questions allowed to pass this quiz.

        Returns:
            str: Result of the as string
        """
        if self.get_wrong_percentage() <= max_error_percentage:
            return "bestanden ✅"
        else:
            return "NICHT BESTANDEN ❌"


question_catalogue.py

import csv
import os

import telegram

from question import Question


class QuestionCatalogue:

    def __init__(self, year: int, version: int, questions: (Question) = ()):
        """Creates a new question catalogue, containing zero ore more questions

        Args:
            year (int): The year of publication for this catalogue
            version (int): The version within the publication year
            questions ((Question), optional): Questions to initialize the catalogue with. Defaults to ().
        """
        self.questions = questions
        self.version = version
        self.year = year

    def catalogue_name(self, escape_markdown: bool = False) -> str:
        """The name of the catalogue, composed of year and version, e.g. "2020_V2"

        Args:
            escape_markdown (bool, optional): Escape the resulting name for markdown use. Defaults to False.

        Returns:
            str: The name of the catalogue
        """
        name = self.year
        if self.version != 1:
            name = f"{self.year}_V{self.version}"

        if escape_markdown:
            return telegram.utils.helpers.escape_markdown(name, 2)
        else:
            return name

    def question_count(self) -> int:
        """Returns number of questions in this catalogue

        Returns:
            int: Number of questions
        """
        return len(self.questions)

    def add_questions_from_file(self, filename: str, image_mode: bool = False):
        """Import questions from a csv-formatted file.
        Lines must be formatted according to the following format: "Nr.;Frage;J;N;Antwort;Art.;"
        If image_mode is used the format is as follows: "Nr.;Frage;J;N;Antwort;Art.;Path;"
        Args:
            filename (str): The csv file to read from. Uses ";" as delimiter.
            image_mode (bool, optional): The input file contains image questions, additional path column to read. Defaults to False.
        """
        with open(filename, newline='') as csvfile:
            reader = csv.DictReader(csvfile, delimiter=';')
            for row in reader:
                if row('J').lower() == 'x':
                    answer = True
                else:
                    answer = False
                if not('K-' in row('Nr.') or 'R-' in row('Nr.')):
                    break
                # extract question type and number
                parts = row('Nr.').split('-')
                question_type = parts(0)
                number = parts(1)
                path = None
                if image_mode:
                    # construct full path to image
                    path = os.path.join(os.path.dirname(filename), row('Path'))
                # create question
                self.questions.append(Question(question_type, number,
                                               row('Frage'), row('Antwort'), row('Art.'), answer, path))

question.py

import telegram


class Question:

    def __init__(self, question_type: str, number: int, question_text: str, reason_text: str, article: str, answer: bool, path: str = None):
        """Creates a new question object

        Args:
            question_type (str): Type of the question, mostly "R" for rule questions or "K" for questrions for table officials.
            number (int): The number of the question
            question_text (str): The question itself
            reason_text (str): The reasoning for the correct answer
            article (str): The article or interpretation on which the question is based
            answer (bool): The correct answer
            path (str, optional): The path to an image, if the question has one. Defaults to None.
        """
        self.question_type = question_type
        self.number = number
        self.question_text = question_text
        self.reason_text = reason_text
        self.article = article
        self.answer = answer
        self.path = path

    def image_question(self) -> bool:
        """Determines whether this question has an image and is therefore an image question or not.

        Returns:
            bool: True, if the question is an image question. False otherwise.
        """
        return self.path != None

    def full_number(self, escape_markdown: bool = False) -> str:
        """Returns the complete question number, composed of the question type and the question number

        Args:
            escape_markdown (bool, optional): Escape the returned string for use in markdown. Defaults to False.

        Returns:
            str: Complete question number, e.g. "R-42" or "K-9"
        """
        text = self.question_type + "-" + self.number
        if escape_markdown:
            return telegram.utils.helpers.escape_markdown(text, 2)
        else:
            return text

    def question_header(self) -> str:
        """Returns markdown formatted header for questions containing the full question number only

        Returns:
            str: Header string , markdown escaped
        """
        return f"*❓ Frage {telegram.utils.helpers.escape_markdown(self.full_number(), 2)}*: n"

    def full_question_string(self) -> str:
        """Returns markdown escaped string containing question number, question text and reason

        Returns:
            str: The resulting question, markdown escaped
        """
        return (f"{self.question_header()}"
                f"{telegram.utils.helpers.escape_markdown(self.question_text, 2)}n"
                f"{self.reason_string()}")

    def reason_string(self) -> str:
        """Returns markdown escaped reasoning string

        Returns:
            str: Reason string, markdown escaped
        """
        return (f"ℹ️ *Begründung*: n"
                f"{telegram.utils.helpers.escape_markdown(self.reason_text, 2)}n")


Sample .csv file for non-image questions

Nr.;Frage;J;N;Antwort;Art.;
R-42;This is a sample question and not an actual question. Correct?;;x;No (Art. 4). Sample questions are illegal according to article 4.;4;

Sample .csv file for image questions, mostly no question text

Nr.;Frage;J;N;Antwort;Art.;Path;
K-9;;x;;Yes (KRHB);KRHB;./images/K-9.png;

Best Crypto Signals Group On Telegram In 2021 & Crypto Trading Bot

Free Crypto Signals – https://t.me/btctradingclub

Free Crypto signals group on Telegram in 2021 providing free daily 3-4 high quality Crypto signals for Binance, Bitmex, Bittrex, Binance Futures, Wazirfx, Coinswitch, Bybit, Huobi, Bitfinex, Okex & many other exchanges around 80-90% accuracy. All signals are provided by our Experts Team with proper take profit and proper stop loss & regular updates are also provided for Traders which ensures they enter and close trades at accurate time. We also provide Free Crypto trading BOT which copied all your Signals in account get free from manual trading & Learn Cryptocurrency trading free training will be provided for more join above given Telegram group

Signals result – https://t.me/premiumclubresults

Jobs – 35 BMF – 3 MINUTE TASK TELEGRAM | Proxies123.com

Thread starter Title Forum Replies Date

batuhankural

Jobs GET 80 BMF in 1 minute very Easy TASK Marketplace 40

Rc cruz

Jobs 50 BMF for less than 5 minute task! Marketplace 2

Absrexo

Closed 20 Bmf for less than a minute task Marketplace 20

Kambas

Jobs 20 BMF + $2.50 for a 2 minute task Marketplace 70

99Cryptos

Closed 100 BMF for less than 2 minute task,Sign Up for Airdrop!. Marketplace 31

Rc cruz

Closed 20 bmf for less than 3 minute task(just sign up) Marketplace 35

Rc cruz

Closed 20 BMF for less than 3 minute task Marketplace 17

Coding Authority

Closed 20 BMF for 2 Minute task Marketplace 18

99Cryptos

Deal Join 1 minute Airdrop for 50 BMF+20$(Already listed). Marketplace 43

M

Jobs GET 40 BMF TASK 6 (FREE) Marketplace 12

tchisom

Deal 3$ TRX (or any crypto) for my BMF Buy / Sell / Trade 17

M

Jobs GET 40 BMF TASK 5 (FREE) Marketplace 16

BMF

ANNOUNCEMENT Pool of 5,000,000 BMF Points – List of Wanted Reviews! Support & Announcements 2

C

Jobs 65 BMF For subscribe & follow Marketplace 54

TomasMNE

Closed Earn 500 BMF Easy Task, Install Browser Marketplace 4

mdtp422

Jobs (40 BMF) SignUp Pipeflare and Claim Marketplace 13

TomasMNE

Jobs Earn 100 BMF Install Android APP Marketplace 12

Josemendez

Deal Join this airdrop and get 50 bmf from me Marketplace 5

mhmgs19

50 BMF for signing up Weshareabundance Buy / Sell / Trade 4

Santh

Buy need Ebay.com profile older than 2015 for 600 bmf. Buy / Sell / Trade 0

solacejoy

SUPPORT My opinion for BMF Like Support & Announcements 2

Nick77

Closed 35 BMF – Sub And Like Youtube Video Buy / Sell / Trade 10

BMF Staff

Roll 21: Win 100,000 BMF Points! Freebies & Giveaways 0

Kambas

Jobs 100 BMF – Easy signup task Marketplace 64

BMF Staff

Giveaways Congrats OffersBux, candybutcher, Guess????, riopp, Evenilson Alves pereira – Winners Prize Pool Worth 100,000 BMF Points! Freebies & Giveaways 5

BMF

Jobs Easy 50 bmf – task free Marketplace 37

EzearninG

☑️NEW Ai Telegram wallet ! 30 aicoin ! pre sell and AirDrop ! Free 50 BMF for REF ! Airdrops Forum 0

A

Jobs DEAL 11 – 50 BMF – FREE Marketplace 60

A

Jobs DEAL 10 – 50 BMF – FREE Marketplace 31

A

Jobs DEAL 9 – 50 BMF – FREE Marketplace 25

A

Jobs DEAL 8 – 50 BMF – FREE Marketplace 38

A

Jobs DEAL 7 – 50 BMF – FREE Marketplace 26

A

Jobs DEAL 6 – 50 BMF – FREE Marketplace 21

A

Jobs DEAL 5 – 50 BMF – FREE Marketplace 22

A

Jobs DEAL 4 – 50 BMF – FREE Marketplace 22

A

Jobs DEAL 3 – 50 BMF – FREE Marketplace 23

A

Jobs DEAL 2 – 50 BMF – FREE Marketplace 23

A

Jobs DEAL 1 – 50 BMF – FREE Marketplace 25

Voltagetherealone

Deal Join Ai Marketing and get 50 BMF Marketplace 8

BMF

Jobs Easy 50 bmf – task 1 Marketplace 41

Joanbetac

SUPPORT why did i lose my BMF? Support & Announcements 2

BMF

Jobs Easy 50 bmf – task 2 Marketplace 43

Gugul 992

Jobs GET 80 BMF To install ZikTalk App Marketplace 8

btcminer

☑️NEW TechToken Airdrop + 100 BMF Reward Airdrops Forum 35

Divine32

Hiw I found bmf Introductions 0

BILL19

SUPPORT Why i can’t start conversation with anyone in BMF ? Support & Announcements 19

BILL19

❓ASK Payeer Payment Method In BMF How to Make Money Online? 6

DevNagy

Jobs 50 BMF Tokens – Install Android/IOS app Marketplace 6

mi306

Jobs 50 BMF + $1 and Passive Income Marketplace 0

Crypto Twist

Sell Selling $5 Skrill USD for 4950 BMF Points Buy / Sell / Trade 0

I created Telegram channel about cricket betting and need advice

I created Telegram channel about cricket betting. The main focus – cricket betting, and little be side posts about casino games and slot machines (betting and casino is cross-products).

I know that in DigitalPoint must be are lot of people, who understand cricket. And may be you give me some advice about cricket betting and igaming content on channel.

Here is the channel
https://t.me/mymzik

Thank you

How to join public group in Telegram group based on an invite code

Someone provide this: tg://join?invite=xxxxxxxx
(UIApplication sharedApplication) openURL:(NSURL URLWithString:@"tg://join?invite=xxxxxxxx") options:@{} completionHandler:^(BOOL success) {});

  1. if the xxxxxxx is private invite code, it worked.
  2. if the xxxxxxx is public invite code, the telegram show "This invite link has expired"
    What do I do?

Is there any way to unmute Telegram profile video sound?

I’ve searched a lot in YouTube, Quora,… but find nothing.

I believe that’s possible , although I have no great ideas .

I think using strange video formats can be helpful but not sure about that.

If you have same question please vote this up to reach more people and to find better answers.

After i changed my phone, i lost my admin privilege to my company Telegram group

I have been an admin of the company telegram group for about a year( The only admin). It is a public group. When i changed my phone with a similar phone number I suddenly don’t have access to my admin status.

Tried to contact Telegram but i haven’t received a response so far.

Any information will be helpful
Thanks