Немного вводной

Я имею самый обычный рабочий график: 5/2, 8ч/день. В настоящий момент удаленно учусь в аспирантуре (коронавирус, все дела) и единственный день, когда я могу вдоволь почувствовать себя человеком-соседом и поделать что-то по дому, – это суббота. Как вы понимаете, здесь что-то пошло не так и вместо обещанных будничных пар, которые должны были проходить по вечерам после работы, нам утрамбовали всю субботу. Но дела ведь себя не переделают, поэтому решено было написать на python простого бота-кликера, который мог бы:

  • Заходить на пару по скормленной извне ссылки;

  • Стартовать запись экрана со звуком;

  • Ожидать окончания пары;

  • Выключать запись и выходить с пары.

Таким образом я и на паре присутствую, и домашними делами занимаюсь. А еще могу просмотреть лекции с удвоенным ускорением и в любое удобное для меня время. Придумано – сделано. Может быть мой опыт поможет кому-то решить аналогичную «проблему».

В ВУЗе мы используем Microsoft Teams и для того, чтобы попасть на лекцию требуется перейти по ссылке, которую за несколько часов до старта этой самой лекции, высылает староста. На мой взгляд, самым простым решением является бот-кликер, наблюдающий за экраном и тыкающий по кнопкам. А также было бы неплохо стартовать все это удаленно и желательно с телефона.

Кликер

Начал я с питонячей библиотеки pyautogui. Если кратенько, она умеет перехватывать управление клавомышью, тыкать кнопки, вводить тексты, делать снимки экрана, искать окна приложений и взаимодействовать с ними и многое-многое другое. Для своего кликера я выстроил следующую логику: я заранее сохраняю картинку нужной кнопки, pyautogui ожидает ее появления на экране, когда он находит кнопку, то тыкает по ней.

Функция ожидания:

def find_element(element):
    """Ожидание появления элемента на экране"""
    sleep(1)
    r = None
    count = 100
    while r is None:
        count -= 1
        r = pyautogui.locateOnScreen(dir_true_files + element, confidence=0.7)
        print(count)
        if count == 0:
            print(element + ' не найден!')
            break
        else:
            if r != None:
                print(element + ' нашОлся!')
                return r
            else:
                continue

Здесь pyautogui ищет на экране в пределах счетчика «100» совпадений в 70% с заранее сохраненной картинкой.

Далее функции для взаимодействия с приложением и создание скриншота экрана:

def press_key(key):
    """Нажатие одной клавиши"""
    sleep(1)
    pyautogui.press(key)


def write_text(text):
    """Ввод текста"""
    sleep(1)
    pyautogui.write(text, interval=0.2)


def create_screenshot(screens_directory, name):
    """Создание скриншота"""
    sleep(1)
    im1 = pyautogui.screenshot()
    screenshot = im1.save(screens_directory + name)
    return screenshot

Здесь можно расширить функционал бота, добавив возможность сравнивать время с временем системы, искать поле для ввода текста, делать его активным и вводить приветствие в зависимости от времени суток – с 09.00 до 12.00 «Доброе утро!», с 12.00 до 17.00 «Добрый день!», с 17.00 до 21.00 «Добрый вечер!». Можно сделать список, из которого раз в n минут бот будет забирать рандомное значение и передавать его функции, отправляющей текст – [‘Где экшон?’, ‘Лучше бы в армию пошел’, ‘Отличная мысль!’, ‘Я не расслышал, повторите’, ‘Хочу есть’] и т.д. Но нас на парах отмечали по факту присутствия и нахождению в онлайне – поэтому я не стал с этим заморачиваться.

Для записи экрана по работе я использую заранее настроенный OBS. Здесь его функциональности хватит с головой. Да, я знаю про ffmpeg, но подружиться с ним у меня так и не получилось. В итоге для старта записи я использую поиск окна по заголовку и выношу приложение «на передний план». Эти манипуляции нужны для того, чтобы, имея активное окно OBS, нажать через pyautogui хоткей для старта и окончания записи.

def active_window(title):
    """Найти окно по заголовку и сделать его активным"""
    sleep(1)
    app = Application().connect(title_re=title, backend='win32')
    app.window(title_re=title).set_focus()
    sleep(3)

