3 мин

Автоматизируем добавление запланирванных стримов youtube каналов в google календарь

Изображение показывает процесс переноса стрима в календрь

Проблематика

На днях столкнулся с такой ситуацией для себя, что мне необходимо добавлять запланированные стримы с каналов YouTube в свой календарь. Я ленивый человек и делать это вручную не особо желаю, поэтому решил это дело автоматизировать.

Для начала определился, можно ли вообще получить такую информацию и был очень удивлен, что у google можно получить очень большой спектр различной информации от разных сервисов, спасибо им большое за предоставленную возможность.

Дальше нашел уже готовую библиотеку по работе с сервисами google и подготовил необходимый алгоритм. Алгоритм состоял из таких шагов:

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

Реализация

Итак, создаю необходимые переменные окружения:

# Настройки
YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY")
YOUTUBE_CHANNEL_ID = os.getenv("YOUTUBE_CHANNEL_ID")
GOOGLE_CALENDAR_ID = os.getenv("GOOGLE_CALENDAR_ID")
TELEGRAM_CHANNEL_ID = os.getenv("TELEGRAM_CHANNEL_ID")
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")

переменные окружения

GOOGLE_CALENDAR_ID - идентификатор канала с которого будут считываться запланированные стримы


Создаю сервис календаря с которым буду работать. По авторизации google приложений есть в документации, подробно останавливаться не буду.

# Авторизация в Google Calendar
creds = Credentials.from_authorized_user_file('м', ['https://www.googleapis.com/auth/calendar'])

service = build('calendar', 'v3', credentials=creds)

авторизация

token.json - файл который появится после авторизации


Ошибки получаемые при запросах я буду отправлять в телеграм, мне так удобнее(все опционально)

def send_telegram_alert(message: str):
    """Отправляет сообщение об ошибке в Telegram канал."""
    try:
        url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
        payload = {
            "chat_id": TELEGRAM_CHANNEL_ID,
            "text": message,
            "disable_notification": False
        }
        response = requests.post(url, json=payload)
        response.raise_for_status()
    except Exception as e:
        print(f"⚠️ Ошибка при отправке в Telegram: {str(e)}")

функция отправки сообщений в канал


И теперь основные шаги алгоритма:

  • получаю предстоящие стримы канала
def get_upcoming_streams():
    """Получает предстоящие стримы с YouTube API с обработкой ошибок."""
    try:
        # Формируем запрос
        url = "https://www.googleapis.com/youtube/v3/search"
        params = {
            "part": "snippet",
            "channelId": YOUTUBE_CHANNEL_ID,
            "eventType": "upcoming",
            "type": "video",
            "key": YOUTUBE_API_KEY
        }

        # Выполняем запрос
        response = requests.get(url, params=params)
        response.raise_for_status()  # Проверка HTTP ошибок
        data = response.json()

        # Проверка ошибок API
        if "error" in data:
            error_msg = data["error"].get("message", "Неизвестная ошибка API")
            raise RuntimeError(f"Ошибка Youtube API: {error_msg}")

        return data.get("items", [])

    except (HTTPError, ConnectionError, Timeout) as e:
        error_msg = f"🚨 Сетевая ошибка: {str(e)}"
        print(error_msg)
        send_telegram_alert(error_msg)
    except Exception as e:
        error_msg = f"⚠️ Неизвестная ошибка: {str(e)}"
        print(error_msg)
        send_telegram_alert(error_msg)

    return []  # Возвращаем пустой список в случае ошибки

функция получения предстоящих стримов

  • записываю стримы в календарь
def create_calendar_event(stream):
    """Записываем в календарь стрим"""
    try:
        snippet = stream["snippet"]
        start_time = datetime.fromisoformat(snippet["publishedAt"].replace("Z", "+03:00"))
        event = {
            "summary": f"Стрим: {snippet['title']}",
            "description": f"Ссылка: https://youtu.be/{stream['id']['videoId']}",
            "start": {
                "dateTime": start_time.isoformat(),
                "timeZone": "UTC"
            },
            "end": {
                "dateTime": (start_time + timedelta(hours=1)).isoformat(),  # Предполагаемая длительность
                "timeZone": "UTC"
            },
            "reminders": {
                "useDefault": False,
                "overrides": [
                    {"method": "popup", "minutes": 30}  # Напоминание за 30 минут
                ]
            }
        }
        service.events().insert(calendarId=GOOGLE_CALENDAR_ID, body=event).execute()
    except HttpError as e:
        error_msg = f"🚨 Ошибка Google Calendar API: {e.resp.status} {e.error_details}"
        send_telegram_alert(error_msg)
    except (ConnectionError, Timeout) as e:
        error_msg = f"🚨 Сетевая ошибка: {str(e)}"
        send_telegram_alert(error_msg)
    except Exception as e:
        error_msg = f"⚠️ Неизвестная ошибка: {str(e)}"
        send_telegram_alert(error_msg)

функция записи стрима в календарь, создание события


Чтобы стримы не дублировались в календаре, я предусмотрел сохранение идентификаторов стрима в БД (sqlite). Можно использовать любой вариант для сохранения идентификаторов, выбирайте под себя.

Дополнительные функции для работы с БД

conn = sqlite3.connect("notifications.db")
cursor = conn.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS sent (video_id TEXT PRIMARY KEY)")


def is_notification_sent(video_id):
    cursor.execute("SELECT 1 FROM sent WHERE video_id=?", (video_id,))
    return cursor.fetchone() is not None


def mark_as_sent(video_id):
    cursor.execute("INSERT INTO sent VALUES (?)", (video_id,))
    conn.commit()

инициализация БД и функции для работы


И сам запуск программы теперь:

# Запуск
streams = get_upcoming_streams()
for stream in streams:
    if not is_notification_sent(stream["id"]["videoId"]):
        create_calendar_event(stream)
        mark_as_sent(stream["id"]["videoId"])

основной цикл и проверка записей в БД

Дальше я использовал менеджер агентов Huginn для автоматического запуска этой программы по времени. Для этого я использую агента Shell для вызова скрипта python (делайте это на свой страх и риск, так как команда разработчиков huginn не рекомендует использовать этот тип агента), можно использовать механизм какой удобно. Настроил агента на запуск в 9 часов утра каждый день.

Дальше можно расширят функционал и задачи. Спасибо за просмотр!