Example #1
0
def orchestra(members, bot, chat_id, skip=None):
    if skip is None:
        skip = []
    orchestra_ = Orchestra()

    for i in range(1, members):
        gender = random.choice([Gender.MALE, Gender.FEMALE])
        phone_number_ = phone_number() if 'phone_number' not in skip else None
        first_name_ = first_name(gender) if 'first_name' not in skip else None
        last_name_ = last_name() if 'last_name' not in skip else None
        nickname_ = nickname() if 'nickname' not in skip else None
        date_of_birth_ = date_of_birth(
        ) if 'date_of_birth' not in skip else None
        instruments_ = instrument() if 'instruments' not in skip else None
        address_ = address() if 'address' not in skip else None
        photo_file_id_ = (PhotoFileID.photo_file_id(bot, chat_id)
                          if 'photo_file_id' not in skip else None)
        orchestra_.register_member(
            Member(
                user_id=i,
                phone_number=phone_number_,
                first_name=first_name_,
                last_name=last_name_,
                nickname=nickname_,
                gender=gender,
                date_of_birth=date_of_birth_,
                instruments=instruments_,
                address=address_,
                photo_file_id=photo_file_id_,
            ))
    return orchestra_
def score_orchestra_anonymous(date):
    o = Orchestra()
    offset = dt.timedelta(weeks=100)
    m_1 = Member(1)
    m_1.user_score.add_to_score(8, 4, date)
    m_1.user_score.add_to_score(10, 10, date - offset)
    o.register_member(m_1)
    return o
Example #3
0
def rebuild_orchestra(update: Update, context: CallbackContext) -> None:
    """
    Builds a new orchestra by registering all members from the one in ``context.bot_data`` to a
    new instance of :class:`components.Orchestra`. Puts the new orchestra in ``bot_data``.
    Useful, if there have been changes to the attribute manager setup.

    Args:
        update: The update.
        context: The context as provided by the :class:`telegram.ext.Dispatcher`.
    """
    members = context.bot_data[ORCHESTRA_KEY].members.values()
    orchestra = Orchestra()

    for member in members:
        orchestra.register_member(member.copy())

    context.bot_data[ORCHESTRA_KEY] = orchestra
    update.message.reply_text('Orchester neu besetzt.')
def score_orchestra(date):
    o = Orchestra()
    offset = dt.timedelta(weeks=100)
    m_1 = Member(1, first_name='One')
    m_1.user_score.add_to_score(8, 4, date)
    m_1.user_score.add_to_score(10, 10, date - offset)
    m_2 = Member(2, first_name='Two')
    m_2.user_score.add_to_score(4, 2, date)
    m_2.user_score.add_to_score(10, 10, date - offset)
    m_3 = Member(3, first_name='Three')
    m_3.user_score.add_to_score(3, 1, date)
    m_3.user_score.add_to_score(10, 10, date - offset)
    m_4 = Member(4, first_name='Four')
    m_4.user_score.add_to_score(4, 1, date)
    m_4.user_score.add_to_score(10, 10, date - offset)
    for m in [m_1, m_2, m_3, m_4]:
        o.register_member(m)
    return o
Example #5
0
def build_questions_hints_keyboard(
    orchestra: Orchestra,
    question: bool = False,
    hint: bool = False,
    current_selection: Optional[Dict[str, bool]] = None,
    multiple_choice: bool = True,
    allowed_hints: List[str] = None,
    exclude_members: Iterable[Member] = None,
) -> InlineKeyboardMarkup:
    """
    Builds a :class:`telegram.InlineKeyboardMarkup` listing all questions that are up for
    selection for the given orchestra. The callback data for each button will equal its text.
    Also appends a button with the text :attr:`DONE` and data :attr:`DONE` at the very
    end of the keyboard.

    Args:
        orchestra: The orchestra to build the keyboard for.
        question: Optional. Set to :obj:`True`, if the keyboard is build for question selection.
        hint: Optional. Set to :obj:`True`, if the keyboard is build for hint selection.
        current_selection: Optional. If passed, gives the current selection and the keyboard will
            reflect that selection state. If not present, all options will be deselected.
            A corresponding dictionary is returned e.g. by :meth:`parse_questions_hints_keyboard`.
        multiple_choice: Optional. Whether the questions are supposed to be multiple choice or free
            text. Defaults to :obj:`True`.
        allowed_hints: Optional. Only relevant if :obj:`question` is :obj:`True`. If passed, in
            this case only question attributes which are questionable for at least one of the
            allowed hints will be listed in the keyboard.
        exclude_members: Optional. Members to exclude from serving as hint.

    Note:
        Exactly one on :attr:`hint` and :attr:`question` must be :obj:`True`.

    Returns:
        InlineKeyboardMarkup

    Raises:
        RuntimeError: If the orchestra currently has no questionable attributes.
    """
    if question == hint:
        raise ValueError('Exactly one on hint and question must be True.')

    current_selection = current_selection or dict()

    questionable = orchestra.questionable(multiple_choice=multiple_choice,
                                          exclude_members=exclude_members)
    if not questionable:
        raise RuntimeError(
            'Orchestra currently has no questionable attributes.')
    hints = [q[0].description for q in questionable]

    if question and allowed_hints:
        questions = [
            q[1].description for q in questionable if q[0] in allowed_hints
        ]
    else:
        questions = [q[1].description for q in questionable]

    buttons = []
    any_selected = False
    for row in QUESTION_HINT_KEYBOARD:
        button_row = []
        for option in row:
            if (hint and option not in hints) or (question
                                                  and option not in questions):
                continue

            text = (
                f'{Orchestra.TO_HR[option]} '
                f'{SELECTED if current_selection.get(option) else DESELECTED}')
            callback_data = f'{option} {SELECTED if current_selection.get(option) else DESELECTED}'
            button = InlineKeyboardButton(text=text,
                                          callback_data=callback_data)
            button_row.append(button)

            if current_selection.get(option):
                any_selected = True
        if row:
            buttons.append(button_row)
    if any_selected:
        buttons.append([
            InlineKeyboardButton(text=ALL, callback_data=ALL),
            InlineKeyboardButton(text=DONE, callback_data=DONE),
        ])
    else:
        buttons.append([InlineKeyboardButton(text=ALL, callback_data=DONE)])
    return InlineKeyboardMarkup(buttons)
