habulaj commited on
Commit
48e3487
·
verified ·
1 Parent(s): b5959fd

Update routers/instagram.py

Browse files
Files changed (1) hide show
  1. routers/instagram.py +28 -329
routers/instagram.py CHANGED
@@ -1,346 +1,45 @@
1
- import os
2
  import httpx
3
- import socket
4
- from typing import Dict, Optional, Tuple
5
  from fastapi import APIRouter, HTTPException
6
- from pydantic import BaseModel
7
 
8
  router = APIRouter()
9
 
10
- # 📱 Instagram API Config
11
- INSTAGRAM_API_BASE = "https://graph.instagram.com" # Removido v23.0 para testar
12
- INSTAGRAM_PAGE_ID = "17841464166934843" # Seu Page ID
13
- INSTAGRAM_TOKEN = os.getenv("INSTAGRAM_ACCESS_TOKEN")
14
-
15
- if not INSTAGRAM_TOKEN:
16
- raise ValueError("❌ INSTAGRAM_ACCESS_TOKEN não foi definido nas variáveis de ambiente!")
17
-
18
- # 📝 Modelos de dados
19
- class InstagramPost(BaseModel):
20
- image_url: str
21
- caption: Optional[str] = None
22
-
23
- class PublishResponse(BaseModel):
24
- success: bool
25
- media_id: Optional[str] = None
26
- post_id: Optional[str] = None
27
- post_url: Optional[str] = None
28
- message: str
29
- comment_posted: bool = False
30
- comment_id: Optional[str] = None
31
-
32
- # 🔍 Função para testar conectividade
33
- async def test_connectivity():
34
- """Testa a conectividade básica com a API do Instagram"""
35
- try:
36
- # Teste 1: Resolução DNS
37
- print("🔍 Testando resolução DNS...")
38
- socket.gethostbyname("graph.instagram.com")
39
- print("✅ DNS resolvido com sucesso")
40
-
41
- # Teste 2: Conexão HTTP
42
- print("🔍 Testando conectividade HTTP...")
43
- async with httpx.AsyncClient(timeout=10.0) as client:
44
- response = await client.get("https://graph.instagram.com")
45
- print(f"✅ Conectividade OK - Status: {response.status_code}")
46
-
47
- except socket.gaierror as e:
48
- print(f"❌ Erro de DNS: {e}")
49
- raise HTTPException(status_code=502, detail=f"Erro de resolução DNS: {e}")
50
- except Exception as e:
51
- print(f"❌ Erro de conectividade: {e}")
52
- raise HTTPException(status_code=502, detail=f"Erro de conectividade: {e}")
53
-
54
- def clean_html_tags(text: str) -> str:
55
- """
56
- Remove todas as tags HTML.
57
- Converte <p> para quebras de linha.
58
-
59
- Returns:
60
- str: Texto limpo sem tags HTML
61
- """
62
- if not text:
63
- return ""
64
-
65
- import re
66
-
67
- # Converte <p> para quebras de linha
68
- text = re.sub(r'<p[^>]*>(.*?)</p>', r'\1\n\n', text, flags=re.DOTALL | re.IGNORECASE)
69
-
70
- # Converte <br> e <br/> para quebra de linha simples
71
- text = re.sub(r'<br\s*/?>', '\n', text, flags=re.IGNORECASE)
72
-
73
- # Remove todas as tags HTML
74
- text = re.sub(r'<[^>]*>', '', text, flags=re.IGNORECASE)
75
-
76
- # Remove quebras de linha excessivas e espaços extras
77
- text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text) # Máximo de 2 quebras consecutivas
78
- text = re.sub(r'[ \t]+', ' ', text) # Remove espaços/tabs extras
79
- text = text.strip()
80
-
81
- return text
82
-
83
- def format_text_for_instagram(text: str) -> Tuple[str, Optional[str]]:
84
  """
85
- Formata o texto para o Instagram:
86
- 1. Limpa tags HTML
87
- 2. Corta se necessário e retorna o texto principal e o resto para comentário
88
-
89
- Returns:
90
- Tuple[str, Optional[str]]: (texto_principal, resto_para_comentario)
91
  """
