artbobylev.ru

Пет-проект: автоматизированный телеграм-канал на python

Идеи пет-проектов с телеграм-ботами мне не нравились.

Опять базы данных, опять много кода, опять трудность «заставить» людей пользоваться. Одна из целей моих пет-проектов – минимизация времени на разработку.

Поэтому для реализации своих небольших идей нашел подход гораздо проще и для себя и для пользователя – автоматизированный телеграм-канал.

Хочу показать пример создания телеграм-канала про интересные гитхаб-репозитории. Интересные они или нет нам скажет число stars, соберем 1000 репозиториев, автоматизируем создание краткого описания с llm и публикацию через скрипт.

Посмотреть на готовый вариант такого канала можно по ссылке.

Шаг 1: сбор ссылок на репозитории

Как собрать ссылки на гитхаб-репозитории с числом stars > 1000, начиная с 2018 года? – второй ответ на stackoverflow:

import json
import requests

GITHUB_API = "https://api.github.com/search/repositories"
TOKEN = "***"

headers = {"Authorization": f"token {TOKEN}"}

def get_repositories_with_min_stars(min_stars, page):
    params = {
        "q": f"created:>=2018-01-01 stars:>={min_stars}",
        "sort": "created",
        "order": "desc",
        "page": page,
        "per_page": 100,
    }
    response = requests.get(GITHUB_API, headers=headers, params=params)
    if response.status_code != 200:
        return 0

    data = json.loads(response.text)
    for item in data["items"]:
        print(item["full_name"])
    return data["total_count"]

for i in range (1, 11):
	repositories = get_repositories_with_min_stars(min_stars=1000, page=i)

Для получения гитхаб-токена в профиле нужно перейти в Settings -> Developer settings -> Personal access tokens -> Fine-grained tokens.

Скрипт выдаст 1000 первых репозиториев запроса. Этого будет достаточно на год регулярных публикаций.

python3 get_git_repos.py > repos_1000
liguodongiot/llm-action
pluja/awesome-privacy
richards199999/Thinking-Claude
gitbutlerapp/gitbutler
openobserve/openobserve
openimsdk/open-im-server
picocss/pico
sindresorhus/ky
omnivore-app/omnivore
...

Шаг 2: автоматизация скриншота + выделение текста-описания проекта

Текста в публикации будет немного, поэтому нужна картинка – будем делать скриншот области с данными про репозиторий с помощью selenium. Аргументом командной строки будем передавать скрипту image.py title репозитория, например, python3 image.py karpathy/micrograd:

from selenium import webdriver
from selenium.webdriver.common.by import By

from PIL import Image
from io import BytesIO
import time
import sys
import requests
import re

def take_screenshot(driver, x, y, width, height, image_title):
    driver.fullscreen_window()
    time.sleep(1)

    full_screenshot = driver.get_screenshot_as_png()

    image = Image.open(BytesIO(full_screenshot))
    cropped_image = image.crop((x, y, x + width, y + height))  

    cropped_image.save("./publications_data/" + image_title + '.png')
    driver.quit()  

title = sys.argv[1]
driver = webdriver.Safari()
url = "https://github.com/" + title
x, y = 1874, 490
width, height = 683, 850

Текстовое описание репозитория (README) заключено в элементе <article>.

Мы собираемся в дальнейшем передать это описание нейросети для формирования краткого описания репозитория, поэтому ограничим text_content в 800 символов – главная информация в начале.

driver.get(url)
article_element = driver.find_element(By.TAG_NAME, "article")

text_content = article_element.text[:800]

Еще один момент – нам могут попадаться китайские репозитории, исключим их:

def contains_chinese(text):
    pattern = r'[\u3040-\u30FF\u4E00-\u9FFF]+'

    return bool(re.search(pattern, text))

if contains_chinese(text_content):
    print("Китайщина")
    driver.quit()
else:
    image_title = title.replace("/","_")
    take_screenshot(driver, 1874, 490, 683, 850, image_title)

    with open("./publications_data/" + title.replace("/", "_") + "_source.txt", "w") as file:
        file.write(text_content)

Шаг 3: краткое описание с llm

Модель llama3.1 дает неплохой результат в данной задаче. Используя ее через ollama, подготовим текст публикации скриптом text.py:

from langchain_community.llms import Ollama
import sys 

title = sys.argv[1]
url = "https://github/" + title
llm = Ollama(model="llama3.1:latest")
text_content = ""

with open("./publications_data/" + title.replace("/", "_") + "_source.txt", "r") as file:
    text_content = file.read()

my_msg = f"На основе приведенной информации напиши публикацию \
на русском языке в телеграм: заголовок, краткое описание, \
2-3 пункта. В ответе только сплошной текст – не пиши 'заголовок', \
'описание', просто весь текст. Отвечай в формате Markdown, \
заголовок выделяй жирным, не используй спорных формулировок, \
не будь слишком эмоциональным. Краткая, сухая справка длиной \
не более 300 символов. Используй смайлики! Информация об установке \
и использовании не нужна. Опиши, что это за проект и его особенности \
если есть. Заголовок состоит из смайлика, названия репозитория \
и краткой фразы. На русском языке. Информация: {text_content}"

result = llm.invoke(my_msg)

text = result + "\n\n" + url
print(text) 

with open("./publications_data/" + title.replace("/", "_") + ".txt", "w") as file:
    file.write(text)

Шаг 4: запускаем генерацию

Проходим циклом по собранным repos_1000, запуская image.py и text.py. Если все в порядке, к соответствующему репозиторию в repos_1000 добавляем |done:

while True; do repo=$(grep -v "done" repos_1000 | head -n 1); \
line=$(echo "$repo" | sed "s/\//\\\//" ); \
result=$(python3 image.py $repo); \
echo $result; if [[ "$result" == "Китайщина" ]]; \
then sed -i.back "/$line/d" ; \
else python3 text.py $repo && sed -i.back "s/$line/${line}|done" ; \
fi; done; 

Шаг 5: автоматизируем публикацию

Потребуется несколько предварительных шагов:

  • Создать бота в телеграм
  • Добавить его в администраторы канала с привилегией отправки сообщений
  • Узнать id канала, например, переслав сообщение из канала в бот @get_id_bot.

Пишем скрипт tg_post.py:

import asyncio
from telegram import Bot, Update
from telegram.ext import Updater, CommandHandler, CallbackContext, MessageHandler, filters
import sys

title = sys.argv[1]
title = title.replace("/","_")
main_path = "./publications_data/"

async def send_msg(text, image_path):
    token = '***' # токен созданного бота
    chat_id = "***" # id канала, начинающийся с -100
    bot = Bot(token=token)
    
    await bot.send_photo(chat_id=chat_id, photo=open(image_path, 'rb'), caption=text, parse_mode='Markdown')

with open(main_path + title + ".txt", "r") as file:
    text = file.read()

text = text.replace("**", "*") # спорный момент – разные markdown

image_path = main_path + title + ".png"
asyncio.run(send_msg(text, image_path))
python3 tg_post.py "f/awesome-chatgpt-prompts"

Получился живой, самонаполняющийся и достаточно полезный канал. Осталось заполнить его подписчиками и продавать рекламу…


Читать ещё:

⟲ на главную

Кто я?

→ Бобылев Артём

Я программист. Нет, инди-разработчик – люблю в одиночку заниматься разработкой своих небольших проектов. Сейчас занят интерфейсами над производством, например:

predstav.store

onlytone.ru

GitCloths

Подпишись на мою рассылку!