Найти в Дзене

🚀 Как я автоматизировал уведомления о печати: Путь к Умному 3D-Принтеру! 📄👀

Сегодня я расскажу не совсем обычную историю для моего блога. Мы не будем говорить о настройках 3D-принтера или новых материалах. Вместо этого я поделюсь тем, как создал скрипт, который автоматически уведомляет моих подписчиков в ВК и Telegram о начале печати заказа. Это не только упрощает мою жизнь, но и делает процесс более интерактивным для вас! 🤝 Когда я только начинал свой путь в 3D-печати, я столкнулся с вопросом: как уведомить подписчиков о начале печати и дать им возможность следить за процессом в реальном времени? Решение нашлось в создании скрипта на Python, который публикует истории в ВК и сообщения в Telegram. Давайте разберем, как это работает! Первым шагом было настроить окружение для работы скрипта. Я использовал библиотеки requests для работы с API ВК и Telegram, PIL для обработки изображений и logging для логирования.
import requests from requests_toolbelt import MultipartEncoder import random import string import logging import time import uuid import asyncio from
Оглавление

Сегодня я расскажу не совсем обычную историю для моего блога. Мы не будем говорить о настройках 3D-принтера или новых материалах. Вместо этого я поделюсь тем, как создал скрипт, который автоматически уведомляет моих подписчиков в ВК и Telegram о начале печати заказа. Это не только упрощает мою жизнь, но и делает процесс более интерактивным для вас! 🤝

Введение

Когда я только начинал свой путь в 3D-печати, я столкнулся с вопросом: как уведомить подписчиков о начале печати и дать им возможность следить за процессом в реальном времени? Решение нашлось в создании скрипта на Python, который публикует истории в ВК и сообщения в Telegram. Давайте разберем, как это работает!

Настройка окружения

Первым шагом было настроить окружение для работы скрипта. Я использовал библиотеки requests для работы с API ВК и Telegram, PIL для обработки изображений и logging для логирования.

import requests
from requests_toolbelt import MultipartEncoder
import random
import string
import logging
import time
import uuid
import asyncio
from PIL import Image, ImageDraw, ImageFont
import sys

Логирование

Логирование помогает отслеживать выполнение скрипта и находить ошибки. Я настроил его так:

logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()]
)

Настройки и аргументы командной строки

Для работы скрипта мне понадобились токены доступа к API ВК и Telegram, а также ID группы и канала. Эти данные я спрятал в переменных:

VK_TOKEN = 'ваш_токен_вк'
TG_TOKEN = 'ваш_токен_tg'
TG_CHANNEL = '@ваш_канал'
VK_GROUP_ID = 'ваш_group_id'

Аргументы командной строки позволяют передавать в скрипт имя файла и время печати:

file_name = sys.argv[1] if len(sys.argv) > 1 else "Неизвестный файл"
file_time = sys.argv[2] if len(sys.argv) > 2 else "Неизвестное время"

Форматирование текста

Текст для публикации я храню в списке TEXT_LINES. Функция format_text объединяет строки в один текст:

TEXT_LINES = [
"Друзья! 👋",
"Запущена печать очередного заказа! 🚀",
f"Файл: {file_name} 📄",
f"Время печати: {file_time} ⏳",
"Жмите кнопку ниже и смотрите прямую трансляцию! 👀"
]

def format_text(text_lines):
return "\n".join(text_lines)

Проверка изображения

Перед загрузкой изображения в ВК, его нужно проверить. Я добавил функцию check_image, которая проверяет размер изображения:

def check_image(image_path):
try:
with Image.open(image_path) as img:
width, height = img.size
logging.info(f"Размер изображения: {width}x{height}")
if width < 100 or height < 100:
logging.error("Изображение слишком маленькое. Минимальный размер: 100x100 пикселей.")
return False
return True
except Exception as e:
logging.error(f"Ошибка при проверке изображения: {e}")
return False

Добавление текста на изображение

Функция add_text_to_image добавляет текст на изображение. Это позволяет сделать публикацию более наглядной:

def add_text_to_image(image_path, output_path, text_lines, font_size=60, padding=20):
try:
with Image.open(image_path) as img:
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("arial.ttf", size=font_size)
except IOError:
font = ImageFont.load_default()

text = format_text(text_lines)
max_width = img.width - 2 * padding
lines = []
for paragraph in text.split('\n'):
words = paragraph.split()
current_line = words[0] if words else ""
for word in words[1:]:
bbox = draw.textbbox((0, 0), current_line + " " + word, font=font)
line_width = bbox[2] - bbox[0]
if line_width <= max_width:
current_line += " " + word
else:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)

