Exemple #1
0
def get_subject_timetable(subject, subtype, attendance, language_code):
    """get a timetable for specified subject"""

    # dict of {weekday: {'online': tt1, 'offline': tt2}}
    tt = SERVER.get_subject_timetable(subject, subtype, attendance)

    # no timetable for this subject
    if not tt:
        return get_text('no_subject_timetable_header_text', language_code=language_code).text()

    weekday_list = []
    for weekday, dct in tt.items():
        # get subheader
        weekday_name = get_text(f'{weekday}_timetable_text', language_code=language_code).text()

        # make timetable
        subject_timetable = __put_together(template=SUBJECT_TEMPLATE_WITH_PARITY, language_code=language_code, **dct)
        if not subject_timetable:
            continue
        day_template = get_text('subject_day_template_text', language_code=language_code).text({
            consts.TIMETABLE: subject_timetable,
            consts.WEEKDAY: weekday_name,
        })
        weekday_list.append(day_template)

    header = get_text('subject_timetable_header_text', language_code=language_code).text()

    return header + '\n'.join(weekday_list)
def report_sent(update: Update, context: CallbackContext):
    """take message to report and send it to all admins"""
    language_code = update.effective_user.language_code
    chat_id = update.effective_chat.id
    data = {
        'user':
        mention_html(update.effective_user.id,
                     update.effective_user.first_name),
    }
    for admin_id in database.get_all_admins_chat_ids():
        cf.send_message(
            context=context,
            chat_id=admin_id,
            text=get_text('report_template_text', language_code).text(data),
        )
        context.bot.forward_message(
            chat_id=admin_id,
            from_chat_id=chat_id,
            message_id=update.message.message_id,
        )
    cf.send_message(
        context=context,
        chat_id=chat_id,
        text=get_text('report_sent_text', language_code).text(),
    )
    return consts.MAIN_STATE
Exemple #3
0
def subject_keyboard(subject, page, attendance, language_code):
    """Make keyboard for subject pages: main and timetable page"""

    # current page, attendance and subject
    current_state = {
        consts.PAGE: page,
        consts.ATTENDANCE: attendance,
        consts.SUBJECT: subject
    }
    # invert states
    not_page = util.to_not_help_page(page)
    not_attendance = util.to_not_attendance(attendance)

    attendance_callback = buttons.SUBJECT % dict(current_state,
                                                 attendance=not_attendance)
    page_callback = buttons.SUBJECT % dict(current_state, page=not_page)
    keyboard = [[
        # buttons names and callback according to current states
        InlineKeyboardButton(
            text=get_text(f'subject_{not_page}_page_button',
                          language_code).text(),
            callback_data=page_callback,
        ),
        InlineKeyboardButton(
            text=get_text(f'subject_{not_attendance}_attendance_button',
                          language_code).text(),
            callback_data=attendance_callback,
        ),
    ]]
    return InlineKeyboardMarkup(inline_keyboard=keyboard)
def pretty_deadlines(deadlines, language_code):
    lst = []
    for subject, dl in deadlines:
        lst.append(
            get_text('dl_template_text', language_code).text({
                consts.DEADLINE:
                re.sub('\n', ' ', dl),
                consts.SUBJECT:
                subject,
            }))
    if not lst:
        return get_text('no_deadlines_text', language_code).text()
    return '\n\n'.join(lst)
