#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Скрипт однократного запуска: загрузка роликов из Google Drive в папку download_dir, публикация Reels в аккаунты Day 4, затем перенос успешных в Day 5 или в Repair, обновление общего листа All с использованием UA из таблицы и хранением сессий в папке sessions. """ import os import sys import time import logging import shutil import subprocess import requests import gspread from gspread_formatting import CellFormat, Color, format_cell_range from instagrapi import Client from instagrapi.exceptions import TwoFactorRequired, ChallengeRequired # Add SUPPORTED_CAPABILITIES if missing to avoid attribute errors import instagrapi.config as _cfg if not hasattr(_cfg, 'SUPPORTED_CAPABILITIES'): _cfg.SUPPORTED_CAPABILITIES = [] # --- Конфигурация --- SERVICE_ACCOUNT_FILE = 'credentials.json' SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/d/1ekBBzwnRsuRddiS-ptz3LgU9OFWj4UEzCqEYFNcNssg/edit' GID_DAY4 = 819797040 # Day 4 input (A–F) GID_DAY5 = 45079317 # Day 5 output GID_REPAIR = 675950768 # Repair GID_ALL = 2002240395 # All DRIVE_FOLDER_ID = '1rmlHCARMCTj2L2FJPqx6sDd6MwgkSx09' DOWNLOAD_DIR = 'download_dir' SESSIONS_DIR = 'sessions' TG_TOKEN = '6701110305:AAHzjIDGgW32EOSegqSumSpeBYnxQmAs7lE' TG_CHAT_ID = '5380248847' CAPTION = ( 'Чтобы забрать бесплатный урок переходи на мой основной аккаунт @m.idrisow ' 'и напиши в директ слово «урок»' ) DAY_NUMBER = 4 NEXT_DAY = DAY_NUMBER + 1 # Форматы RED_FORMAT = CellFormat(backgroundColor=Color(1, 0.8, 0.8)) WHITE_FORMAT = CellFormat(backgroundColor=Color(1, 1, 1)) logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') def send_telegram(msg: str): logging.info(f"Telegram: {msg}") if TG_TOKEN and TG_CHAT_ID: try: requests.post( f'https://api.telegram.org/bot{TG_TOKEN}/sendMessage', json={'chat_id': TG_CHAT_ID, 'text': msg}, timeout=10 ) except Exception as e: logging.error(f"Telegram error: {e}") # Google Sheets client gc = gspread.service_account(filename=SERVICE_ACCOUNT_FILE) def open_sheet(gid: int): ws = gc.open_by_url(SPREADSHEET_URL).get_worksheet_by_id(gid) logging.info(f"Открыт лист '{ws.title}' ({gid})") return ws # Утилиты def find_and_delete(ws, key: str): try: cell = ws.find(key) ws.delete_rows(cell.row) logging.info(f"Удалена запись {key} из '{ws.title}'") except Exception: pass def update_or_append_all(ws_all, row_vals: list, ua: str, tag: str): # Guard against empty row_vals if not row_vals or not row_vals[0]: logging.error(f"update_or_append_all: пустые row_vals: {row_vals}") return try: cell = ws_all.find(row_vals[0]) ws_all.update_cell(cell.row, 6, ua) ws_all.update_cell(cell.row, 7, tag) fmt = RED_FORMAT if 'ремонт' in tag else WHITE_FORMAT format_cell_range(ws_all, f"A{cell.row}:G{cell.row}", fmt) logging.info(f"Обновлён All для {row_vals[0]}: UA={ua}, статус={tag}") except Exception: idx = len(ws_all.col_values(1)) + 1 new_row = row_vals + [tag] ws_all.insert_row(new_row, index=idx) fmt = RED_FORMAT if 'ремонт' in tag else WHITE_FORMAT format_cell_range(ws_all, f"A{idx}:G{idx}", fmt) logging.info(f"Добавлен в All {row_vals[0]}: UA={ua}, статус={tag}") # Загрузка видео из Google Drive # Скачиваем только один раз, далее используем папку DOWNLOAD_DIR def download_videos_once(): if os.path.isdir(DOWNLOAD_DIR) and any(fn.lower().endswith(('.mp4', '.mov')) for fn in os.listdir(DOWNLOAD_DIR)): logging.info("Видео уже загружены, пропуск загрузки.") return shutil.rmtree(DOWNLOAD_DIR, ignore_errors=True) os.makedirs(DOWNLOAD_DIR, exist_ok=True) logging.info("Скачиваем ролики из Drive...") subprocess.run([ 'gdown', '--folder', f'https://drive.google.com/drive/folders/{DRIVE_FOLDER_ID}', '-O', DOWNLOAD_DIR ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) logging.info("Видео загружены в папку download_dir.") # Логин и сессия (использует UA из таблицы) def try_login(user: str, pwd: str, proxy: str, ua: str): os.makedirs(SESSIONS_DIR, exist_ok=True) session_file = os.path.join(SESSIONS_DIR, f"{user}.json") cl = Client(settings={"user_agent": ua}) cl.set_user_agent(ua) if proxy: proxy_val = proxy if proxy.startswith('socks5h://') else f'socks5h://{proxy}' cl.set_proxy(proxy_val) if os.path.isfile(session_file): try: cl.load_settings(session_file) logging.info(f"Загружена сессия: {user}") return cl, None except Exception: os.remove(session_file) try: cl.login(user, pwd) try: cl.private_request('si/fetch_headers/') except Exception as e: logging.warning(f"Не удалось обновить CSRF для {user}: {e}") cl.dump_settings(session_file) logging.info(f"Успешный логин: {user}") return cl, None except TwoFactorRequired: return None, 'two_factor_required' except ChallengeRequired: return None, 'challenge_required' except Exception as e: return None, str(e) # Публикация Reels def publish_reel(cl: Client, video_path: str): try: cl.clip_upload(video_path, caption=CAPTION) logging.info(f"Опубликован Reel: {os.path.basename(video_path)}") return True, None except Exception as e: logging.error(f"Ошибка при публикации {os.path.basename(video_path)}: {e}") return False, str(e) # Очищаем исходную строку A-F def clear_source_row(ws, idx: int): ws.batch_clear([f'A{idx}:F{idx}']) logging.info(f"Очищены A-F строки {idx} в {ws.title}") # Основной процесс Day4->Day5 if __name__ == '__main__': download_videos_once() ws_day4 = open_sheet(GID_DAY4) ws_day5 = open_sheet(GID_DAY5) ws_repair = open_sheet(GID_REPAIR) ws_all = open_sheet(GID_ALL) # Собираем список видео рекурсивно из DOWNLOAD_DIR videos = [] for root, dirs, files in os.walk(DOWNLOAD_DIR): for fn in files: if fn.lower().endswith(('.mp4', '.mov')): videos.append(os.path.join(root, fn)) videos.sort() if not videos: logging.error("Нет видео в папке download_dir. Проверьте download_videos_once.") sys.exit(1) rows = ws_day4.get_values('A2:F') success = fail = 0 for idx, row in enumerate(rows, start=2): user = row[0].strip() if len(row)>0 else '' pwd = row[1] if len(row)>1 else '' proxy = row[2] if len(row)>2 else '' ua = row[5] if len(row)>5 else '' find_and_delete(ws_repair, user) find_and_delete(ws_all, user) # Пропускаем если нет прокси, данных или UA if not user or not pwd or not ua or not proxy: ws_repair.insert_row(row + [str(DAY_NUMBER), 'no_data'], index=len(ws_repair.col_values(1))+1) update_or_append_all(ws_all, row, ua, f'ремонт {DAY_NUMBER}') clear_source_row(ws_day4, idx) logging.error(f"Пропуск {user}: недостаточно данных, UA или proxy отсутствует") fail += 1 continue session_file = os.path.join(SESSIONS_DIR, f"{user}.json") cl, err = try_login(user, pwd, proxy, ua) if cl: video = videos.pop(0) video_path = video ok, pub_err = publish_reel(cl, video_path) if not ok and pub_err and 'CSRF token missing' in pub_err: logging.info(f"CSRF error for {user}, сброс сессии и повторный вход") try: os.remove(session_file) except Exception: pass cl, err = try_login(user, pwd, proxy, ua) if cl: ok, pub_err = publish_reel(cl, video_path) if ok: ws_day5.insert_row(row, index=len(ws_day5.col_values(1))+1) update_or_append_all(ws_all, row, ua, str(NEXT_DAY)) success += 1 else: err = pub_err if not cl or err: ws_repair.insert_row(row + [str(DAY_NUMBER), err], index=len(ws_repair.col_values(1))+1) update_or_append_all(ws_all, row, ua, f'ремонт {DAY_NUMBER}') fail += 1 logging.error(f"Неудача для {user}: {err}") clear_source_row(ws_day4, idx) time.sleep(1) report = f"Day{DAY_NUMBER}->{NEXT_DAY} success: {success}; failed: {fail}" send_telegram(report)