total_height = 0
for line in lines:
bbox = draw.textbbox((0, 0), line, font=font)
total_height += bbox[3] - bbox[1]

y = (img.height - total_height) // 2
for line in lines:
bbox = draw.textbbox((0, 0), line, font=font)
text_width = bbox[2] - bbox[0]
x = (img.width - text_width) // 2
draw.text((x, y), line, font=font, fill="white")
y += bbox[3] - bbox[1]

img.save(output_path)
logging.info(f"Текст добавлен на изображение: {text}")
return True
except Exception as e:
logging.error(f"Ошибка при добавлении текста на изображение: {e}")
return False

Публикация истории в ВК

Функция post_story_to_vk публикует историю в ВК. Для этого я использовал метод stories.getPhotoUploadServer из API ВК, чтобы получить URL для загрузки изображения:

async def post_story_to_vk(text_lines, link_url=None):
try:
resized_image_path = "resized_image.png"
if not resize_image("black.png", resized_image_path):
return

image_with_text_path = "image_with_text.png"
if not add_text_to_image(resized_image_path, image_with_text_path, text_lines):
return

if not check_image(image_with_text_path):
return

formatted_text = format_text([line.replace("👋", "").replace("🚀", "").replace("📄", "").replace("👀", "") for line in text_lines])
logging.info(f"Используемый текст для ВК: {formatted_text}")

unique_message = formatted_text + f" [{time.time()}] [{uuid.uuid4()}] [Random: {random.randint(1000, 9999)}]"
logging.info(f"Создан уникальный текст: {unique_message}")

get_upload_url_data = {
"v": "5.199",
"group_id": VK_GROUP_ID,
"access_token": VK_TOKEN,
"add_to_news": 1
}

if link_url:
get_upload_url_data["link_text"] = "view"
get_upload_url_data["link_url"] = link_url

logging.info(f"Запрос на получение URL для загрузки: {get_upload_url_data}")
r = requests.post(
"https://api.vk.com/method/stories.getPhotoUploadServer",
data=get_upload_url_data
)
logging.info(f"Ответ от stories.getPhotoUploadServer: {r.status_code}, {r.text}")
upload_url = r.json()["response"]["upload_url"]
logging.info(f"Получен URL для загрузки: {upload_url}")

fields = {
'file': ('resized_image.png', open(image_with_text_path, "rb"), "image/png"),
}
boundary = '----WebKitFormBoundary' + ''.join(random.sample(string.ascii_letters + string.digits, 16))
m = MultipartEncoder(fields=fields, boundary=boundary)
logging.info(f"Подготовлен multipart запрос с boundary: {boundary}")

logging.info(f"Загрузка изображения на URL: {upload_url}")
r = requests.post(
upload_url,
data=m,
headers={"Content-Type": m.content_type}
)
logging.info(f"Ответ от сервера загрузки: {r.status_code}, {r.text}")

if r.status_code != 200:
logging.error(f"Ошибка при загрузке изображения: {r.text}")
return

upload_results = r.json()["response"]
logging.info(f"Результаты загрузки: {upload_results}")

save_story_data = {
"v": "5.199",
"upload_results": upload_results["upload_result"],
"text": unique_message,
"group_id": VK_GROUP_ID,
"access_token": VK_TOKEN
}

if link_url:
save_story_data["link"] = {
"text": "Купить",
"url": link_url
}
logging.info(f"Добавлена ссылка к истории: {link_url}")

logging.info(f"Запрос на создание истории: {save_story_data}")
story_response = requests.post(
"https://api.vk.com/method/stories.save",
data=save_story_data
)
logging.info(f"Ответ от stories.save: {story_response.status_code}, {story_response.text}")

if story_response.status_code == 200:
logging.info('История успешно опубликована в ВК от имени группы')
else:
logging.error(f'Ошибка при публикации истории: {story_response.text}')

except Exception as e:
logging.error(f'Неожиданная ошибка при публикации в ВК: {e}')

Публикация сообщения в Telegram

Функция post_to_tg публикует сообщение в Telegram. Я использовал метод sendPhoto из API Telegram для отправки фото с подписью:

async def post_to_tg(text_lines, image_path):
try:
formatted_text = format_text(text_lines)
logging.info(f"Используемый текст для Telegram: {formatted_text}")