Exemple #5
0
def timetable_keyboard(weekday, attendance, week_parity, language_code):
    """
    Make weekday timetable key board
    3 rows of buttons: 2 for weekdays and 1 to change attendance and week_parity
    """

    # for weekday buttons current_state is the same except weekday
    current_state = {
        consts.ATTENDANCE: attendance,
        consts.WEEK_PARITY: week_parity,
        consts.WEEKDAY: weekday,
    }
    # invert attendance and week_parity
    not_attendance = util.to_not_attendance(attendance)
    not_week_parity = util.to_not_week_parity(week_parity)

    # for attendance and week parity buttons callback changing respectively, weekday stays the same
    attendance_callback = buttons.TIMETABLE_BUTTON % dict(
        current_state, attendance=not_attendance)
    week_parity_callback = buttons.TIMETABLE_BUTTON % dict(
        current_state, week_parity=not_week_parity)
    keyboard = [
        [
            make_timetable_button(consts.MONDAY, current_state, language_code),
            make_timetable_button(consts.TUESDAY, current_state,
                                  language_code),
            make_timetable_button(consts.WEDNESDAY, current_state,
                                  language_code),
            make_timetable_button(consts.THURSDAY, current_state,
                                  language_code),
            make_timetable_button(consts.FRIDAY, current_state, language_code),
            make_timetable_button(consts.SATURDAY, current_state,
                                  language_code),
        ],
        [
            # buttons names and callback according to current states
            InlineKeyboardButton(
                text=get_text(f'timetable_{not_week_parity}_week_button',
                              language_code).text(),
                callback_data=week_parity_callback,
            ),
            InlineKeyboardButton(
                text=get_text(f'timetable_{not_attendance}_attendance_button',
                              language_code).text(),
                callback_data=attendance_callback,
            ),
        ]
    ]
    return InlineKeyboardMarkup(inline_keyboard=keyboard)
Exemple #6
0
def __user_time_input_chg(update: Update, context: CallbackContext, validation,
                          attr_name, error_state):
    """change mailing parameters with validation"""

    language_code = update.effective_user.language_code
    new_info = update.message.text
    user_id = update.effective_user.id
    chat_id = update.effective_chat.id

    # validate new_info. if False send error message and try again
    if validation(new_info):
        # update database
        database.set_user_attrs(user_id=user_id, attrs={attr_name: new_info})

        # reset mailing job
        jobs.reset_mailing_job(context, user_id, chat_id, language_code)

        # get and send mailing page
        text, reply_markup = __get_mailing_page(user_id, language_code)
        cf.send_message(
            context=context,
            chat_id=chat_id,
            text=text,
            reply_markup=reply_markup,
        )
        return consts.PARAMETERS_MAIN_STATE
    cf.send_message(
        context=context,
        chat_id=chat_id,
        text=get_text('format_error_parameters_text', language_code).text(),
    )
    return error_state
def unknown_callback(update: Update, context: CallbackContext):
    """handles unknown callbacks"""
    language_code = update.effective_user.language_code
    cf.send_message(context=context,
                    chat_id=update.effective_chat.id,
                    text=get_text('unknown_callback_text',
                                  language_code).text())
def get_deadlines(day: str, utcoffset, language_code):
    """request deadlines from server and returns them as readable text"""

    today = tm.get_today(utcoffset)

    # str_day: date in readable format; day: datetime.date for requested day
    if day == consts.TODAY:
        day = today
        str_day = day.strftime(consts.DEADLINE_DAY_FORMAT)
    else:
        str_day = day
        day = datetime.strptime(day, consts.DEADLINE_DAY_FORMAT)
        day = day.replace(year=today.year)

    deadlines, weekday = SERVER.get_deadlines(day.toordinal())

    template = get_text('deadline_text', language_code).text({
        consts.DATE:
        str_day,
        consts.WEEKDAY:
        weekday,
        consts.DEADLINE:
        pretty_deadlines(deadlines, language_code),
    })
    return template
def cancel_callback(update: Update, context: CallbackContext):
    """manage cancel button"""
    data, language_code = cf.manage_callback_query(update)
    parsed_data = data.split('_')
    if parsed_data[0] == consts.CANCEL:
        cf.edit_message(
            update=update,
            text=get_text('cancel_main_text',
                          update.effective_user.language_code).text(),
        )
        return consts.MAIN_STATE
    cf.send_message(
        context=context,
        chat_id=update.effective_chat.id,
        text=get_text('wrong_callback_text', language_code).text(),
    )
