Exemple #1
0
async def _sub(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not context.args:
        await update.effective_message.reply_text("Укажи cups л🔥гин")
        return
    if len(context.args) > 1:
        await update.effective_message.reply_text(
            "Подписаться можно только на 1 cups л🔥гин")
        return

    login = context.chat_data.pop('battle_login', None)
    if login:
        battle_subs = context.bot_data.get('battle_subs', dict())
        context.bot_data['battle_subs'] = battle_subs
        chats = battle_subs.get(login, set())
        battle_subs[login] = chats
        chats.discard(update.effective_chat.id)

    login = context.args[0]
    context.chat_data['battle_login'] = login
    battle_subs = context.bot_data.get('battle_subs', dict())
    context.bot_data['battle_subs'] = battle_subs
    chats = battle_subs.get('battle_subs', set())
    battle_subs[login] = chats
    chats.add(update.effective_chat.id)

    await update.effective_message.reply_markdown(
        f"Подписка на системные игры `{login}` установлена")
async def select_feature(update: Update,
                         context: ContextTypes.DEFAULT_TYPE) -> str:
    """Select a feature to update for the person."""
    buttons = [[
        InlineKeyboardButton(text="Name", callback_data=str(NAME)),
        InlineKeyboardButton(text="Age", callback_data=str(AGE)),
        InlineKeyboardButton(text="Done", callback_data=str(END)),
    ]]
    keyboard = InlineKeyboardMarkup(buttons)

    # If we collect features for a new person, clear the cache and save the gender
    if not context.user_data.get(START_OVER):
        context.user_data[FEATURES] = {GENDER: update.callback_query.data}
        text = "Please select a feature to update."

        await update.callback_query.answer()
        await update.callback_query.edit_message_text(text=text,
                                                      reply_markup=keyboard)
    # But after we do that, we need to send a new message
    else:
        text = "Got it! Please select a feature to update."
        await update.message.reply_text(text=text, reply_markup=keyboard)

    context.user_data[START_OVER] = False
    return SELECTING_FEATURE
async def select_gender(update: Update,
                        context: ContextTypes.DEFAULT_TYPE) -> str:
    """Choose to add mother or father."""
    level = update.callback_query.data
    context.user_data[CURRENT_LEVEL] = level

    text = "Please choose, whom to add."

    male, female = _name_switcher(level)

    buttons = [
        [
            InlineKeyboardButton(text=f"Add {male}", callback_data=str(MALE)),
            InlineKeyboardButton(text=f"Add {female}",
                                 callback_data=str(FEMALE)),
        ],
        [
            InlineKeyboardButton(text="Show data", callback_data=str(SHOWING)),
            InlineKeyboardButton(text="Back", callback_data=str(END)),
        ],
    ]
    keyboard = InlineKeyboardMarkup(buttons)

    await update.callback_query.answer()
    await update.callback_query.edit_message_text(text=text,
                                                  reply_markup=keyboard)

    return SELECTING_GENDER
async def end_second_level(update: Update,
                           context: ContextTypes.DEFAULT_TYPE) -> int:
    """Return to top level conversation."""
    context.user_data[START_OVER] = True
    await start(update, context)

    return END
Exemple #5
0
async def _task_button(update: Update,
                       context: ContextTypes.DEFAULT_TYPE) -> None:
    """Parses the CallbackQuery and updates the message text."""
    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
    await query.answer()
    task_id = query.data.split()[1]
    task = allcups.task(task_id)
    contest = allcups.contest(task['contest']['slug'])
    context.chat_data['contest_slug'] = task['contest']['slug']
    context.chat_data['task_id'] = task_id

    info_txt = msg_formatter.format_chat_info(contest, task)
    await query.edit_message_text(info_txt, parse_mode='markdown')
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> str:
    """Select an action: Adding parent/child or show data."""
    text = (
        "You may choose to add a family member, yourself, show the gathered data, or end the "
        "conversation. To abort, simply type /stop.")

    buttons = [
        [
            InlineKeyboardButton(text="Add family member",
                                 callback_data=str(ADDING_MEMBER)),
            InlineKeyboardButton(text="Add yourself",
                                 callback_data=str(ADDING_SELF)),
        ],
        [
            InlineKeyboardButton(text="Show data", callback_data=str(SHOWING)),
            InlineKeyboardButton(text="Done", callback_data=str(END)),
        ],
    ]
    keyboard = InlineKeyboardMarkup(buttons)

    # If we're starting over we don't need to send a new message
    if context.user_data.get(START_OVER):
        await update.callback_query.answer()
        await update.callback_query.edit_message_text(text=text,
                                                      reply_markup=keyboard)
    else:
        await update.message.reply_text(
            "Hi, I'm Family Bot and I'm here to help you gather information about your family."
        )
        await update.message.reply_text(text=text, reply_markup=keyboard)

    context.user_data[START_OVER] = False
    return SELECTING_ACTION
async def list_button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Parses the CallbackQuery and updates the message text."""
    query = update.callback_query
    await query.answer()
    # Get the data from the callback_data.
    # If you're using a type checker like MyPy, you'll have to use typing.cast
    # to make the checker get the expected type of the callback_data
    number, number_list = cast(Tuple[int, List[int]], query.data)
    # append the number to the list
    number_list.append(number)

    await query.edit_message_text(
        text=f"So far you've selected {number_list}. Choose the next item:",
        reply_markup=build_keyboard(number_list),
    )

    # we can delete the data stored for the query, because we've replaced the buttons
    context.drop_callback_data(query)
async def regular_choice(update: Update,
                         context: ContextTypes.DEFAULT_TYPE) -> int:
    """Ask the user for info about the selected predefined choice."""
    text = update.message.text
    context.user_data["choice"] = text
    await update.message.reply_text(
        f"Your {text.lower()}? Yes, I would love to hear about that!")

    return TYPING_REPLY
Exemple #9
0
async def _unsub(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    login = context.chat_data.pop('battle_login', None)
    if login:
        battle_subs = context.bot_data.get('battle_subs', dict())
        context.bot_data['battle_subs'] = battle_subs
        chats = battle_subs.get(login, set())
        battle_subs[login] = chats
        chats.discard(update.effective_chat.id)
    await update.effective_message.reply_text(
        "Подписка на системные игры отключена")
async def ask_for_input(update: Update,
                        context: ContextTypes.DEFAULT_TYPE) -> str:
    """Prompt user to input data for selected feature."""
    context.user_data[CURRENT_FEATURE] = update.callback_query.data
    text = "Okay, tell me."

    await update.callback_query.answer()
    await update.callback_query.edit_message_text(text=text)

    return TYPING
async def adding_self(update: Update,
                      context: ContextTypes.DEFAULT_TYPE) -> str:
    """Add information about yourself."""
    context.user_data[CURRENT_LEVEL] = SELF
    text = "Okay, please tell me about yourself."
    button = InlineKeyboardButton(text="Add info", callback_data=str(MALE))
    keyboard = InlineKeyboardMarkup.from_button(button)

    await update.callback_query.answer()
    await update.callback_query.edit_message_text(text=text,
                                                  reply_markup=keyboard)

    return DESCRIBING_SELF
Exemple #12
0
async def _chat_remove(update: Update,
                       context: ContextTypes.DEFAULT_TYPE) -> None:
    usernames = set(context.args)
    if not usernames:
        await update.effective_message.reply_text("Ст🔥ит  указ🔥ть  ник")
        return

    cups_logins = context.chat_data.get('cups_logins', set())
    cups_logins.difference_update(usernames)
    context.chat_data['cups_logins'] = cups_logins

    msg_txt = msg_formatter.chat_logins(cups_logins)
    await update.effective_message.reply_markdown(msg_txt)
async def regular_choice(update: Update,
                         context: ContextTypes.DEFAULT_TYPE) -> int:
    """Ask the user for info about the selected predefined choice."""
    text = update.message.text.lower()
    context.user_data["choice"] = text
    if context.user_data.get(text):
        reply_text = (
            f"Your {text}? I already know the following about that: {context.user_data[text]}"
        )
    else:
        reply_text = f"Your {text}? Yes, I would love to hear about that!"
    await update.message.reply_text(reply_text)

    return TYPING_REPLY
async def received_information(update: Update,
                               context: ContextTypes.DEFAULT_TYPE) -> int:
    """Store info provided by user and ask for the next category."""
    text = update.message.text
    category = context.user_data["choice"]
    context.user_data[category] = text.lower()
    del context.user_data["choice"]

    await update.message.reply_text(
        "Neat! Just so you know, this is what you already told me:"
        f"{facts_to_str(context.user_data)}"
        "You can tell me more, or change your opinion on something.",
        reply_markup=markup,
    )

    return CHOOSING
Exemple #15
0
async def _set_contest(update: Update,
                       context: ContextTypes.DEFAULT_TYPE) -> None:
    await context.bot.send_chat_action(chat_id=update.effective_chat.id,
                                       action=ChatAction.TYPING)

    if len(context.args) != 1:
        await update.effective_message.reply_text("🔥❓")
        return
    slug = context.args[0]

    contest = allcups.contest(slug)
    if contest is None:
        logger.warning(f"There is no contest with slug `{slug}`")
        await update.effective_message.reply_markdown(
            f"There is no c🔥ntest with slug `{slug}`")
        return

    context.chat_data['contest_slug'] = slug
    context.chat_data.pop('task_id', None)
    info_txt = msg_formatter.format_chat_info(contest, None)
    await update.effective_message.reply_markdown(info_txt)
Exemple #16
0
async def games_notifications(context: ContextTypes.DEFAULT_TYPE) -> None:
    battles = allcups.battles()
    now = datetime.now(timezone.utc)

    # sent_battle_ids = context.bot_data.get('sent_battle_ids', dict())
    # context.bot_data['sent_battle_ids'] = sent_battle_ids

    last_battle_ts = context.bot_data.get('last_battle_ts', dict())
    context.bot_data['last_battle_ts'] = last_battle_ts

    # battle_updates = context.bot_data.get('battle_updates', dict())
    # context.bot_data['battle_updates'] = battle_updates

    # battle_last_id = context.bot_data.get('battle_last_id', dict())
    # context.bot_data['battle_last_id'] = battle_last_id
    context.bot_data.pop('battle_last_id', None)
    context.bot_data.pop('sent_battle_ids', None)

    for b in battles:
        end_date = datetime.fromisoformat(b['finish_date'])
        if now > end_date and b['slug'] != 'coderoyale':
            continue
        for r in b['rounds']:
            start_date = datetime.fromisoformat(r['start_date'])
            end_date = datetime.fromisoformat(r['finish_date'])
            if now < start_date or now > end_date:
                continue
            for t in r['tasks']:
                name = f"{b['name']}: {r['name']}: {t['name']}"
                last_ts = last_battle_ts.get(t['id'], None)
                last = datetime.fromtimestamp(
                    last_ts, timezone.utc) if last_ts else None
                # sent_ids = sent_battle_ids.get(t['id'], set())
                # battle_update = battle_updates.get(t['id'], None)
                # task_battles = allcups.battles_bot(t['id'], since=last)
                task_battles = allcups.battles_bot(t['id'])
                if not task_battles:
                    continue

                ts = datetime.fromisoformat(
                    task_battles[0]['updated_at']).timestamp()
                last_battle_ts[t['id']] = ts

                if last_ts is None:
                    continue

                scores = allcups.task_leaderboard(t['id'])
                lb_scores = {}
                for s in scores:
                    lb_scores[s['user']['login']] = {
                        'rank': s['rank'],
                        'score': s['score'],
                    }

                for battle in task_battles[::-1]:
                    if datetime.fromisoformat(battle['updated_at']) <= last:
                        continue
                    # if battle['id'] in sent_ids:
                    #     continue
                    if battle['status'] != 'DONE':
                        continue
                    await _process_battle_results(battle, name, lb_scores,
                                                  context)
Exemple #17
0
async def _plot_logins(cups_logins,
                       update: Update,
                       context: ContextTypes.DEFAULT_TYPE,
                       relative_login=None,
                       relative_rank=None,
                       display_rank=False,
                       plot_type='step') -> None:
    task = allcups.task(context.chat_data['task_id'])
    display_field = 'rank' if display_rank else 'score'

    # context.bot_data.pop('history', None)
    history = context.bot_data.get('history', {})
    context.bot_data['history'] = history
    task_history = history.get(context.chat_data['task_id'], [])
    history[context.chat_data['task_id']] = task_history

    ts = datetime.fromisoformat(task['start_date'])
    finish_date = datetime.fromisoformat(task['finish_date'])
    time_step = timedelta(minutes=15)
    now = datetime.now(timezone.utc)
    end = min(finish_date, now)
    if task_history:
        ts = datetime.fromtimestamp(task_history[-1]['ts'], timezone.utc)

    # TODO: Remove in future.
    while ts > finish_date:
        task_history.pop()
        ts = datetime.fromtimestamp(task_history[-1]['ts'], timezone.utc)

    time_limit = timedelta(days=2)
    # ts = max(now - time_limit, ts)
    ts += time_step
    while ts <= end:
        scores = allcups.task_leaderboard(context.chat_data['task_id'], ts)
        lb = [{
            'rank': s['rank'],
            'login': s['user']['login'],
            'score': s['score']
        } for s in scores]
        task_history.append({'ts': ts.timestamp(), 'leaderboard': lb})
        ts += time_step

    plt.clf()
    fig, ax = plt.subplots(1, 1, figsize=(15, 7))

    # ax.tick_params(axis='x', rotation=0, labelsize=12)
    ax.tick_params(axis='y', rotation=0, labelsize=12, labelcolor='tab:red')
    myFmt = mdates.DateFormatter('%b %d %H:%M')
    ax.xaxis.set_major_formatter(myFmt)
    ax.grid(alpha=.9)

    task_history = [
        h for h in task_history
        if now - datetime.fromtimestamp(h['ts'], timezone.utc) < time_limit
    ]
    dates = [
        datetime.fromtimestamp(h['ts'], timezone.utc) for h in task_history
    ]

    plot_data = {}
    ls = set(cups_logins)
    if relative_login is not None:
        ls.add(relative_login)

    for login in ls:
        pd = []
        plot_data[login] = pd
        for h in task_history:
            point = None
            for s in h['leaderboard']:
                if s['login'].lower() == login.lower():
                    point = s[display_field]
                    break
            pd.append(point)

    if relative_login is not None and relative_login in plot_data:
        relative_data = list(plot_data[relative_login])
        for login in cups_logins:
            login_data = plot_data[login]
            for i, rel_d in enumerate(relative_data):
                if login_data[i] is None or rel_d is None:
                    login_data[i] = None
                else:
                    login_data[i] -= rel_d

        plt.axhline(y=0.0,
                    color='darkviolet',
                    linestyle='--',
                    label=relative_login)

    if relative_rank is not None:
        relative_data = [
            h['leaderboard'][relative_rank - 1][display_field]
            if relative_rank <= len(h['leaderboard']) else None
            for h in task_history
        ]
        for login in cups_logins:
            login_data = plot_data[login]
            for i, rel_d in enumerate(relative_data):
                if login_data[i] is None or rel_d is None:
                    login_data[i] = None
                else:
                    login_data[i] -= rel_d

        plt.axhline(y=0.0,
                    color='darkviolet',
                    linestyle='--',
                    label=f'rank={relative_rank}')

    for login in cups_logins:
        if relative_login is not None and login.lower(
        ) == relative_login.lower():
            continue
        if plot_type == 'lines':
            plt.plot(dates, plot_data[login], label=login)
        else:
            plt.step(dates, plot_data[login], where='mid', label=login)

    plt.grid(color='0.95')
    plt.legend(fontsize=16, bbox_to_anchor=(1, 1), loc="upper left")

    if display_field == 'rank':
        plt.gca().invert_yaxis()

    plot_file = BytesIO()
    fig.tight_layout()
    fig.savefig(plot_file, format='png')
    plt.clf()
    plt.close(fig)
    plot_file.seek(0)

    await update.effective_message.reply_photo(plot_file, caption="🔥")
Exemple #18
0
async def _process_battle_results(battle, name, lb_scores,
                                  context: ContextTypes.DEFAULT_TYPE) -> None:
    battle_subs = context.bot_data.get('battle_subs', dict())
    logins = [r['user__login'] for r in battle['user_results']]
    for login in logins:
        if login not in battle_subs:
            continue

        scores = []
        sorted_results = list(
            sorted(battle['user_results'],
                   key=lambda x: x['score'],
                   reverse=True))
        win = False
        solution = -1
        for i, r in enumerate(sorted_results):
            if login == r['user__login']:
                win = (i + 1) <= (len(sorted_results) // 2)
                solution = r['solution_id']

            score = {
                'rank': i + 1,
                'login': r['user__login'],
                'sub_flag': login == r['user__login'],
                'score': r['score'],
                'id': r['solution_id'],
                'language': r['language__name'],
            }
            if r['user__login'] in lb_scores:
                score['lb_score'] = lb_scores[r['user__login']]['score']
                score['lb_rank'] = lb_scores[r['user__login']]['rank']
            else:
                score['lb_score'] = "-"
                score['lb_rank'] = "-"
            scores.append(score)

        msg_txt = msg_formatter.format_game(battle, name, scores,
                                            lb_scores[login], win, solution)

        replay_url = "https://cups.online" + battle['visualizer_url'] + "?"
        for br in battle['user_results']:
            replay_url += f"&player-names=" + urllib.parse.quote(
                br['user__login'])
            # replay_url += f"&player-names=" + urllib.parse.quote(names.get_name())
            replay_url += f"&client-ids=" + urllib.parse.quote(
                str(br['solution__external_id']))
        replay_url += f"&replay=" + urllib.parse.quote(
            battle['battle_result_file'])
        reply_markup = InlineKeyboardMarkup(
            [[InlineKeyboardButton(text='Watch Replay', url=replay_url)]])

        blocked = []
        for chat_id in battle_subs[login]:
            try:
                await context.bot.send_message(chat_id=chat_id,
                                               text=msg_txt,
                                               parse_mode='markdown',
                                               reply_markup=reply_markup,
                                               read_timeout=5,
                                               write_timeout=5,
                                               connect_timeout=5,
                                               pool_timeout=5)
            except Forbidden as e:
                logger.warning(
                    f"Bot blocked by '{chat_id}' - removing subscription.")
                blocked.append(chat_id)
                context.application.chat_data[chat_id].pop(
                    'battle_login', None)
            except Exception as e:
                logger.warning(f"Error sending game subscription: {e}")
            time.sleep(1)
        if blocked:
            battle_subs[login] = [
                s for s in battle_subs[login] if s not in blocked
            ]
    context.bot_data['battle_subs'] = battle_subs