@danhachuel/thunderbolt 0.2.66 → 0.2.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANUAL-INSTALACAO.md +3 -3
- package/README.md +5 -3
- package/app/main.py +441 -219
- package/hermes_ui/storage.py +1 -0
- package/integrations/tiktok_public.py +229 -0
- package/package.json +1 -1
- package/seed/references/guide-instagram.md +179 -0
package/hermes_ui/storage.py
CHANGED
|
@@ -208,6 +208,7 @@ DEFAULTS: dict[str, Any] = {
|
|
|
208
208
|
"postiz_auto_publish": False,
|
|
209
209
|
"tiktok_client_key": "",
|
|
210
210
|
"tiktok_client_secret": "",
|
|
211
|
+
"tiktok_accounts": [],
|
|
211
212
|
"tiktok_redirect_uri": "http://localhost:3030/oauth/tiktok/callback",
|
|
212
213
|
"tiktok_scopes": "user.info.basic,video.publish,video.upload",
|
|
213
214
|
"tiktok_access_token": "",
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from html import unescape
|
|
5
|
+
from hashlib import sha256
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from integrations.platforms import IntegrationResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TIKTOK_PUBLIC_HOSTS = {"tiktok.com", "www.tiktok.com", "m.tiktok.com"}
|
|
15
|
+
TIKTOK_PUBLIC_URL = "https://www.tiktok.com"
|
|
16
|
+
PUBLIC_USER_AGENT = "Thunderbolt/0.2 TikTok public profile lookup; manual user initiated request"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _now_iso() -> str:
|
|
20
|
+
return datetime.now(timezone.utc).isoformat()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _text(value: Any) -> str:
|
|
24
|
+
if isinstance(value, str):
|
|
25
|
+
return value.strip()
|
|
26
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
27
|
+
return str(value)
|
|
28
|
+
if isinstance(value, dict):
|
|
29
|
+
for key in ("text", "content", "simpleText", "name", "title", "value"):
|
|
30
|
+
text = _text(value.get(key))
|
|
31
|
+
if text:
|
|
32
|
+
return text
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _first(value: Any, keys: set[str]) -> Any:
|
|
37
|
+
if isinstance(value, dict):
|
|
38
|
+
for key, child in value.items():
|
|
39
|
+
if key in keys and child not in (None, "", [], {}):
|
|
40
|
+
return child
|
|
41
|
+
for child in value.values():
|
|
42
|
+
found = _first(child, keys)
|
|
43
|
+
if found is not None:
|
|
44
|
+
return found
|
|
45
|
+
elif isinstance(value, list):
|
|
46
|
+
for child in value:
|
|
47
|
+
found = _first(child, keys)
|
|
48
|
+
if found is not None:
|
|
49
|
+
return found
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _number(value: Any) -> int | None:
|
|
54
|
+
if isinstance(value, bool) or value is None:
|
|
55
|
+
return None
|
|
56
|
+
if isinstance(value, (int, float)):
|
|
57
|
+
return int(value)
|
|
58
|
+
text = _text(value).lower().replace(" ", "")
|
|
59
|
+
match = re.search(r"([0-9][0-9.,]*)(k|m|b|mil|milhões|mi|bi)?", text)
|
|
60
|
+
if not match:
|
|
61
|
+
return None
|
|
62
|
+
raw = match.group(1)
|
|
63
|
+
suffix = match.group(2) or ""
|
|
64
|
+
try:
|
|
65
|
+
if suffix in {"k", "mil"}:
|
|
66
|
+
return int(float(raw.replace(",", ".")) * 1_000)
|
|
67
|
+
if suffix in {"m", "mi", "milhões"}:
|
|
68
|
+
return int(float(raw.replace(",", ".")) * 1_000_000)
|
|
69
|
+
if suffix in {"b", "bi"}:
|
|
70
|
+
return int(float(raw.replace(",", ".")) * 1_000_000_000)
|
|
71
|
+
return int(re.sub(r"[^0-9]", "", raw))
|
|
72
|
+
except ValueError:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _meta(document: str, *names: str) -> str:
|
|
77
|
+
for name in names:
|
|
78
|
+
patterns = (
|
|
79
|
+
rf'<meta[^>]+(?:name|property)=["\']{re.escape(name)}["\'][^>]+content=["\']([^"\']*)["\']',
|
|
80
|
+
rf'<meta[^>]+content=["\']([^"\']*)["\'][^>]+(?:name|property)=["\']{re.escape(name)}["\']',
|
|
81
|
+
)
|
|
82
|
+
for pattern in patterns:
|
|
83
|
+
match = re.search(pattern, document, flags=re.IGNORECASE)
|
|
84
|
+
if match:
|
|
85
|
+
return unescape(match.group(1)).strip()
|
|
86
|
+
return ""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _json_scripts(document: str) -> list[Any]:
|
|
90
|
+
values: list[Any] = []
|
|
91
|
+
for match in re.finditer(r"<script[^>]*>(.*?)</script>", document, flags=re.IGNORECASE | re.DOTALL):
|
|
92
|
+
body = match.group(1).strip()
|
|
93
|
+
if not body or not (body.startswith("{") or body.startswith("[")):
|
|
94
|
+
continue
|
|
95
|
+
try:
|
|
96
|
+
values.append(json.loads(unescape(body)))
|
|
97
|
+
except (TypeError, ValueError, json.JSONDecodeError):
|
|
98
|
+
continue
|
|
99
|
+
return values
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _username_from_reference(source: str) -> str:
|
|
103
|
+
value = str(source or "").strip()
|
|
104
|
+
if not value:
|
|
105
|
+
return ""
|
|
106
|
+
if not value.startswith(("http://", "https://")):
|
|
107
|
+
value = value if value.startswith("@") else f"@{value}"
|
|
108
|
+
return value[1:].strip("/ ")
|
|
109
|
+
parsed = urlparse(value)
|
|
110
|
+
for part in parsed.path.split("/"):
|
|
111
|
+
if part.startswith("@") and len(part) > 1:
|
|
112
|
+
return part[1:].strip()
|
|
113
|
+
return ""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def normalize_tiktok_reference(source: str) -> dict[str, str]:
|
|
117
|
+
value = str(source or "").strip()
|
|
118
|
+
if not value:
|
|
119
|
+
raise ValueError("Informe um @handle ou URL pública do TikTok.")
|
|
120
|
+
if value.startswith("@"):
|
|
121
|
+
username = value[1:].strip()
|
|
122
|
+
elif value.startswith(("http://", "https://")):
|
|
123
|
+
parsed = urlparse(value)
|
|
124
|
+
if parsed.netloc.lower().split(":", 1)[0] not in TIKTOK_PUBLIC_HOSTS:
|
|
125
|
+
raise ValueError("Use uma URL pública do TikTok, por exemplo https://www.tiktok.com/@conta.")
|
|
126
|
+
username = _username_from_reference(value)
|
|
127
|
+
else:
|
|
128
|
+
username = value
|
|
129
|
+
username = username.strip().lstrip("@").split("/", 1)[0]
|
|
130
|
+
if not re.fullmatch(r"[A-Za-z0-9._-]{2,64}", username):
|
|
131
|
+
raise ValueError("O @handle TikTok deve conter apenas letras, números, ponto, sublinhado ou hífen.")
|
|
132
|
+
handle = f"@{username}"
|
|
133
|
+
url = f"{TIKTOK_PUBLIC_URL}/{handle}"
|
|
134
|
+
return {
|
|
135
|
+
"id": f"tiktok_{sha256(url.encode('utf-8')).hexdigest()[:20]}",
|
|
136
|
+
"username": username,
|
|
137
|
+
"handle": handle,
|
|
138
|
+
"url": url,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _canonical_profile_data(source: str, document: str) -> dict[str, Any]:
|
|
143
|
+
reference = normalize_tiktok_reference(source)
|
|
144
|
+
title = _meta(document, "og:title", "twitter:title")
|
|
145
|
+
description = _meta(document, "og:description", "description", "twitter:description")
|
|
146
|
+
avatar_url = _meta(document, "og:image", "twitter:image")
|
|
147
|
+
canonical_url = _meta(document, "og:url") or reference["url"]
|
|
148
|
+
scripts = _json_scripts(document)
|
|
149
|
+
for payload in scripts:
|
|
150
|
+
candidate = payload
|
|
151
|
+
if isinstance(payload, dict) and payload.get("@type") in {"Person", "ProfilePage"}:
|
|
152
|
+
display_name = _text(payload.get("name"))
|
|
153
|
+
description = description or _text(payload.get("description"))
|
|
154
|
+
avatar_url = avatar_url or _text(payload.get("image"))
|
|
155
|
+
canonical_url = _text(payload.get("url")) or canonical_url
|
|
156
|
+
if display_name:
|
|
157
|
+
title = display_name
|
|
158
|
+
username = _first(candidate, {"uniqueId", "unique_id", "username"})
|
|
159
|
+
display_name = _first(candidate, {"nickname", "displayName", "display_name"})
|
|
160
|
+
bio = _first(candidate, {"signature", "bio", "bioDescription", "bio_description"})
|
|
161
|
+
avatar = _first(candidate, {"avatarLarger", "avatarMedium", "avatar_url", "avatarUrl"})
|
|
162
|
+
if username:
|
|
163
|
+
reference["username"] = _text(username).lstrip("@").strip() or reference["username"]
|
|
164
|
+
reference["handle"] = f"@{reference['username']}"
|
|
165
|
+
reference["url"] = f"{TIKTOK_PUBLIC_URL}/{reference['handle']}"
|
|
166
|
+
if display_name and not title:
|
|
167
|
+
title = _text(display_name)
|
|
168
|
+
if bio and not description:
|
|
169
|
+
description = _text(bio)
|
|
170
|
+
if avatar and not avatar_url:
|
|
171
|
+
avatar_url = _text(avatar)
|
|
172
|
+
|
|
173
|
+
title = re.sub(r"\s*[|·—-]\s*TikTok\s*$", "", title, flags=re.IGNORECASE).strip()
|
|
174
|
+
if title and "(" in title:
|
|
175
|
+
title = title.split("(", 1)[0].strip()
|
|
176
|
+
if title.startswith("@"):
|
|
177
|
+
title = ""
|
|
178
|
+
follower_count = None
|
|
179
|
+
following_count = None
|
|
180
|
+
likes_count = None
|
|
181
|
+
video_count = None
|
|
182
|
+
for payload in scripts:
|
|
183
|
+
follower_count = follower_count or _number(_first(payload, {"followerCount", "followers", "follower_count"}))
|
|
184
|
+
following_count = following_count or _number(_first(payload, {"followingCount", "following", "following_count"}))
|
|
185
|
+
likes_count = likes_count or _number(_first(payload, {"heartCount", "likes", "likeCount", "likes_count"}))
|
|
186
|
+
video_count = video_count or _number(_first(payload, {"videoCount", "video_count"}))
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
**reference,
|
|
190
|
+
"name": title or reference["username"],
|
|
191
|
+
"bio": description,
|
|
192
|
+
"avatar_url": avatar_url,
|
|
193
|
+
"subscriber_count": follower_count,
|
|
194
|
+
"following_count": following_count,
|
|
195
|
+
"likes_count": likes_count,
|
|
196
|
+
"video_count": video_count,
|
|
197
|
+
"public_url": canonical_url if "tiktok.com" in canonical_url else reference["url"],
|
|
198
|
+
"public_lookup": True,
|
|
199
|
+
"metrics_source": "tiktok_public_page",
|
|
200
|
+
"last_public_lookup_at": _now_iso(),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def fetch_public_tiktok_profile(source: str) -> IntegrationResult:
|
|
205
|
+
try:
|
|
206
|
+
reference = normalize_tiktok_reference(source)
|
|
207
|
+
except ValueError as exc:
|
|
208
|
+
return IntegrationResult(False, str(exc), {})
|
|
209
|
+
headers = {
|
|
210
|
+
"User-Agent": PUBLIC_USER_AGENT,
|
|
211
|
+
"Accept-Language": "pt-BR,pt;q=0.9,en;q=0.8",
|
|
212
|
+
"Accept": "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8",
|
|
213
|
+
}
|
|
214
|
+
try:
|
|
215
|
+
response = requests.get(reference["url"], headers=headers, timeout=12, allow_redirects=True)
|
|
216
|
+
except requests.RequestException as exc:
|
|
217
|
+
return IntegrationResult(False, f"Não foi possível consultar o perfil público do TikTok: {exc}", reference)
|
|
218
|
+
if response.status_code in {401, 403, 429}:
|
|
219
|
+
return IntegrationResult(False, "O TikTok bloqueou ou limitou a pesquisa pública. Use o cadastro manual com o @handle e a URL.", reference | {"status_code": response.status_code})
|
|
220
|
+
if response.status_code >= 400:
|
|
221
|
+
return IntegrationResult(False, f"O perfil público do TikTok devolveu HTTP {response.status_code}. Confirme o @handle ou use o cadastro manual.", reference | {"status_code": response.status_code})
|
|
222
|
+
data = _canonical_profile_data(source, response.text)
|
|
223
|
+
recognized = bool(data.get("name") or data.get("bio") or data.get("avatar_url") or any(data.get(key) is not None for key in ("subscriber_count", "video_count", "likes_count")))
|
|
224
|
+
if not recognized:
|
|
225
|
+
return IntegrationResult(False, "A página pública não expôs dados estruturados reconhecíveis. Pode cadastrar a conta manualmente.", data)
|
|
226
|
+
return IntegrationResult(True, "Perfil TikTok encontrado publicamente. Reveja os dados antes de cadastrar.", data)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
__all__ = ["PUBLIC_USER_AGENT", "fetch_public_tiktok_profile", "normalize_tiktok_reference"]
|
package/package.json
CHANGED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Setting up Instagram for automations with n8n
|
|
2
|
+
|
|
3
|
+
## [📚 Join our Skool community for support, premium content and more!](https://www.skool.com/ai-agents-az/about?gw8)
|
|
4
|
+
|
|
5
|
+
### Be part of a growing community and help us create more content like this
|
|
6
|
+
|
|
7
|
+
## Convert the Instagram account to a professional account
|
|
8
|
+
|
|
9
|
+
> [!IMPORTANT]
|
|
10
|
+
> Converting the Instagram account to a professional (business or creator) account will make the account public
|
|
11
|
+
|
|
12
|
+
In the Instagram application, follow these steps to convert your account to a professional account.
|
|
13
|
+
|
|
14
|
+
<table>
|
|
15
|
+
<tr>
|
|
16
|
+
<td>
|
|
17
|
+
1. Open the settings, and select `Account type and tools`
|
|
18
|
+
<img src="https://github.com/user-attachments/assets/adffda31-f8ce-4a49-a971-40e5cd5f5395" alt="" />
|
|
19
|
+
</td>
|
|
20
|
+
<td>
|
|
21
|
+
2. Click on `Switch to professinal account`
|
|
22
|
+
<img src="https://github.com/user-attachments/assets/5ea13aeb-b6ac-40b4-906f-2fc563625c52" alt="" />
|
|
23
|
+
</td>
|
|
24
|
+
<td>
|
|
25
|
+
3. Click on `Next`
|
|
26
|
+
<img src="https://github.com/user-attachments/assets/10942cc8-6045-4ca1-a14a-241a2d94591c" />
|
|
27
|
+
</td>
|
|
28
|
+
</tr>
|
|
29
|
+
<tr>
|
|
30
|
+
<td>
|
|
31
|
+
4. Select the type of account your want to create
|
|
32
|
+
<img src="https://github.com/user-attachments/assets/c9e19bf8-b48d-49e7-b560-e8edf423233d" alt="" />
|
|
33
|
+
</td>
|
|
34
|
+
<td>
|
|
35
|
+
5. Select the right category, that describe your account
|
|
36
|
+
<img src="https://github.com/user-attachments/assets/eb4680b2-1d38-476c-8fb0-c428d1e12db9" alt="" />
|
|
37
|
+
</td>
|
|
38
|
+
<td>
|
|
39
|
+
6. Confirm the choice
|
|
40
|
+
<img src="https://github.com/user-attachments/assets/22ee13cf-f751-4cea-9fc0-be466c885b02" alt="" />
|
|
41
|
+
</td>
|
|
42
|
+
</tr>
|
|
43
|
+
</table>
|
|
44
|
+
|
|
45
|
+
## Create credentials to use with n8n
|
|
46
|
+
|
|
47
|
+
### 1. Create a Facebook page
|
|
48
|
+
|
|
49
|
+
Navigate to [https://www.facebook.com/pages/create](https://www.facebook.com/pages/create) to create a new Facebook page.
|
|
50
|
+
Fill out the required parameters (name, category) and create the page.
|
|
51
|
+
|
|
52
|
+
<img width="1554" alt="Screenshot 2025-05-05 at 10 02 00 AM" src="https://github.com/user-attachments/assets/f0e321a2-7b85-4b23-b85b-f8e63281b6b9" />
|
|
53
|
+
|
|
54
|
+
Now, you need to be acting on behalf of the page - if it's not active for some reason, select it from the menu.
|
|
55
|
+
|
|
56
|
+
<img width="372" alt="image" src="https://github.com/user-attachments/assets/7aaaccc8-4b0a-4f72-a8c9-f2ecc453584a" />
|
|
57
|
+
|
|
58
|
+
### 2. Connect Instagram account to the Facebook page
|
|
59
|
+
|
|
60
|
+
Click on your page's name on the left side.
|
|
61
|
+
|
|
62
|
+
<img width="379" alt="image" src="https://github.com/user-attachments/assets/fd1ed1d8-8890-4636-a4ae-0496db252283" />
|
|
63
|
+
|
|
64
|
+
Select `Settings` from the menu.
|
|
65
|
+
|
|
66
|
+
<img width="368" alt="image" src="https://github.com/user-attachments/assets/641904ec-764e-4d56-bede-618da33db7fe" />
|
|
67
|
+
|
|
68
|
+
Scroll down to `Permissions` and select `Linked accounts`.
|
|
69
|
+
|
|
70
|
+
<img width="369" alt="image" src="https://github.com/user-attachments/assets/a024ad7e-8358-4329-a864-c7b621810405" />
|
|
71
|
+
|
|
72
|
+
Select `Instagram`.
|
|
73
|
+
|
|
74
|
+
<img width="740" alt="image" src="https://github.com/user-attachments/assets/141164a0-c88e-46a0-a335-466f81297bc6" />
|
|
75
|
+
|
|
76
|
+
Click on `Connect account` and sign in with your Instagram account.
|
|
77
|
+
|
|
78
|
+
<img width="708" alt="image" src="https://github.com/user-attachments/assets/5324b945-621b-4f2b-b513-9b51e5b5c071" />
|
|
79
|
+
|
|
80
|
+
Click on `Connect`.
|
|
81
|
+
|
|
82
|
+
<img width="573" alt="image" src="https://github.com/user-attachments/assets/85cad094-a815-441a-a0e5-84081fd0ee2d" />
|
|
83
|
+
|
|
84
|
+
Click on `Confirm`.
|
|
85
|
+
|
|
86
|
+
<img width="570" alt="image" src="https://github.com/user-attachments/assets/df5bb377-22a8-41de-b99d-0f80052cb372" />
|
|
87
|
+
|
|
88
|
+
Click on `Continue`.
|
|
89
|
+
|
|
90
|
+
<img width="561" alt="image" src="https://github.com/user-attachments/assets/81fd3143-f00b-4e33-9521-12c0c9ce623d" />
|
|
91
|
+
|
|
92
|
+
You are done.
|
|
93
|
+
|
|
94
|
+
<img width="713" alt="image" src="https://github.com/user-attachments/assets/ce4c0bbe-beed-4c6c-8731-23216efbccb5" />
|
|
95
|
+
|
|
96
|
+
### 3. Create a Facebook application
|
|
97
|
+
|
|
98
|
+
Head to [https://developers.facebook.com/apps](https://developers.facebook.com/apps) and click on `Create app`
|
|
99
|
+
|
|
100
|
+
<img width="1027" alt="image" src="https://github.com/user-attachments/assets/ca5c79b9-974a-4c55-8f88-6def8c1b98d1" />
|
|
101
|
+
|
|
102
|
+
Select the `Other` use case and hit `Next`.
|
|
103
|
+
|
|
104
|
+
<img width="1033" alt="image" src="https://github.com/user-attachments/assets/31f296bd-3b47-432b-9037-7974e821a311" />
|
|
105
|
+
|
|
106
|
+
Select `Business` app type.
|
|
107
|
+
|
|
108
|
+
<img width="818" alt="image" src="https://github.com/user-attachments/assets/2ab06dd1-33ba-4d87-8365-3637c8327877" />
|
|
109
|
+
|
|
110
|
+
Review, and click on `Create app`
|
|
111
|
+
|
|
112
|
+
<img width="815" alt="image" src="https://github.com/user-attachments/assets/c8cc8e1d-c006-428a-9bfa-8cc78a2932c9" />
|
|
113
|
+
|
|
114
|
+
Add the Instagram product to your app.
|
|
115
|
+
|
|
116
|
+
<img width="1008" alt="image" src="https://github.com/user-attachments/assets/4186f90b-b619-4c1c-9ea8-bfc5294eaeb4" />
|
|
117
|
+
|
|
118
|
+
You don't need to configure it, just leave it as it is.
|
|
119
|
+
|
|
120
|
+
### 4. Create an access token
|
|
121
|
+
|
|
122
|
+
From the tools menu, select the `Graph API Explorer`
|
|
123
|
+
|
|
124
|
+
<img width="877" alt="image" src="https://github.com/user-attachments/assets/006a40a7-894a-4bd0-9483-0d495b77206f" />
|
|
125
|
+
|
|
126
|
+
You'll see this interface below.
|
|
127
|
+
|
|
128
|
+
<img width="541" alt="image" src="https://github.com/user-attachments/assets/414ece47-2f1d-4d08-9998-ada85ffb065f" />
|
|
129
|
+
|
|
130
|
+
1. Make sure the Meta app is the one you just created
|
|
131
|
+
2. In the permissions panel, add the following permissions
|
|
132
|
+
|
|
133
|
+
- `pages_show_list`
|
|
134
|
+
- `business_management`
|
|
135
|
+
- `instagram_basic`
|
|
136
|
+
- `instagram_content_publish`
|
|
137
|
+
- `pages_read_engagement`
|
|
138
|
+
|
|
139
|
+
3. Click on `Generate Access Token`
|
|
140
|
+
|
|
141
|
+
4. You'll be prompted to select the Facebook page you give access to, the business account and the Instagram account. Select all of them. During the process, take a not of the **Instagram account id** you are connecting, you'll need that later.
|
|
142
|
+
|
|
143
|
+
<table>
|
|
144
|
+
<tr>
|
|
145
|
+
<td>
|
|
146
|
+
<img width="564" alt="Screenshot 2025-05-09 at 11 07 22 AM" src="https://github.com/user-attachments/assets/6b77415e-1ca2-47f0-b046-974fd391c90a" />
|
|
147
|
+
</td>
|
|
148
|
+
<td>
|
|
149
|
+
<img width="562" alt="Screenshot 2025-05-09 at 11 07 27 AM" src="https://github.com/user-attachments/assets/353b9973-426b-4034-b5fc-e77840ca0d18" />
|
|
150
|
+
</td>
|
|
151
|
+
<td>
|
|
152
|
+
<img width="562" alt="Screenshot 2025-05-09 at 11 08 35 AM" src="https://github.com/user-attachments/assets/58132694-1376-4c40-9d20-61cb8fdb9df5" />
|
|
153
|
+
</td>
|
|
154
|
+
<td>
|
|
155
|
+
<img width="562" alt="Screenshot 2025-05-09 at 11 08 58 AM" src="https://github.com/user-attachments/assets/c2676755-f12f-49d6-9a92-49ea688fc832" />
|
|
156
|
+
</td>
|
|
157
|
+
</tr>
|
|
158
|
+
</table>
|
|
159
|
+
|
|
160
|
+
5. Copy the access token, and head to the [Facebook token debugger tool](https://developers.facebook.com/tools/debug/accesstoken) and paste in the access token, and click `debug`. Scroll to the very bottom of the page, and click on `Extend Access Token`. Copy the long-lived access token.
|
|
161
|
+
|
|
162
|
+
6. Go back to the `Graph API Explorer` and paste the access token to the access token field and add `me/accounts` to the graph path and click `Submit`. In the results, you'll find field `access_token` that contains the long-lived page access token that will never expire. Copy that.
|
|
163
|
+
|
|
164
|
+
<img width="693" alt="image" src="https://github.com/user-attachments/assets/0e1bd821-5c27-4c9a-b14e-eeed9c17850f" />
|
|
165
|
+
|
|
166
|
+
7. (optional) you can verify the expiration of the token, if you paste it to the [Facebook token debugger tool](https://developers.facebook.com/tools/debug/accesstoken).
|
|
167
|
+
|
|
168
|
+
8. (optional) you can also get the Instagram Business Account's id, if you click on the id of the page in the Graph API explorer, and add `?fields=instagram_business_account` after the id, then click `Submit`
|
|
169
|
+
|
|
170
|
+
<img width="766" alt="image" src="https://github.com/user-attachments/assets/ac0cb186-849a-4c55-8f88-6def8c1b98d1" />
|
|
171
|
+
|
|
172
|
+
**If you did everything above, you'll have two things**
|
|
173
|
+
|
|
174
|
+
1. A page token, that won't expire to manage your Instagram account. You will use the page token to setup the Facebook Graph node in n8n.
|
|
175
|
+
2. The Instagram account id which you'll need to set in the `Configure` node in the n8n workflow
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
> **Source:** [ai_agents_az/episode_8/guide-instagram.md](https://github.com/gyoridavid/ai_agents_az/blob/main/episode_8/guide-instagram.md)
|