Exemple #10
0
def admin_ls(page_number, language_code):
    """list all users"""

    # get page of users (left/right bound)
    page_number = int(page_number)
    users = database.get_all_users()
    count = len(users)
    page_number = min((count - 1) // USERS_ON_PAGE, page_number)

    lb = page_number * USERS_ON_PAGE
    rb = lb + USERS_ON_PAGE

    text = get_text('ls_admin_text', language_code).text({
        consts.USERS: '\n'.join(map(lambda pair: mention_html(pair[0], pair[1]), users[lb:rb])),
        consts.LB: lb + 1,
        consts.RB: min(rb, count),
        consts.TOTAL: count
    })

    # detect page type
    if lb == 0 and rb >= count:
        page_type = consts.SINGLE_PAGE
    else:
        page_type = (
            consts.FIRST_PAGE if lb == 0
            else (consts.LAST_PAGE if rb >= count else consts.MIDDLE_PAGE)
        )
    reply_markup = keyboard.admin_ls(page_number, page_type, language_code)

    return text, consts.MAIN_STATE, reply_markup
Exemple #11
0
def admin_notify(update: Update, context: CallbackContext):
    """sends provided text to specified users"""
    language_code = update.effective_user.language_code

    user_nick = context.chat_data.pop('notify_username_admin', None)
    disable_notification = context.chat_data.pop('n_mode', None)

    notification_text = update.message.text

    # send notification
    if user_nick is not None:
        cf.send_notification(
            context=context, user_nick=user_nick,
            text=notification_text, disable_notification=disable_notification,
            language_code=language_code,
        )
    else:
        cf.send_notification_to_all(
            context=context, sender_id=update.effective_user.id,
            text=notification_text, disable_notification=disable_notification,
            language_code=language_code,
        )

    # notify sender that everything is ok
    cf.send_message(
        context=context,
        chat_id=update.effective_chat.id,
        text=get_text('notification_sent_notify_admin_text', language_code).text()
    )
    return consts.MAIN_STATE
Exemple #12
0
def timetable_args_error(context: CallbackContext, chat_id, error_type, language_code):
    """send argument error message"""
    cf.send_message(
        context=context,
        chat_id=chat_id,
        text=get_text('timetable_args_error_text', language_code).text({'error_type': error_type}),
    )
Exemple #13
0
 def error(update: Update, context: CallbackContext):
     language_code = update.effective_user.language_code
     cf.send_message(
         context=context,
         chat_id=update.effective_chat.id,
         text=get_text(f'{name}_parameters_error_text',
                       language_code).text(),
     )
Exemple #14
0
def admin_request_notify(context: CallbackContext, args, language_code):
    """send request for notification"""
    ret_lvl = consts.MAIN_STATE
    reply_markup = None
    if len(args) == 1:
        text = get_text('no_args_notify_admin_text', language_code).text()
    else:
        params = args[1].split('=')
        n_mode = False
        if len(args) >= 3:
            tr_flag, val = args[2].split('=')
            if tr_flag != '-s':
                text = get_text('invalid_flag_admin_text', language_code).text()
                return text, ret_lvl, reply_markup
            if val not in {'1', '0'}:
                text = get_text('invalid_flag_value_admin_text', language_code).text()
                return text, ret_lvl, reply_markup
            n_mode = val == '1'
        context.chat_data['n_mode'] = n_mode
        ret_lvl = consts.ADMIN_NOTIFY_STATE
        reply_markup = keyboard.cancel_operation(consts.ADMIN_NOTIFY_STATE)(language_code)
        if params[0] == '--all':
            text = get_text('all_users_notify_admin_text', language_code).text()
        elif params[0] == '--user':
            if len(params) == 2 and database.has_user(params[1]):
                context.chat_data['notify_username_admin'] = params[1]
                text = get_text('user_notify_admin_text', language_code).text()
            else:
                text = get_text('invalid_username_admin_text', language_code).text()
        else:
            ret_lvl, reply_markup = consts.MAIN_STATE, None
            text = get_text('invalid_flag_admin_text', language_code).text()
    return text, ret_lvl, reply_markup
def help_cmd(update: Update, context: CallbackContext):
    """help command callback"""
    language_code = update.effective_user.language_code
    cf.send_message(
        context=context,
        chat_id=update.effective_chat.id,
        text=get_text('help_main_text', language_code).text(),
        reply_markup=keyboard.help_keyboard(consts.MAIN_PAGE, language_code),
    )
Exemple #16
0
 def inner(language_code):
     keyboard = [
         [
             InlineKeyboardButton(
                 text=get_text(buttons.CANCEL, language_code).text(),
                 callback_data=buttons.CANCEL_CALLBACK % {'data': data},
             )
         ],
     ]
     return InlineKeyboardMarkup(inline_keyboard=keyboard)
Exemple #17
0
def admin(update: Update, context: CallbackContext):
    """
    admin's control panel
    current functions:
    '/admin [-ls]' - list of all users
    '/admin [-n <--user=user_nick | --all> [--s=<0 | 1>] ]' - send a notification to the specified user or to all users.
        Flag --s (silence) is set to 0 by default
    '/admin [-m |-um <--user=user_nick | --all>]' - mute/unmute reports from user
    """
    language_code = update.effective_user.language_code
    args = context.args
    ret_lvl = consts.MAIN_STATE
    reply_markup = None

    # unauthorized user tries to access admin panel
    if not database.get_user_attr('admin', user_id=update.effective_user.id):
        text = get_text('unauthorized_user_admin_text', language_code).text()

    # empty args
    elif len(args) == 0:
        text = get_text('no_args_admin_text', language_code).text()

    # notifications
    elif args[0] == '-n':
        text, ret_lvl, reply_markup = admin_request_notify(context, args, language_code)

    # list of the users
    elif args[0] == '-ls':
        text, ret_lvl, reply_markup = admin_ls(0, language_code)

    # mute/unmute users
    elif args[0] == '-m' or args[0] == '-um':
        text, ret_lvl = admin_mute(args, language_code)
    else:
        text = get_text('invalid_flag_admin_text', language_code).text()

    cf.send_message(
        context=context,
        chat_id=update.effective_chat.id,
        text=text,
        reply_markup=reply_markup,
    )
    return ret_lvl
def report(update: Update, context: CallbackContext):
    """will wait for message to report if unmuted"""
    language_code = update.effective_user.language_code
    if database.get_user_attr(consts.MUTED, update.effective_user.id):
        text = get_text('cannot_send_report_text', language_code).text()
        ret_lvl = consts.MAIN_STATE
        reply_markup = None
    else:
        text = get_text('report_text', language_code).text()
        ret_lvl = consts.REPORT_MESSAGE_STATE
        reply_markup = keyboard.cancel_operation(
            consts.REPORT_STATE)(language_code)
    cf.send_message(
        context=context,
        chat_id=update.effective_chat.id,
        text=text,
        reply_markup=reply_markup,
    )
    return ret_lvl
Exemple #19
0
def get_weekday_timetable(weekday: str, subject_names, attendance, week_parity, language_code, footer=None) -> str:
    """
    makes timetable for specified day
    (subject_names: all user's subjects or subtypes)
    """

    # checks if it is sunday
    if weekday == consts.SUNDAY:
        return get_text('today_sunday_text', language_code=language_code).text()

    # get text templates
    template = get_text('timetable_text', language_code)

    template.add_global_vars({
        consts.WEEKDAY: get_text(f'{weekday}_timetable_text', language_code).text(),
        consts.WEEK_PARITY: get_text(f'{week_parity}_week_timetable_text', language_code=language_code).text(),
        consts.FOOTER: footer or '',
    })

    # get timetables as dict
    subject_tt: dict = SERVER.get_weekday_timetable(
        weekday=weekday,
        valid_subject_names=subject_names,
        attendance=attendance,
        week_parity=week_parity
    )

    online_tt, offline_tt = subject_tt['online'], subject_tt['offline']

    # no subjects that day
    if not online_tt and not offline_tt:
        happy_text = get_text('happy_timetable_text', language_code=language_code).text()
        return template.text({consts.TIMETABLE: happy_text})

    # make full timetable
    weekday_timetable = __put_together(
        online=online_tt,
        offline=offline_tt,
        template=SUBJECT_TEMPLATE,
        language_code=language_code,
    )
    return template.text({consts.TIMETABLE: weekday_timetable})
Exemple #20
0
def __get_mailing_page(user_id, language_code):
    """get mailing page attrs"""
    attrs = database.get_user_parameters(user_id)
    text = get_text('mailing_parameters_text', language_code).text(
        cf.pretty_user_parameters(attrs, language_code), )
    reply_markup = keyboard.mailing_keyboard(
        mailing_status=attrs[consts.MAILING_STATUS],
        notification_status=attrs[consts.NOTIFICATION_STATUS],
        language_code=language_code,
    )
    return text, reply_markup
Exemple #21
0
def __return_callback(update: Update, language_code):
    """show main parameters page"""
    user_id = update.effective_user.id
    cf.edit_message(
        update=update,
        text=get_text('main_parameters_text', language_code).text(
            cf.pretty_user_parameters(database.get_user_parameters(user_id),
                                      language_code)),
        reply_markup=keyboard.parameters_keyboard(language_code),
    )
    return consts.PARAMETERS_MAIN_STATE
def help_callback(update: Update, data: list, language_code):
    """change help page"""
    if data[1] in {consts.MAIN_PAGE, consts.ADDITIONAL_PAGE}:
        text = get_text(f'help_{data[1]}_text', language_code).text()
    else:
        raise ValueError(f'Invalid help callback: {data[0]}')
    cf.edit_message(
        update=update,
        text=text,
        reply_markup=keyboard.help_keyboard(data[1], language_code),
    )
def get_subject_info(subject,
                     user_id,
                     page,
                     language_code,
                     request: dict = None):
    """
    Returns subject info and attendance as tuple
     - subject: name of the subject without subtype
     - user_id: id to get user parameters from database
     - page: main or timetable
     - request: dict with 2 values - attendance and subtype.
       By default user parameters are taken from database, but if the request has values, bot uses them
    """
    if request is None:
        request = {}
    # default parameters
    subtype, attendance = database.get_user_attrs([subject, consts.ATTENDANCE],
                                                  user_id=user_id).values()

    # substitutes with request parameters if exists
    attendance = request.get(consts.ATTENDANCE) or attendance
    subtype = request.get(consts.SUBTYPE) or subtype

    # select page to return
    if page == consts.TIMETABLE_PAGE:
        timetable = tt.get_subject_timetable(subject, subtype, attendance,
                                             language_code)
        return get_text('subject_timetable_text', language_code).text({
            consts.TIMETABLE:
            timetable,
        }), attendance
    elif page == consts.MAIN_PAGE:
        return get_text(f'{subject}_subject_text', language_code).text({
            consts.SUBTYPE:
            subtype,
            consts.ATTENDANCE:
            (attendance
             if attendance != consts.ATTENDANCE_BOTH else consts.ALL),
        }), attendance
    else:
        raise ValueError(f'Invalid subject page type: {page}')
Exemple #24
0
def admin_mute(args, language_code):
    """mute/unmute users"""
    if len(args) < 2:
        text = get_text('empty_user_id_admin_text', language_code).text()
    else:
        if args[0] != '-m' and args[0] != '-um':
            raise ValueError(f'Invalid flag: {args[0]}')
        flag = consts.MUTE if args[0] == '-m' else consts.UNMUTE
        target = args[1].split('=')
        if target[0] == '--all':
            text = get_text(f'{flag}_all_users_admin_text', language_code).text()
            database.set_attr_to_all(consts.MUTED, flag == consts.MUTE)
        elif target[0] == '--user':
            if len(target) == 2 and database.has_user(target[1]):
                database.set_user_attrs(user_nick=target, attrs={consts.MUTED: flag == consts.MUTE})
                text = get_text(f'{flag}_user_admin_text', language_code).text()
            else:
                text = get_text('invalid_username_admin_text', language_code).text()
        else:
            text = get_text('invalid_flag_admin_text', language_code).text()
    return text, consts.MAIN_STATE
Exemple #25
0
def make_timetable_button(weekday, current_state, language_code):
    """
    Make timetable button according to current page state
    current_state: dict with 3 parameters: attendance, week_parity, weekday
    """
    return InlineKeyboardButton(
        text=get_text(buttons.WEEKDAY_BUTTON % {
            consts.WEEKDAY: weekday
        }, language_code).text(),
        callback_data=buttons.TIMETABLE_BUTTON %
        dict(current_state, weekday=weekday),
    )
def error_callback(update: Update, context: CallbackContext):
    """
    Error callback function
    notifies user that error occurred, sends feedback to all admins
    """
    language_code = update.effective_user.language_code
    # notify user
    cf.send_message(context=context,
                    chat_id=update.effective_chat.id,
                    text=get_text('error_handler_user_text',
                                  language_code).text())

    # make logs
    logger.error(
        f'{str(context.error)}\n\n{"".join(traceback.format_tb(sys.exc_info()[2]))}'
    )

    # collect data about error
    data = {'error': str(context.error)}
    if update.effective_user:
        data['user'] = mention_html(update.effective_user.id,
                                    update.effective_user.first_name)
    else:
        data['user'] = '******'

    if update.callback_query:
        data[
            'info'] = f'type = query\ncontent = "{update.callback_query.data}"'
    else:
        data['info'] = f'type = message\ncontent = "{update.message.text}"'

    text = get_text('error_handler_dev_text', language_code)

    # send collected data to all admins
    for dev_id in database.get_all_admins_chat_ids():
        cf.send_message(
            context=context,
            chat_id=dev_id,
            text=text.text(data),
        )
Exemple #27
0
def parameters(update: Update, context: CallbackContext):
    """start parameters conversation"""
    language_code = update.effective_user.language_code
    user_id = update.effective_user.id
    cf.send_message(
        context=context,
        chat_id=update.effective_chat.id,
        text=get_text('main_parameters_text', language_code).text(
            cf.pretty_user_parameters(database.get_user_parameters(user_id),
                                      language_code), ),
        reply_markup=keyboard.parameters_keyboard(language_code),
    )
    return consts.PARAMETERS_MAIN_STATE
    def inner(update: Update, context: CallbackContext):
        language_code = update.effective_user.language_code
        chat_id = update.effective_chat.id

        text = get_text(f'{name}_text', language_code).text()

        send_message(
            context=context,
            chat_id=chat_id,
            text=text,
            reply_markup=reply_markup,
        )
        return ret_state
def pretty_user_parameters(user_parameters: dict, language_code):
    """make parameters readable for users"""
    retval = {}
    for attr_name, attr_value in user_parameters.items():
        # attrs without modifications
        if (attr_name == consts.USERNAME
                and attr_value) or (attr_name == consts.MAILING_TIME):
            retval[attr_name] = attr_value
            continue
        # add sign to utcoffset
        elif attr_name == consts.UTCOFFSET:
            retval[attr_name] = (str(attr_value)
                                 if attr_value < 0 else f'+{attr_value}')
            continue
        # get readable values
        text = get_text(f'{attr_name}_{attr_value}_user_data_text',
                        language_code).text()
        # attach group's number to general name
        if attr_name == consts.ENG and attr_value != consts.ALL:
            text = get_text('eng_std_user_data_text',
                            language_code).text({consts.ENG: text})
        retval[attr_name] = text
    return retval
def doc(update: Update, context: CallbackContext):
    """
    show documentation
    if argument specified, shows docs for command
    shows special docs for admins
    """
    language_code = update.effective_user.language_code
    args = context.args
    if_admin = database.get_user_attr('admin',
                                      user_id=update.effective_user.id)
    if len(args) > 2:
        text = get_text('quantity_error_doc_text', language_code).text()
    else:
        if len(args) == 0:
            text = get_text('doc_text', language_code).text({
                'command': consts.ALL,
                'admin': if_admin
            })
        else:
            if args[0] not in consts.DOC_COMMANDS:
                text = get_text('wrong_command_error_doc_text',
                                language_code).text()
            else:
                text = get_text('doc_text', language_code).text({
                    'command':
                    args[0],
                    'admin':
                    if_admin
                })
                if not if_admin and args[0] == 'admin':
                    text += get_text('doc_unavailable_text',
                                     language_code).text()
    cf.send_message(
        context=context,
        chat_id=update.effective_chat.id,
        text=text,
    )