Example #6
0
def empty_orchestra():
    return Orchestra()
Example #7
0
def setup(  # pylint: disable=R0913,R0914,R0915
    dispatcher: Dispatcher,
    admin: Union[int, str],
    oc_url: str,
    oc_username: str,
    oc_password: str,
    oc_path: str,
    ad_url: str,
    ad_url_active: str,
    ad_username: str,
    ad_password: str,
    yourls_url: str,
    yourls_signature: str,
) -> None:
    """
    * Adds handlers. Convenience method to avoid doing that all in the main script.
    * Sets the bot commands and makes sure ``dispatcher.bot_data`` is set up correctly.
    * Registers a :class:`telegram.ext.TypeHandler` that makes sure that conversations are not
      interrupted
    * Sets up statistics

    Args:
        dispatcher: The :class:`telegram.ext.Dispatcher`.
        admin: The admins chat id.
        oc_url: URL of the OwnCloud Instance.
        oc_username: Username for the OwnCloud Instance.
        oc_password: Password of the OwnCloud Instance.
        oc_path: Remote path on the OwnCloud Instance.
        ad_url: URL of the AkaDressen file.
        ad_url_active: URL of the AkaDressen file containing only the active members.
        ad_username: Username for the AkaDressen.
        ad_password: Password for the AkaDressen.
        yourls_url: URL of the YOURLS instance.
        yourls_signature: Signature for the YOURLS instance.
    """
    def check_conversation_status(update: Update,
                                  context: CallbackContext) -> None:
        """
        Checks if the user is currently in a conversation. If they are and the corresponding
        conversation does *not* handle the incoming update, the user will get a corresponding
        message and the update will be discarded.

        Args:
            update: The update.
            context: The context as provided by the :class:`telegram.ext.Dispatcher`.
        """
        if not update.effective_user:
            return

        conversation = context.user_data.get(CONVERSATION_KEY, None)
        if not conversation:
            return

        conversation_check = not bool(
            conversations[conversation].check_update(update))
        # Make sure that the callback queries for vcard requests are not processed
        if update.callback_query:
            contact_request_check = bool(
                re.match(inline.REQUEST_CONTACT_PATTERN,
                         update.callback_query.data))
            highscore_check = 'highscore' in update.callback_query.data
        else:
            contact_request_check = False
            highscore_check = False

        if conversation_check or contact_request_check or highscore_check:
            text = interrupt_replies[conversation]
            if update.callback_query:
                update.callback_query.answer(text=text, show_alert=True)
            elif update.effective_message:
                update.effective_message.reply_text(text)
            raise DispatcherHandlerStop()

    def clear_conversation_status(update: Update,
                                  context: CallbackContext) -> None:
        """
        Clears the conversation status of a user in case of an error. Just to be sure.

        Args:
            update: The update.
            context: The context as provided by the :class:`telegram.ext.Dispatcher`.
        """
        if update.effective_user:
            context.user_data.pop(CONVERSATION_KEY)

    # ------------------------------------------------------------------------------------------- #

    # Set up statistics
    set_dispatcher(dispatcher)
    # Count total number of updates
    register_stats(SimpleStats('stats', lambda u: bool(u.effective_user)),
                   admin_id=int(admin))
    # Count number of started games
    register_stats(
        SimpleStats(
            'game_stats',
            lambda u: bool(u.message) and Filters.text('/spiel_starten')(u)),
        admin_id=int(admin),
    )
    # Count number of requested contacts
    register_stats(
        admin_id=int(admin),
        stats=SimpleStats(
            'contact_stats',
            lambda u: bool(u.callback_query and 'contact_request' in u.
                           callback_query.data),
        ),
    )

    # Handlers

    # Prepare conversations
    game_handler = game.GAME_HANDLER
    editing_conversation = editing.build_editing_handler(int(admin))
    canceling_conversation = cancel_membership.CANCEL_MEMBERSHIP_HANDLER
    banning_conversation = ban.build_banning_handler(int(admin))
    conversations: Dict[str, ConversationHandler] = {
        game.CONVERSATION_VALUE: game_handler,
        editing.CONVERSATION_VALUE: editing_conversation,
        cancel_membership.CONVERSATION_VALUE: canceling_conversation,
        ban.CONVERSATION_VALUE: banning_conversation,
    }
    interrupt_replies: Dict[str, str] = {
        game.CONVERSATION_VALUE: game.CONVERSATION_INTERRUPT_TEXT,
        editing.CONVERSATION_VALUE: editing.CONVERSATION_INTERRUPT_TEXT,
        cancel_membership.CONVERSATION_VALUE:
        cancel_membership.CONVERSATION_INTERRUPT_TEXT,
        ban.CONVERSATION_VALUE: ban.CONVERSATION_INTERRUPT_TEXT,
    }

    # Registration status
    dispatcher.add_handler(TypeHandler(Update,
                                       registration.check_registration_status),
                           group=-2)

    # Conversation Interruption behaviour
    dispatcher.add_handler(TypeHandler(Update, check_conversation_status),
                           group=-1)

    # Game Conversation
    # Must be first so that the fallback can catch unrelated messages
    dispatcher.add_handler(game_handler)

    # Registration process
    # We need the filter here in order to not catch /start with deep linking parameter used for
    # inline help
    dispatcher.add_handler(
        CommandHandler('start',
                       registration.start,
                       filters=Filters.text('/start')))
    dispatcher.add_handler(
        CallbackQueryHandler(registration.request_registration,
                             pattern=REGISTRATION_PATTERN,
                             run_async=True))
    dispatcher.add_handler(registration.ACCEPT_REGISTRATION_HANDLER)
    dispatcher.add_handler(registration.DENY_REGISTRATION_HANDLER)

    # Edit user data
    dispatcher.add_handler(editing_conversation)

    # Cancel membership
    dispatcher.add_handler(canceling_conversation)

    # Banning members
    dispatcher.add_handler(banning_conversation)

    # Simple commands
    dispatcher.add_handler(
        CommandHandler(['hilfe', 'help'], commands.help_message))
    dispatcher.add_handler(CommandHandler('daten_anzeigen',
                                          commands.show_data))
    dispatcher.add_handler(
        CommandHandler('kontakt_abrufen', commands.start_inline))
    dispatcher.add_handler(
        CommandHandler('start',
                       commands.start_inline,
                       filters=Filters.text(f'/start {INLINE_HELP}')))

    # Inline Mode
    dispatcher.add_handler(InlineQueryHandler(inline.search_users))
    dispatcher.add_handler(inline.SEND_VCARD_HANDLER)

    # Highscores
    dispatcher.add_handler(
        CommandHandler('highscore', highscore.show_highscore))
    dispatcher.add_handler(highscore.HIGHSCORE_HANDLER)

    # Set commands
    dispatcher.bot.set_my_commands(BOT_COMMANDS)

    # Admin stuff
    dispatcher.add_handler(
        CommandHandler('rebuild',
                       bot.admin.rebuild_orchestra,
                       filters=Filters.user(int(admin))))

    # Error Handler
    dispatcher.add_error_handler(error.handle_error)
    dispatcher.add_error_handler(clear_conversation_status)

    # Schedule jobs
    check_user_status.schedule_daily_job(dispatcher)
    backup.PATH = oc_path
    backup.URL = oc_url
    backup.USERNAME = oc_username
    backup.PASSWORD = oc_password
    backup.schedule_daily_job(dispatcher)

    # Set up AkaDressen credentials
    Member.set_akadressen_credentials(ad_url, ad_url_active, ad_username,
                                      ad_password)

    # Set up bot_data
    bot_data = dispatcher.bot_data
    if not bot_data.get(ORCHESTRA_KEY):
        bot_data[ORCHESTRA_KEY] = Orchestra()
    else:
        # We rebuild the orchestra on start up to make sure code changes are applied
        old_orchestra = bot_data.pop(ORCHESTRA_KEY)
        new_orchestra = old_orchestra.copy()
        bot_data[ORCHESTRA_KEY] = new_orchestra
    if not bot_data.get(PENDING_REGISTRATIONS_KEY):
        bot_data[PENDING_REGISTRATIONS_KEY] = dict()
    if not bot_data.get(DENIED_USERS_KEY):
        bot_data[DENIED_USERS_KEY] = list()
    bot_data[ADMIN_KEY] = int(admin)

    yourls_client = YOURLSClient(yourls_url,
                                 signature=yourls_signature,
                                 nonce_life=True)
    bot_data[YOURLS_KEY] = yourls_client

    # Clear conversation key
    user_data = dispatcher.user_data
    for user_id in user_data:
        user_data[user_id].pop(CONVERSATION_KEY, None)
def orchestra():
    return Orchestra()