Робот-самурай. Как научить телеграм-бота писать хокку

Привет! Давно ничего не писал. Обещанного в прошло статье бота закончил, проект оказался довольно сложным, но опыта и знаний заметно прибавилось. А значит время начать новый проект! В этот раз, вдохновившись глубочайшими мыслями японских поэтов-философов, мы будем делать бота, который не просто постит хокку, а сам пишет и подбирает картинку по теме. Строго говоря, бот не придумывает хокку, а формирует новые из уже существующих, но хуже, как мне кажется, он от этого не становится. Итоговый код я оставлю на своём GitHub, а за работой бота можно следить в этом Телеграм канале. Подпишитесь, очень хочу, чтобы этот канал набрал аудиторию.

Готовый результат

Как это работает?

Первым делом нужно создать базу хокку, из которой наш бот будет брать строчки своих стихов. Для этого пришлось изучить несколько сотен хокку и выбрать подходящие. Вставляем всё в текстовый документ, чтобы у нас всегда была возможность добавить строки. Всего 1903 строки, так как хокку всегда состоят из трёх строк, то такого набора хватит на примерно столько вариантов:

Как вы уже могли догадаться, наш бот будет брать 3 случайных строки из списка и формировать из них хокку. Казалось бы, должно получиться нечто совсем бессмысленное, но учитывая специфику жанра, даже самые бредовые сочетания будут иметь (очень глубокий) смысл. С именами авторов мы поступим таким же образом, а год будем генерировать случайный от 0 до 2022 года (н.э или до н.э).

Код

В итоге у нас получилась такая функция:

def hokky_bot():
    f = open('hokky.txt', 'r', encoding='UTF-8')  # Открываем файл с хокку
    all_hokky = f.read().split('\n')  # Записываем каждую строчку в отдульный элемент списка
    f.close()

    f = open('names.txt', 'r', encoding='UTF-8')  # То же самое для файла с именами
    all_names = f.read().split('\n')
    f.close()
    j = 0
    print('Power on!')   
    while j < 10000:
        a = randint(0,1)  # генерируем случайное число для вставки н.э или до н.э.
        if a == 1:
            era ='до н.э.'
        else: 
            era = 'н.э'
        i =0 
        name = [1, 2, 3]
        text = [1, 2, 3]
        while i<=2: 
            name[i] = all_names[randint(1, len(all_names)-1)]  # Формируем списки из 3 строчек хокку и 3-х имён
            text[i] = all_hokky[randint(1, len(all_hokky)-1)] 
            i += 1
        message = (f'{text[0]}\n{text[1]}\n{text[2]}\n\n     - {name[0].title()} {name[1].title()} {name[2].title()}, {randint(0, 2022)} г. {era}')
        j += 1
        search = text[randint(0,2)] 
        print(f'Японская живопись {search}')
        picture(message, search)
        time.sleep(randint(28800, 57600))

Про последние 4 строки подробнее далее.

Теперь наш бот способен выводить хокку, если прописать message внутрь bot.send_message. Примерно так:

Но так как нам этого мало, давайте научим бота отправлять нам картинку, желательно в той же стилистике и с соответствующей тематике.

Как вариант, можно было бы найти несколько изображений руками и выводить их в случайном порядке. Но так нам не получится сделать так, чтобы картинки соответствовали по смыслу тексту хокку. Поэтому мы будем искать (парсить) наши картинки в поиске Яндекса. На помощь нам тут приходит библиотека request и волшебный алгоритм, написанный давно моим близким другом.

def request_photo(message):
    req = requests.get("https://yandex.ru/images/search?text="+message)
    ph_links = list(filter(lambda x: '.jpg' in x, re.findall('''(?<=["'])[^"']+''', req.text)))
    ph_list = []
    for i in range(len(ph_links)):
        if (ph_links[i][0] == "h"):
            ph_list.append(ph_links[i])
    del ph_links
    return ph_list[randint(0, len(ph_list))]

Итак, ищем картинки в поиске, извлекаем ссылку из кода страницы и вуаля, бот вставляет случайную картинку. Чтобы содержание её соответствовало нашим стихам, посылаем поисковой запрос в форме 'Японская живопись + {строчка из хокку}'. То есть в 28-й строке функции hokky_bot м посылаем такой запрос в функцию picture (о ней далее), откуда мы отправляем наш запрос в функцию request_photo.

Штош, нам удалось сделать, чтобы бот выводил то что нам нужно.

Такой результат меня уже более чем устраивает, но как насчёт сделать из картинки полноценное изображение, содержащее хокку. Это позволит нам убрать текст из постов и улучшить общий вид изображений. Для этого нам и нужна функция picture.

def picture(message, search):
    # Код для вставки своего хокку в изображение из request_photo

    im = requests.get(request_photo('японcкая живопись'))  
    out = open("img.jpg", "wb")
    out.write(im.content)
    out.close()
    image = Image.open('img.jpg')

    # Создаем объект со шрифтом
    font = ImageFont.truetype('font.name', size= int(image.width/15))
    draw_text = ImageDraw.Draw(image)
    draw_text.text(
        (int(image.width/50), int(image.height/4)),
        message,
        # Добавляем шрифт к изображению
        font=font,
        fill='#d60000') # Цвет

В этом варианте решения мы записываем найденный запрос в файл img.jpg, после чего уже открываем для редактирования (библиотека Pillow), куда вставляем наш message.

В итоге после настройки и подбора разных шрифтов, лучшее что у меня вышло выглядит так:

Но тут уже не угадаешь, какое у нас будет изображение, тёмное или светлое, а фильтры и т.п. вставлять не хочется. Поэтому этот вариант мы оставим для других проектов, а пока довольствуемся результатом.

Всё! Ещё раз напоминаю, код проекта можно найти на GitHub.

Телеграм канал с хокку. Подпишитесь, мне будет очень приятно.

Если есть идеи, как можно доработать бота, делитесь в комментариях. Всем пока, до встречи в новых статьях!

© Habrahabr.ru