92
- if not text:
93
- return "", None
94
-
95
- # 🧹 Limpa as tags HTML
96
- text = clean_html_tags(text)
97
-
98
- max_length = 2200
99
- suffix = '\n\n💬 Continua nos comentários!'
100
-
101
- if len(text) <= max_length:
102
- return text, None
103
-
104
- cutoff_length = max_length - len(suffix)
105
- if cutoff_length <= 0:
106
- return suffix.strip(), text
107
 
108
- trimmed = text[:cutoff_length]
 
109
 
110
- def is_inside_quotes(s: str, index: int) -> bool:
111
- """Verifica se há aspas abertas não fechadas até o índice"""
112
- up_to_index = s[:index + 1]
113
- quote_count = up_to_index.count('"')
114
- return quote_count % 2 != 0
 
115
 
116
- # Encontra o último ponto final fora de aspas
117
- last_valid_dot = -1
118
- for i in range(len(trimmed) - 1, -1, -1):
119
- if trimmed[i] == '.' and not is_inside_quotes(trimmed, i):
120
- last_valid_dot = i
121
- break
122
-
123
- if last_valid_dot > 100:
124
- main_text = trimmed[:last_valid_dot + 1]
125
- remaining_text = text[last_valid_dot + 1:].strip()
126
- else:
127
- main_text = trimmed
128
- remaining_text = text[cutoff_length:].strip()
129
-
130
- final_main_text = f"{main_text}{suffix}"
131
-
132
- return final_main_text, remaining_text if remaining_text else None
133
-
134
- async def post_comment(client: httpx.AsyncClient, post_id: str, comment_text: str) -> Optional[str]:
135
- """
136
- Posta um comentário no post do Instagram
137
-
138
- Returns:
139
- Optional[str]: ID do comentário se postado com sucesso
140
- """
141
  try:
142
- comment_url = f"{INSTAGRAM_API_BASE}/{post_id}/comments"
143
- headers = {
144
- "Content-Type": "application/json"
145
- }
146
-
147
- comment_payload = {
148
- "message": comment_text,
149
- "access_token": INSTAGRAM_TOKEN
150
- }
151
-
152
- print(f"💬 Postando comentário no post {post_id}")
153
- comment_response = await client.post(
154
- comment_url,
155
- headers=headers,
156
- json=comment_payload
157
- )
158
-
159
- if comment_response.status_code == 200:
160
- comment_data = comment_response.json()
161
- comment_id = comment_data.get("id")
162
- print(f"✅ Comentário postado com ID: {comment_id}")
163
- return comment_id
164
- else:
165
- error_detail = comment_response.text
166
- print(f"⚠️ Erro ao postar comentário: {error_detail}")
167
- return None
168
 