with open(image_path, 'rb') as photo:
files = {'photo': photo}
data = {
'chat_id': TG_CHANNEL,
'caption': formatted_text
}
response = requests.post(
f"https://api.telegram.org/bot{TG_TOKEN}/sendPhoto",
files=files,
data=data
)
logging.info(f"Telegram response: {response.json()}")
if response.status_code == 200:
logging.info('Пост успешно опубликован в Telegram')
else:
logging.error(f'Ошибка при публикации в Telegram: {response.text}')
except Exception as e:
logging.error(f'Неожиданная ошибка при публикации в Telegram: {e}')

Запуск скрипта при старте печати

Для автоматического запуска скрипта при старте печати необходимо добавить макрос в файл printer.cfg:

[gcode_macro START_PRINT]
gcode:
RUN_SHELL_COMMAND CMD="python3 /path/to/your/botik.py '{params.FILE_NAME}' '{params.FILE_TIME}'"

И в стартовый gcode печати добавить вызов макроса:

START_PRINT FILE_NAME="test.gcode" FILE_TIME="2 часа 30 минут"

Заключение

Создание скрипта для автоматического оповещения подписчиков — это увлекательный процесс, полный испытаний и ошибок. Но результат стоит того: теперь мои подписчики всегда в курсе, когда начинается печать нового заказа и могут следить за процессом в реальном времени. Надеюсь, эта статья была полезна и вдохновит вас на создание собственных автоматизированных решений!

Следите за обновлениями в нашем блоге и не забывайте подписываться на наши каналы! 🚀📄👀

Полный код скрипта
import requests
from requests_toolbelt import MultipartEncoder
import random
import string
import logging
import time
import uuid
import asyncio
from PIL import Image, ImageDraw, ImageFont
import sys

logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()]
)

VK_TOKEN = 'ваш_токен_вк'
TG_TOKEN = 'ваш_токен_tg'
TG_CHANNEL = '@ваш_канал'
VK_GROUP_ID = 'ваш_group_id'

file_name = sys.argv[1] if len(sys.argv) > 1 else "Неизвестный файл"
file_time = sys.argv[2] if len(sys.argv) > 2 else "Неизвестное время"

TEXT_LINES = [
"Друзья! 👋",
"Запущена печать очередного заказа! 🚀",
f"Файл: {file_name} 📄",
f"Время печати: {file_time} ⏳",
"Жмите кнопку ниже и смотрите прямую трансляцию! 👀"
]

def format_text(text_lines):
return "\n".join(text_lines)

def check_image(image_path):
try:
with Image.open(image_path) as img:
width, height = img.size
logging.info(f"Размер изображения: {width}x{height}")
if width < 100 or height < 100:
logging.error("Изображение слишком маленькое. Минимальный размер: 100x100 пикселей.")
return False
return True
except Exception as e:
logging.error(f"Ошибка при проверке изображения: {e}")
return False

def add_text_to_image(image_path, output_path, text_lines, font_size=60, padding=20):
try:
with Image.open(image_path) as img:
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("arial.ttf", size=font_size)
except IOError:
font = ImageFont.load_default()

text = format_text(text_lines)
max_width = img.width - 2 * padding
lines = []
for paragraph in text.split('\n'):
words = paragraph.split()
current_line = words[0] if words else ""
for word in words[1:]:
bbox = draw.textbbox((0, 0), current_line + " " + word, font=font)
line_width = bbox[2] - bbox[0]
if line_width <= max_width:
current_line += " " + word
else:
lines.append(current_line)
current_line = word
if current_line:
lines.append(current_line)

total_height = 0
for line in lines:
bbox = draw.textbbox((0, 0), line, font=font)
total_height += bbox[3] - bbox[1]

y = (img.height - total_height) // 2
for line in lines:
bbox = draw.textbbox((0, 0), line, font=font)
text_width = bbox[2] - bbox[0]
x = (img.width - text_width) // 2
draw.text((x, y), line, font=font, fill="white")
y += bbox[3] - bbox[1]

img.save(output_path)
logging.info(f"Текст добавлен на изображение: {text}")
return True
except Exception as e:
logging.error(f"Ошибка при добавлении текста на изображение: {e}")
return False

async def post_story_to_vk(text_lines, link_url=None):
try:
resized_image_path = "resized_image.png"
if not resize_image("black.png", resized_image_path):
return

image_with_text_path = "image_with_text.png"
if not add_text_to_image(resized_image_path, image_with_text_path, text_lines):
return

