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"
Получился живой, самонаполняющийся и достаточно полезный канал. Осталось заполнить его подписчиками и продавать рекламу…