Кнопки сохранены, шаги в MS Teams боту прописаны, все отлажено. Как запускаться?

Чат бот

До этого я много игрался с чат-ботами, поэтому мой выбор пал именно на них. Самым простым решением имхо здесь является Телеграмм с его @BotFather. Создаем бота, используя telebot. Я захотел отрезать от него все, что можно и нельзя, оставив ему один единственный функционал – получать ссылку и заходит на пару.

А как скармливать ему ссылку и открывать ее в MS Teams? Тут я вспомнил про давно задвинутый в дальний ящик чудесный selenium. Расчехлил и инициализировал хромдрайвер с нажатием на кнопку «Open Microsoft Teams»:

def site_desktop(url):
    chrome_options = webdriver.ChromeOptions()
    driver = webdriver.Chrome(executable_path=driver_chrome, options=chrome_options)
    driver.set_window_size(1920, 1080)
    driver.get(url)
    find_and_click('/open_team.png')
    sleep(3)
    driver.close()

Можно заморочиться с распознаванием сообщений в другом чате, откуда к нам поступаю ссылки на лекции, привязаться ко времени (первая пара в 9.00, вторая в 10.40 и т.д.) и в нужное время автоматом забрать сообщение, начинающееся с ‘https://’, но я предпочитаю контролировать этот процесс и копипастить ссылки лично.

В итоге, когда я отправляю боту url, он делает следующее:

  1. Открывает selenium chrome driver с присланной ссылкой;

  2. Кликает по кнопке "Открыть Microsoft Teams";

  3. Открывает MS Teams, проверяет статус микрофона - включен/выключен. Если включен, отключает;

  4. Подключается к паре;

  5. Делает активным окно OBS, нажимает хоткей для записи экрана, отправляет скриншот рабочего стола в Телеграмм;

  6. Ждет 90 минут;

  7. Отправляет сообщение в Телеграмм о том, что пара закончилась;

  8. Вновь делает активным окно OBS, выключает запись, кликает на кнопку для выхода с пары;

  9. Отправляет финальное сообщение о завершении цикла. Ждет новой ссылки.

bot = telebot.TeleBot(bot_token)


@bot.message_handler(content_types=['text'])
def get_text_messages(message):
    if message.text:
        try:
            bot.send_message(message.chat.id, text='Стартую вход на пару')
            txt = message.text
            site_desktop(txt)
            find_and_click('/disable_micro.png')
            if find_element('/micro_status_on.png'):
                find_and_click('/disable_micro.png')
            find_and_click('/apply_study.png')
            active_window('OBS')
            press_key('f7')
            sleep(3)

            create_screenshot(dir_to_save_fail_screen, name_fail_screen)
            bot.send_photo(message.chat.id, open(dir_to_save_fail_screen + name_fail_screen, 'rb'))
            bot.send_message(message.chat.id, text='Зашел на пару, включил запись экрана')

            # Длительность пары 90 мин == 5400
            sleep(5400)

            bot.send_message(message.chat.id, text='Пара подошла к концу, выключаю запись экрана, выхожу')
            active_window('OBS')
            press_key('f8')
            sleep(3)

            find_and_click('/exit.png')
            bot.send_message(message.chat.id, text='Успешное завершение')
        except Exception as e:
            bot.send_message(message.chat.id, text='ОШИБКА + ' + str(e))


bot.polling()

Не очень хорошо, что 90 минут пары я обернул в простой time.sleep, но этого, как показала практика, более, чем достаточно. Также в OBS можно настроить запись видео только окна MS Teams в небольшом разрешении с фиксированной частотой кадров и пожатым звуком, дабы уменьшить итоговый размер видоса. И затем слать его себе в Телеграмм.

А вот так это выглядит для меня, который в этот момент вполне может ехать куда-нибудь за стройматериалами (ссылки замазюкал, время сократил):

З.Ы. Я ни в коем случае не призываю пропускать таким образом уроки/пары – ученье свет! Но, увы, отечественное образование частично состоит из архаичных лекций времен советского союза, выхлоп от которых… чуть больше, чем никакой. И такой робот позволит хотя бы частично разгрузиться и переключиться на что-то более полезное и продуктивное.