Automation_Threads_EU / threads_api.py
FincH12's picture
Initial commit from local
9963647 verified
Raw
History Blame Contribute Delete
13.2 kB
# -*- coding: utf-8 -*-
import os
import time
import requests
from config import THREADS_ACCESS_TOKEN
THREADS_API_BASE = "https://graph.threads.net/v1.0"
LAST_ERROR = None
def upload_to_tmpfiles(file_path):
"""
Загружает локальный файл на tmpfiles.org и возвращает прямую ссылку на скачивание.
"""
url = "https://tmpfiles.org/api/v1/upload"
try:
print(f"[Threads API] Загрузка файла {os.path.basename(file_path)} на tmpfiles.org...")
with open(file_path, "rb") as f:
files = {"file": f}
r = requests.post(url, files=files, timeout=60)
if r.status_code == 200:
res_data = r.json()
if res_data.get("status") == "success":
view_url = res_data["data"]["url"]
# Конвертируем ссылку на страницу в прямую ссылку на скачивание файла
direct_url = view_url.replace("https://tmpfiles.org/", "https://tmpfiles.org/dl/")
print(f"[Threads API] Файл успешно загружен на tmpfiles.org. Ссылка: {direct_url}")
return direct_url
print(f"[Threads API Error] Ошибка загрузки на tmpfiles.org ({r.status_code}): {r.text}")
return None
except Exception as e:
print(f"[Threads API Exception] Исключение при загрузке на tmpfiles.org: {e}")
return None
def upload_to_catbox(file_path):
"""
Загружает локальный файл на бесплатный хостинг изображений Catbox.moe
и возвращает прямую ссылку.
"""
url = "https://catbox.moe/user/api.php"
payload = {"reqtype": "fileupload"}
try:
print(f"[Threads API] Загрузка файла {os.path.basename(file_path)} на Catbox...")
with open(file_path, "rb") as f:
files = {"fileToUpload": f}
r = requests.post(url, data=payload, files=files, timeout=60)
if r.status_code == 200:
public_url = r.text.strip()
# Проверим, что это валидный URL, а не HTML с ошибкой
if public_url.startswith("http") and "catbox" in public_url:
print(f"[Threads API] Файл успешно загружен. Ссылка: {public_url}")
return public_url
print(f"[Threads API Error] Ошибка загрузки на Catbox ({r.status_code}): {r.text}")
return None
except Exception as e:
print(f"[Threads API Exception] Исключение при загрузке на Catbox: {e}")
return None
def upload_to_uguu(file_path):
"""
Загружает локальный файл на бесплатный хостинг uguu.se
"""
url = "https://uguu.se/upload"
try:
print(f"[Threads API] Загрузка файла {os.path.basename(file_path)} на Uguu.se...")
with open(file_path, "rb") as f:
files = {"files[]": f}
r = requests.post(url, files=files, timeout=60)
if r.status_code == 200:
res_data = r.json()
if res_data.get("success"):
public_url = res_data["files"][0]["url"]
print(f"[Threads API] Файл успешно загружен на Uguu.se. Ссылка: {public_url}")
return public_url
print(f"[Threads API Error] Ошибка загрузки на Uguu.se ({r.status_code}): {r.text}")
return None
except Exception as e:
print(f"[Threads API Exception] Исключение при загрузке на Uguu.se: {e}")
return None
def upload_image(file_path):
"""
Универсальная загрузка. Сначала tmpfiles.org, затем Catbox, затем Uguu.se.
Возвращает URL или None.
"""
url = upload_to_tmpfiles(file_path)
if url:
return url
print("[Threads API Warning] tmpfiles.org сбоит. Переключение на резервный хостинг Catbox...")
url = upload_to_catbox(file_path)
if url:
return url
print("[Threads API Warning] Catbox сбоит. Переключение на Uguu.se...")
return upload_to_uguu(file_path)
def create_media_container(payload):
"""
Создает медиа-контейнер в Threads API.
"""
global LAST_ERROR
url = f"{THREADS_API_BASE}/me/threads"
params = {"access_token": THREADS_ACCESS_TOKEN}
try:
r = requests.post(url, params=params, json=payload, timeout=30)
if r.status_code == 200:
LAST_ERROR = None
container_id = r.json().get("id")
print(f"[Threads API] Создан контейнер: {container_id}")
return container_id
else:
LAST_ERROR = r.text
print(f"[Threads API Error] Ошибка создания контейнера ({r.status_code}): {r.text}")
return None
except Exception as e:
LAST_ERROR = str(e)
print(f"[Threads API Exception] Исключение при создании контейнера: {e}")
return None
def publish_media_container(container_id):
"""
Публикует ранее созданный медиа-контейнер.
"""
global LAST_ERROR
url = f"{THREADS_API_BASE}/me/threads_publish"
params = {"access_token": THREADS_ACCESS_TOKEN}
payload = {"creation_id": container_id}
try:
r = requests.post(url, params=params, json=payload, timeout=30)
if r.status_code == 200:
LAST_ERROR = None
media_id = r.json().get("id")
print(f"[Threads API] Контейнер {container_id} успешно опубликован! Media ID: {media_id}")
return media_id
else:
LAST_ERROR = r.text
print(f"[Threads API Error] Ошибка публикации контейнера ({r.status_code}): {r.text}")
return None
except Exception as e:
LAST_ERROR = str(e)
print(f"[Threads API Exception] Исключение при публикации контейнера: {e}")
return None
def wait_for_container(container_id, max_retries=15, sleep_time=5):
"""
Периодически проверяет статус медиа-контейнера до получения FINISHED.
"""
url = f"{THREADS_API_BASE}/{container_id}"
params = {
"fields": "status,error_message",
"access_token": THREADS_ACCESS_TOKEN
}
for i in range(max_retries):
try:
r = requests.get(url, params=params, timeout=10)
if r.status_code == 200:
data = r.json()
status = data.get("status")
if status == "FINISHED":
print(f"[Threads API] Контейнер {container_id} готов (FINISHED)!")
return True
elif status == "ERROR":
error_msg = data.get("error_message", "Unknown error")
print(f"[Threads API Error] Ошибка обработки контейнера {container_id}: {error_msg}")
return False
else:
print(f"[Threads API] Ожидание контейнера {container_id} (статус: {status})...")
else:
print(f"[Threads API] Ошибка проверки статуса: {r.text}")
except Exception as e:
print(f"[Threads API] Исключение при проверке статуса: {e}")
time.sleep(sleep_time)
print(f"[Threads API Error] Тайм-аут ожидания контейнера {container_id}")
return False
def publish_text_post(text, reply_to_id=None):
"""
Публикует текстовый пост (или комментарий/self-ответ, если передан reply_to_id).
"""
payload = {
"media_type": "TEXT",
"text": text
}
if reply_to_id:
payload["reply_to_id"] = reply_to_id
container_id = create_media_container(payload)
if container_id:
# Небольшая пауза для обработки
time.sleep(2)
return publish_media_container(container_id)
return None
def publish_image_post(image_path, text="", reply_to_id=None):
"""
Публикует одиночный пост с картинкой.
"""
image_url = upload_image(image_path)
if not image_url:
return None
payload = {
"media_type": "IMAGE",
"image_url": image_url
}
if text:
payload["text"] = text
if reply_to_id:
payload["reply_to_id"] = reply_to_id
container_id = create_media_container(payload)
if container_id:
print(f"[Threads API] Ожидание обработки изображения {container_id}...")
if wait_for_container(container_id, max_retries=10):
return publish_media_container(container_id)
else:
print("[Threads API Error] Отмена публикации из-за ошибки обработки.")
return None
def publish_carousel_post(image_paths, text="", reply_to_id=None):
"""
Публикует карусель изображений.
"""
child_ids = []
# 1. Создаем контейнеры для каждого слайда
for path in image_paths:
img_url = upload_image(path)
if not img_url:
print(f"[Threads API Error] Ошибка загрузки слайда {path}. Публикация карусели прервана.")
return None
slide_payload = {
"media_type": "IMAGE",
"image_url": img_url,
"is_carousel_item": True
}
child_id = create_media_container(slide_payload)
if not child_id:
print(f"[Threads API Error] Ошибка создания контейнера для слайда {path}. Публикация карусели прервана.")
return None
child_ids.append(child_id)
if not child_ids:
print("[Threads API Error] Не удалось создать слайды для карусели.")
return None
# 1.5. Ждем окончания обработки всех слайдов-потомков
print(f"[Threads API] Ожидание обработки {len(child_ids)} слайдов-потомков...")
all_ready = True
for child_id in child_ids:
if not wait_for_container(child_id, max_retries=25):
print(f"[Threads API Error] Слайд {child_id} не был успешно обработан. Карусель не будет опубликована.")
all_ready = False
break
if not all_ready:
return None
# 2. Создаем контейнер-родитель для карусели
carousel_payload = {
"media_type": "CAROUSEL",
"children": child_ids
}
if text:
carousel_payload["text"] = text
if reply_to_id:
carousel_payload["reply_to_id"] = reply_to_id
parent_container_id = create_media_container(carousel_payload)
if parent_container_id:
print(f"[Threads API] Ожидание обработки контейнера карусели {parent_container_id}...")
if wait_for_container(parent_container_id, max_retries=20):
return publish_media_container(parent_container_id)
else:
print("[Threads API Error] Карусель не опубликована из-за ошибки обработки.")
return None
def fetch_recent_posts(limit=10):
"""
Получает тексты последних постов пользователя из Threads API.
"""
url = f"{THREADS_API_BASE}/me/threads"
params = {
"fields": "id,text,timestamp,media_type",
"access_token": THREADS_ACCESS_TOKEN,
"limit": limit
}
try:
r = requests.get(url, params=params, timeout=20)
if r.status_code == 200:
data = r.json().get("data", [])
# Извлекаем тексты только основных постов
texts = [item.get("text") for item in data if item.get("text")]
return texts
else:
print(f"[Threads API Error] Ошибка получения последних тредов ({r.status_code}): {r.text}")
return []
except Exception as e:
print(f"[Threads API Exception] Исключение при получении последних тредов: {e}")
return []