169
- except Exception as e:
170
- print(f"⚠️ Erro inesperado ao postar comentário: {str(e)}")
171
- return None
172
-
173
- # 🚀 Endpoint principal para publicar no Instagram
174
- @router.post("/publish", response_model=PublishResponse)
175
- async def publish_instagram_post(post: InstagramPost) -> PublishResponse:
176
- """
177
- Publica uma imagem no Instagram em duas etapas:
178
- 1. Cria o media container
179
- 2. Publica o post
180
- 3. Se necessário, posta o resto do texto como comentário
181
- """
182
-
183
- # 🔍 Testa conectividade antes de prosseguir
184
- await test_connectivity()
185
-
186
- # Configuração do cliente HTTP com mais opções
187
- client_config = httpx.AsyncClient(
188
- timeout=httpx.Timeout(30.0, connect=10.0),
189
- follow_redirects=True,
190
- verify=True, # Verifica certificados SSL
191
- limits=httpx.Limits(max_connections=10, max_keepalive_connections=5)
192
- )
193
-
194
- async with client_config as client:
195
- try:
196
- # 📝 Processa o texto da caption
197
- main_caption, remaining_text = format_text_for_instagram(post.caption) if post.caption else ("", None)
198
-
199
- # 🎯 ETAPA 1: Criar o media container
200
- # Usando parâmetros de query ao invés de JSON body (como no curl que funcionou)
201
- media_params = {
202
- "image_url": post.image_url,
203
- "access_token": INSTAGRAM_TOKEN
204
- }
205
-
206
- # Adiciona caption processada se fornecida
207
- if main_caption:
208
- media_params["caption"] = main_caption
209
-
210
- media_url = f"{INSTAGRAM_API_BASE}/{INSTAGRAM_PAGE_ID}/media"
211
-
212
- print(f"📤 Criando media container para: {post.image_url}")
213
- print(f"🔗 URL: {media_url}")
214
- if remaining_text:
215
- print(f"✂️ Texto cortado - será postado comentário com {len(remaining_text)} caracteres")
216
-
217
- # Usando POST com parâmetros de query (igual ao curl)
218
- media_response = await client.post(
219
- media_url,
220
- params=media_params # Mudança aqui: usando params ao invés de json
221
- )
222
-
223
- print(f"📊 Status da resposta: {media_response.status_code}")
224
- print(f"📊 Headers da resposta: {dict(media_response.headers)}")
225
-
226
- if media_response.status_code != 200:
227
- error_detail = media_response.text
228
- print(f"❌ Erro ao criar media container: {error_detail}")
229
- raise HTTPException(
230
- status_code=media_response.status_code,
231
- detail=f"Erro ao criar media container: {error_detail}"
232
- )
233
 
234
- media_data = media_response.json()
235
- media_id = media_data.get("id")
236
 
237
- if not media_id:
238
- raise HTTPException(
239
- status_code=500,
240
- detail="ID do media container não retornado"
241
- )
242
-
243
- print(f"✅ Media container criado com ID: {media_id}")
244
-
245
- # 🎯 ETAPA 2: Publicar o post
246
- publish_params = {
247
- "creation_id": media_id,
248
- "access_token": INSTAGRAM_TOKEN
249
  }
250
 
251
- publish_url = f"{INSTAGRAM_API_BASE}/{INSTAGRAM_PAGE_ID}/media_publish"
252
-
253
- print(f"📤 Publicando post com creation_id: {media_id}")
254
- publish_response = await client.post(
255
- publish_url,
256
- params=publish_params # Usando params também aqui
257
- )
258
-
259
- if publish_response.status_code != 200:
260
- error_detail = publish_response.text
261
- print(f"❌ Erro ao publicar post: {error_detail}")
262
- raise HTTPException(
263
- status_code=publish_response.status_code,
264
- detail=f"Erro ao publicar post: {error_detail}"
265
- )
266
-
267
- publish_data = publish_response.json()
268
- post_id = publish_data.get("id")
269
-
270
- # 🔗 ETAPA 3: Obter detalhes do post para construir URL
271
- post_url = None
272
- if post_id:
273
- try:
274
- # Query para obter o permalink do post
275
- post_details_params = {
276
- "fields": "permalink",
277
- "access_token": INSTAGRAM_TOKEN
278
- }
279
- post_details_url = f"{INSTAGRAM_API_BASE}/{post_id}"
280
- details_response = await client.get(post_details_url, params=post_details_params)
281
-
282
- if details_response.status_code == 200:
283
- details_data = details_response.json()
284
- post_url = details_data.get("permalink")
285
- print(f"🔗 Link do post: {post_url}")
286
- else:
287
- print(f"⚠️ Não foi possível obter o link do post: {details_response.text}")
288
- except Exception as e:
289
- print(f"⚠️ Erro ao obter link do post: {str(e)}")
290
-
291
- # 💬 ETAPA 4: Postar comentário com o resto do texto (se necessário)
292
- comment_posted = False
293
- comment_id = None
294
-
295
- if remaining_text and post_id:
296
- comment_id = await post_comment(client, post_id, remaining_text)
297
- comment_posted = comment_id is not None
298
-
299
- success_message = "Post publicado com sucesso no Instagram!"
300
- if comment_posted:
301
- success_message += " Texto adicional postado como comentário."
302
- elif remaining_text and not comment_posted:
303
- success_message += " ATENÇÃO: Não foi possível postar o comentário com o resto do texto."
304
-
305
- print(f"🎉 {success_message} Post ID: {post_id}")
306
-
307
- return PublishResponse(
308
- success=True,
309
- media_id=media_id,
310
- post_id=post_id,
311
- post_url=post_url,
312
- message=success_message,
313
- comment_posted=comment_posted,
314
- comment_id=comment_id
315
- )
316
-
317
- except httpx.TimeoutException:
318
- print("⏰ Timeout na requisição para Instagram API")
319
- raise HTTPException(
320
- status_code=408,
321
- detail="Timeout na comunicação com a API do Instagram"
322
- )
323
- except httpx.RequestError as e:
324
- print(f"🌐 Erro de conexão: {str(e)}")
325
- raise HTTPException(
326
- status_code=502,
327
- detail=f"Erro de conexão: {str(e)}"
328
- )
329
- except Exception as e:
330
- print(f"💥 Erro inesperado: {str(e)}")
331
- raise HTTPException(
332
- status_code=500,
333
- detail=f"Erro interno do servidor: {str(e)}"
334
- )
335
-
336
- # 🔍 Endpoint para testar conectividade
337
- @router.get("/test-connection")
338
- async def test_connection():
339
- """Endpoint para testar a conectividade com a API do Instagram"""
340
- try:
341
- await test_connectivity()
342
- return {"status": "success", "message": "Conectividade OK"}
343
- except HTTPException as e:
344
- raise e
345
  except Exception as e:
346
- raise HTTPException(status_code=500, detail=f"Erro no teste: {str(e)}")
 
 
 
 
 
 
1
  import httpx
 
 
2
  from fastapi import APIRouter, HTTPException
 
3
 
4
  router = APIRouter()
5
 
6
+ @router.get("/get")
7
+ async def test_instagram_api():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  """
9
+ Executa exatamente o mesmo cURL que funcionou no playground
 
 
 
 
 
10
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ # URL exata do cURL que funcionou
13
+ url = "https://graph.instagram.com/17841464166934843/media"
14
 
15
+ # Parâmetros exatos do cURL
16
+ params = {
17
+ "domain": "INSTAGRAM",
18
+ "image_url": "https://static01.nyt.com/images/2025/08/06/multimedia/06xp-mack-vkjp/06xp-mack-vkjp-mobileMasterAt3x.jpg",
19
+ "access_token": "IGAAPf2LpaWcxBZAE1xTUdBeGFFTWFZAVTFMOHdBc21pTlJULXVaWVBXZAUNfZAUFKS1hMeENUV1FFRmpURjUxdnhZAeDN1cmVJLVFJeTZAuclFKVVRWNmltTHV1TE9pdXVWTVpzbFNqbGxVb1U1X3d0a3ozNDJWeFhvbHlZAckR0OXRlawZDZD"
20
+ }
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  try:
23
+ async with httpx.AsyncClient(timeout=30.0) as client:
24
+ print(f"🚀 Fazendo POST para: {url}")
25
+ print(f"📋 Parâmetros: {params}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ # Faz a requisição POST exatamente como o cURL
28
+ response = await client.post(url, params=params)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ print(f"📊 Status Code: {response.status_code}")
31
+ print(f"📄 Response: {response.text}")
32
 
33
+ # Retorna a resposta completa
34
+ return {
35
+ "status_code": response.status_code,
36
+ "response_text": response.text,
37
+ "success": response.status_code == 200
 
 
 
 
 
 
 
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  except Exception as e:
41
+ print(f"Erro: {str(e)}")
42
+ raise HTTPException(
43
+ status_code=500,
44
+ detail=f"Erro: {str(e)}"
45
+ )