if not check_image(image_with_text_path):
return

formatted_text = format_text([line.replace("👋", "").replace("🚀", "").replace("📄", "").replace("👀", "") for line in text_lines])
logging.info(f"Используемый текст для ВК: {formatted_text}")

unique_message = formatted_text + f" [{time.time()}] [{uuid.uuid4()}] [Random: {random.randint(1000, 9999)}]"
logging.info(f"Создан уникальный текст: {unique_message}")

get_upload_url_data = {
"v": "5.199",
"group_id": VK_GROUP_ID,
"access_token": VK_TOKEN,
"add_to_news": 1
}

if link_url:
get_upload_url_data["link_text"] = "view"
get_upload_url_data["link_url"] = link_url

logging.info(f"Запрос на получение URL для загрузки: {get_upload_url_data}")
r = requests.post(
"https://api.vk.com/method/stories.getPhotoUploadServer",
data=get_upload_url_data
)
logging.info(f"Ответ от stories.getPhotoUploadServer: {r.status_code}, {r.text}")
upload_url = r.json()["response"]["upload_url"]
logging.info(f"Получен URL для загрузки: {upload_url}")

fields = {
'file': ('resized_image.png', open(image_with_text_path, "rb"), "image/png"),
}
boundary = '----WebKitFormBoundary' + ''.join(random.sample(string.ascii_letters + string.digits, 16))
m = MultipartEncoder(fields=fields, boundary=boundary)
logging.info(f"Подготовлен multipart запрос с boundary: {boundary}")

logging.info(f"Загрузка изображения на URL: {upload_url}")
r = requests.post(
upload_url,
data=m,
headers={"Content-Type": m.content_type}
)
logging.info(f"Ответ от сервера загрузки: {r.status_code}, {r.text}")

if r.status_code != 200:
logging.error(f"Ошибка при загрузке изображения: {r.text}")
return

upload_results = r.json()["response"]
logging.info(f"Результаты загрузки: {upload_results}")

save_story_data = {
"v": "5.199",
"upload_results": upload_results["upload_result"],
"text": unique_message,
"group_id": VK_GROUP_ID,
"access_token": VK_TOKEN
}

if link_url:
save_story_data["link"] = {
"text": "Купить",
"url": link_url
}
logging.info(f"Добавлена ссылка к истории: {link_url}")

logging.info(f"Запрос на создание истории: {save_story_data}")
story_response = requests.post(
"https://api.vk.com/method/stories.save",
data=save_story_data
)
logging.info(f"Ответ от stories.save: {story_response.status_code}, {story_response.text}")

if story_response.status_code == 200:
logging.info('История успешно опубликована в ВК от имени группы')
else:
logging.error(f'Ошибка при публикации истории: {story_response.text}')

except Exception as e:
logging.error(f'Неожиданная ошибка при публикации в ВК: {e}')

async def post_to_tg(text_lines, image_path):
try:
formatted_text = format_text(text_lines)
logging.info(f"Используемый текст для Telegram: {formatted_text}")

with open(image_path, 'rb') as photo:
files = {'photo': photo}
data = {
'chat_id': TG_CHANNEL,
'caption': formatted_text
}
response = requests.post(
f"https://api.telegram.org/bot{TG_TOKEN}/sendPhoto",
files=files,
data=data
)
logging.info(f"Telegram response: {response.json()}")
if response.status_code == 200:
logging.info('Пост успешно опубликован в Telegram')
else:
logging.error(f'Ошибка при публикации в Telegram: {response.text}')
except Exception as e:
logging.error(f'Неожиданная ошибка при публикации в Telegram: {e}')

async def main():
image_path = "black.png"
link_url = "https://vk.com"
await asyncio.gather(
post_story_to_vk(TEXT_LINES, link_url),
post_to_tg(TEXT_LINES, image_path)
)

if __name__ == '__main__':
asyncio.run(main())

Создание этого скрипта стало для меня не только техническим вызовом, но и шагом на пути к автоматизации моего блога. Теперь, когда проект готов, я рад поделиться им с вами! Готовый проект бота со всеми инструкциями и настройками можно будет скачать в моем Telegram канале и группе ВК. Подписывайтесь, чтобы не пропустить обновления и быть в курсе всех новинок! Если у вас возникнут вопросы или предложения, не стесняйтесь писать — я всегда рад общению. Вместе мы сделаем процесс 3D-печати еще увлекательнее и доступнее! 🚀📄👀