meta-ads-mcp 0.3.10__tar.gz → 0.4.0__tar.gz

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.
Files changed (47) hide show
  1. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/PKG-INFO +3 -11
  2. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/README.md +2 -10
  3. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/__init__.py +1 -3
  4. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/__init__.py +1 -2
  5. meta_ads_mcp-0.4.0/meta_ads_mcp/core/insights.py +62 -0
  6. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/pyproject.toml +1 -1
  7. meta_ads_mcp-0.3.10/meta_ads_mcp/core/insights.py +0 -429
  8. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/.github/workflows/publish.yml +0 -0
  9. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/.github/workflows/test.yml +0 -0
  10. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/.gitignore +0 -0
  11. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/CUSTOM_META_APP.md +0 -0
  12. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/Dockerfile +0 -0
  13. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/LICENSE +0 -0
  14. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/LOCAL_INSTALLATION.md +0 -0
  15. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/META_API_NOTES.md +0 -0
  16. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/RELEASE.md +0 -0
  17. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/STREAMABLE_HTTP_SETUP.md +0 -0
  18. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/examples/README.md +0 -0
  19. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/examples/example_http_client.py +0 -0
  20. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/future_improvements.md +0 -0
  21. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/images/meta-ads-example.png +0 -0
  22. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_auth.sh +0 -0
  23. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/__main__.py +0 -0
  24. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/accounts.py +0 -0
  25. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/ads.py +0 -0
  26. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/ads_library.py +0 -0
  27. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/adsets.py +0 -0
  28. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/api.py +0 -0
  29. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/auth.py +0 -0
  30. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/authentication.py +0 -0
  31. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/budget_schedules.py +0 -0
  32. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/callback_server.py +0 -0
  33. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/campaigns.py +0 -0
  34. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/http_auth_integration.py +0 -0
  35. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/pipeboard_auth.py +0 -0
  36. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/reports.py +0 -0
  37. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/resources.py +0 -0
  38. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/server.py +0 -0
  39. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/meta_ads_mcp/core/utils.py +0 -0
  40. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/requirements.txt +0 -0
  41. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/setup.py +0 -0
  42. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/smithery.yaml +0 -0
  43. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/tests/README.md +0 -0
  44. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/tests/__init__.py +0 -0
  45. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/tests/conftest.py +0 -0
  46. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/tests/test_http_transport.py +0 -0
  47. {meta_ads_mcp-0.3.10 → meta_ads_mcp-0.4.0}/tests/test_openai.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meta-ads-mcp
3
- Version: 0.3.10
3
+ Version: 0.4.0
4
4
  Summary: Model Calling Protocol (MCP) plugin for interacting with Meta Ads API
5
5
  Project-URL: Homepage, https://github.com/pipeboard-co/meta-ads-mcp
6
6
  Project-URL: Bug Tracker, https://github.com/pipeboard-co/meta-ads-mcp/issues
@@ -313,21 +313,13 @@ For local installation configuration, authentication options, and advanced techn
313
313
  - `level`: Level of aggregation (ad, adset, campaign, account)
314
314
  - Returns: Performance metrics for the specified object
315
315
 
316
- 20. `mcp_meta_ads_debug_image_download`
317
- - Debug image download issues and report detailed diagnostics
318
- - Inputs:
319
- - `access_token` (optional): Meta API access token (will use cached token if not provided)
320
- - `url`: Direct image URL to test (optional)
321
- - `ad_id`: Meta Ads ad ID (optional, used if url is not provided)
322
- - Returns: Diagnostic information about image download attempts
323
-
324
- 21. `mcp_meta_ads_get_login_link`
316
+ 20. `mcp_meta_ads_get_login_link`
325
317
  - Get a clickable login link for Meta Ads authentication
326
318
  - Inputs:
327
319
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
328
320
  - Returns: A clickable resource link for Meta authentication
329
321
 
330
- 22. `mcp_meta-ads_create_budget_schedule`
322
+ 21. `mcp_meta-ads_create_budget_schedule`
331
323
  - Create a budget schedule for a Meta Ads campaign.
332
324
  - Inputs:
333
325
  - `campaign_id`: Meta Ads campaign ID.
@@ -290,21 +290,13 @@ For local installation configuration, authentication options, and advanced techn
290
290
  - `level`: Level of aggregation (ad, adset, campaign, account)
291
291
  - Returns: Performance metrics for the specified object
292
292
 
293
- 20. `mcp_meta_ads_debug_image_download`
294
- - Debug image download issues and report detailed diagnostics
295
- - Inputs:
296
- - `access_token` (optional): Meta API access token (will use cached token if not provided)
297
- - `url`: Direct image URL to test (optional)
298
- - `ad_id`: Meta Ads ad ID (optional, used if url is not provided)
299
- - Returns: Diagnostic information about image download attempts
300
-
301
- 21. `mcp_meta_ads_get_login_link`
293
+ 20. `mcp_meta_ads_get_login_link`
302
294
  - Get a clickable login link for Meta Ads authentication
303
295
  - Inputs:
304
296
  - `access_token` (optional): Meta API access token (will use cached token if not provided)
305
297
  - Returns: A clickable resource link for Meta authentication
306
298
 
307
- 22. `mcp_meta-ads_create_budget_schedule`
299
+ 21. `mcp_meta-ads_create_budget_schedule`
308
300
  - Create a budget schedule for a Meta Ads campaign.
309
301
  - Inputs:
310
302
  - `campaign_id`: Meta Ads campaign ID.
@@ -7,7 +7,7 @@ with the Claude LLM.
7
7
 
8
8
  from meta_ads_mcp.core.server import main
9
9
 
10
- __version__ = "0.3.10"
10
+ __version__ = "0.4.0"
11
11
 
12
12
  __all__ = [
13
13
  'get_ad_accounts',
@@ -24,7 +24,6 @@ __all__ = [
24
24
  'get_ad_image',
25
25
  'update_ad',
26
26
  'get_insights',
27
- 'debug_image_download',
28
27
  'get_login_link',
29
28
  'login_cli',
30
29
  'main'
@@ -46,7 +45,6 @@ from .core import (
46
45
  get_ad_image,
47
46
  update_ad,
48
47
  get_insights,
49
- debug_image_download,
50
48
  get_login_link,
51
49
  login_cli,
52
50
  main
@@ -5,7 +5,7 @@ from .accounts import get_ad_accounts, get_account_info
5
5
  from .campaigns import get_campaigns, get_campaign_details, create_campaign
6
6
  from .adsets import get_adsets, get_adset_details, update_adset
7
7
  from .ads import get_ads, get_ad_details, get_ad_creatives, get_ad_image, update_ad
8
- from .insights import get_insights, debug_image_download
8
+ from .insights import get_insights
9
9
  from .authentication import get_login_link
10
10
  from .server import login_cli, main
11
11
  from .auth import login
@@ -29,7 +29,6 @@ __all__ = [
29
29
  'get_ad_image',
30
30
  'update_ad',
31
31
  'get_insights',
32
- 'debug_image_download',
33
32
  'get_login_link',
34
33
  'login_cli',
35
34
  'login',
@@ -0,0 +1,62 @@
1
+ """Insights and Reporting functionality for Meta Ads API."""
2
+
3
+ import json
4
+ from typing import Optional, Union, Dict
5
+ from .api import meta_api_tool, make_api_request
6
+ from .utils import download_image, try_multiple_download_methods, ad_creative_images, create_resource_from_image
7
+ from .server import mcp_server
8
+ import base64
9
+ import datetime
10
+
11
+
12
+ @mcp_server.tool()
13
+ @meta_api_tool
14
+ async def get_insights(access_token: str = None, object_id: str = None,
15
+ time_range: Union[str, Dict[str, str]] = "maximum", breakdown: str = "",
16
+ level: str = "ad") -> str:
17
+ """
18
+ Get performance insights for a campaign, ad set, ad or account.
19
+
20
+ Args:
21
+ access_token: Meta API access token (optional - will use cached token if not provided)
22
+ object_id: ID of the campaign, ad set, ad or account
23
+ time_range: Either a preset time range string or a dictionary with "since" and "until" dates in YYYY-MM-DD format
24
+ Preset options: today, yesterday, this_month, last_month, this_quarter, maximum, data_maximum,
25
+ last_3d, last_7d, last_14d, last_28d, last_30d, last_90d, last_week_mon_sun,
26
+ last_week_sun_sat, last_quarter, last_year, this_week_mon_today, this_week_sun_today, this_year
27
+ Dictionary example: {"since":"2023-01-01","until":"2023-01-31"}
28
+ breakdown: Optional breakdown dimension (e.g., age, gender, country)
29
+ level: Level of aggregation (ad, adset, campaign, account)
30
+ """
31
+ if not object_id:
32
+ return json.dumps({"error": "No object ID provided"}, indent=2)
33
+
34
+ endpoint = f"{object_id}/insights"
35
+ params = {
36
+ "fields": "account_id,account_name,campaign_id,campaign_name,adset_id,adset_name,ad_id,ad_name,impressions,clicks,spend,cpc,cpm,ctr,reach,frequency,actions,conversions,unique_clicks,cost_per_action_type",
37
+ "level": level
38
+ }
39
+
40
+ # Handle time range based on type
41
+ if isinstance(time_range, dict):
42
+ # Use custom date range with since/until parameters
43
+ if "since" in time_range and "until" in time_range:
44
+ params["time_range"] = json.dumps(time_range)
45
+ else:
46
+ return json.dumps({"error": "Custom time_range must contain both 'since' and 'until' keys in YYYY-MM-DD format"}, indent=2)
47
+ else:
48
+ # Use preset date range
49
+ params["date_preset"] = time_range
50
+
51
+ if breakdown:
52
+ params["breakdowns"] = breakdown
53
+
54
+ data = await make_api_request(endpoint, access_token, params)
55
+
56
+ return json.dumps(data, indent=2)
57
+
58
+
59
+
60
+
61
+
62
+
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meta-ads-mcp"
7
- version = "0.3.10"
7
+ version = "0.4.0"
8
8
  description = "Model Calling Protocol (MCP) plugin for interacting with Meta Ads API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,429 +0,0 @@
1
- """Insights and Reporting functionality for Meta Ads API."""
2
-
3
- import json
4
- from typing import Optional, Union, Dict
5
- from .api import meta_api_tool, make_api_request
6
- from .utils import download_image, try_multiple_download_methods, ad_creative_images, create_resource_from_image
7
- from .server import mcp_server
8
- import base64
9
- import datetime
10
-
11
-
12
- @mcp_server.tool()
13
- @meta_api_tool
14
- async def get_insights(access_token: str = None, object_id: str = None,
15
- time_range: Union[str, Dict[str, str]] = "maximum", breakdown: str = "",
16
- level: str = "ad") -> str:
17
- """
18
- Get performance insights for a campaign, ad set, ad or account.
19
-
20
- Args:
21
- access_token: Meta API access token (optional - will use cached token if not provided)
22
- object_id: ID of the campaign, ad set, ad or account
23
- time_range: Either a preset time range string or a dictionary with "since" and "until" dates in YYYY-MM-DD format
24
- Preset options: today, yesterday, this_month, last_month, this_quarter, maximum, data_maximum,
25
- last_3d, last_7d, last_14d, last_28d, last_30d, last_90d, last_week_mon_sun,
26
- last_week_sun_sat, last_quarter, last_year, this_week_mon_today, this_week_sun_today, this_year
27
- Dictionary example: {"since":"2023-01-01","until":"2023-01-31"}
28
- breakdown: Optional breakdown dimension (e.g., age, gender, country)
29
- level: Level of aggregation (ad, adset, campaign, account)
30
- """
31
- if not object_id:
32
- return json.dumps({"error": "No object ID provided"}, indent=2)
33
-
34
- endpoint = f"{object_id}/insights"
35
- params = {
36
- "fields": "account_id,account_name,campaign_id,campaign_name,adset_id,adset_name,ad_id,ad_name,impressions,clicks,spend,cpc,cpm,ctr,reach,frequency,actions,conversions,unique_clicks,cost_per_action_type",
37
- "level": level
38
- }
39
-
40
- # Handle time range based on type
41
- if isinstance(time_range, dict):
42
- # Use custom date range with since/until parameters
43
- if "since" in time_range and "until" in time_range:
44
- params["time_range"] = json.dumps(time_range)
45
- else:
46
- return json.dumps({"error": "Custom time_range must contain both 'since' and 'until' keys in YYYY-MM-DD format"}, indent=2)
47
- else:
48
- # Use preset date range
49
- params["date_preset"] = time_range
50
-
51
- if breakdown:
52
- params["breakdowns"] = breakdown
53
-
54
- data = await make_api_request(endpoint, access_token, params)
55
-
56
- return json.dumps(data, indent=2)
57
-
58
-
59
- @mcp_server.tool()
60
- @meta_api_tool
61
- async def debug_image_download(access_token: str = None, url: str = "", ad_id: str = "") -> str:
62
- """
63
- Debug image download issues and report detailed diagnostics.
64
-
65
- Args:
66
- access_token: Meta API access token (optional - will use cached token if not provided)
67
- url: Direct image URL to test (optional)
68
- ad_id: Meta Ads ad ID (optional, used if url is not provided)
69
- """
70
- results = {
71
- "diagnostics": {
72
- "timestamp": str(datetime.datetime.now()),
73
- "methods_tried": [],
74
- "request_details": [],
75
- "network_info": {}
76
- }
77
- }
78
-
79
- # If no URL provided but ad_id is, get URL from ad creative
80
- if not url and ad_id:
81
- print(f"Getting image URL from ad creative for ad {ad_id}")
82
- # Get the creative details
83
- from .ads import get_ad_creatives
84
- creative_json = await get_ad_creatives(ad_id=ad_id, access_token=access_token)
85
- creative_data = json.loads(creative_json)
86
- results["creative_data"] = creative_data
87
-
88
- # Look for image URL in the creative
89
- if "full_image_url" in creative_data:
90
- url = creative_data.get("full_image_url")
91
- elif "thumbnail_url" in creative_data:
92
- url = creative_data.get("thumbnail_url")
93
-
94
- if not url:
95
- return json.dumps({
96
- "error": "No image URL provided or found in ad creative",
97
- "results": results
98
- }, indent=2)
99
-
100
- results["image_url"] = url
101
-
102
- # Try to get network information to help debug
103
- try:
104
- import socket
105
- from urllib.parse import urlparse
106
- hostname = urlparse(url).netloc
107
- ip_address = socket.gethostbyname(hostname)
108
- results["diagnostics"]["network_info"] = {
109
- "hostname": hostname,
110
- "ip_address": ip_address,
111
- "is_facebook_cdn": "fbcdn" in hostname
112
- }
113
- except Exception as e:
114
- results["diagnostics"]["network_info"] = {
115
- "error": str(e)
116
- }
117
-
118
- # Method 1: Basic download
119
- method_result = {
120
- "method": "Basic download with standard headers",
121
- "success": False
122
- }
123
- results["diagnostics"]["methods_tried"].append(method_result)
124
-
125
- try:
126
- headers = {
127
- "User-Agent": "curl/8.4.0"
128
- }
129
- import httpx
130
- async with httpx.AsyncClient(follow_redirects=True) as client:
131
- response = await client.get(url, headers=headers, timeout=30.0)
132
- method_result["status_code"] = response.status_code
133
- method_result["headers"] = dict(response.headers)
134
-
135
- if response.status_code == 200:
136
- method_result["success"] = True
137
- method_result["content_length"] = len(response.content)
138
- method_result["content_type"] = response.headers.get("content-type")
139
-
140
- # Save this successful result
141
- results["image_data"] = {
142
- "length": len(response.content),
143
- "type": response.headers.get("content-type"),
144
- "base64_sample": base64.b64encode(response.content[:100]).decode("utf-8") + "..." if response.content else None
145
- }
146
- except Exception as e:
147
- method_result["error"] = str(e)
148
-
149
- # Method 2: Browser emulation
150
- method_result = {
151
- "method": "Browser emulation with cookies",
152
- "success": False
153
- }
154
- results["diagnostics"]["methods_tried"].append(method_result)
155
-
156
- try:
157
- headers = {
158
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
159
- "Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
160
- "Accept-Language": "en-US,en;q=0.9",
161
- "Referer": "https://www.facebook.com/",
162
- "Cookie": "presence=EDvF3EtimeF1697900316EuserFA21B00112233445566AA0EstateFDutF0CEchF_7bCC"
163
- }
164
-
165
- import httpx
166
- async with httpx.AsyncClient(follow_redirects=True) as client:
167
- response = await client.get(url, headers=headers, timeout=30.0)
168
- method_result["status_code"] = response.status_code
169
- method_result["headers"] = dict(response.headers)
170
-
171
- if response.status_code == 200:
172
- method_result["success"] = True
173
- method_result["content_length"] = len(response.content)
174
- method_result["content_type"] = response.headers.get("content-type")
175
-
176
- # If first method didn't succeed, save this successful result
177
- if "image_data" not in results:
178
- results["image_data"] = {
179
- "length": len(response.content),
180
- "type": response.headers.get("content-type"),
181
- "base64_sample": base64.b64encode(response.content[:100]).decode("utf-8") + "..." if response.content else None
182
- }
183
- except Exception as e:
184
- method_result["error"] = str(e)
185
-
186
- # Method 3: Graph API direct access (if applicable)
187
- if "fbcdn" in url or "facebook" in url:
188
- method_result = {
189
- "method": "Graph API direct access",
190
- "success": False
191
- }
192
- results["diagnostics"]["methods_tried"].append(method_result)
193
-
194
- try:
195
- # Try to reconstruct the attachment ID from URL if possible
196
- from urllib.parse import urlparse
197
- url_parts = urlparse(url).path.split("/")
198
- potential_ids = [part for part in url_parts if part.isdigit() and len(part) > 10]
199
-
200
- if potential_ids:
201
- attachment_id = potential_ids[0]
202
- endpoint = f"{attachment_id}?fields=url,width,height"
203
- api_result = await make_api_request(endpoint, access_token)
204
-
205
- method_result["api_response"] = api_result
206
-
207
- if "url" in api_result:
208
- graph_url = api_result["url"]
209
- method_result["graph_url"] = graph_url
210
-
211
- # Try to download from this Graph API URL
212
- import httpx
213
- async with httpx.AsyncClient() as client:
214
- response = await client.get(graph_url, timeout=30.0)
215
-
216
- method_result["status_code"] = response.status_code
217
- if response.status_code == 200:
218
- method_result["success"] = True
219
- method_result["content_length"] = len(response.content)
220
-
221
- # If previous methods didn't succeed, save this successful result
222
- if "image_data" not in results:
223
- results["image_data"] = {
224
- "length": len(response.content),
225
- "type": response.headers.get("content-type"),
226
- "base64_sample": base64.b64encode(response.content[:100]).decode("utf-8") + "..." if response.content else None
227
- }
228
- except Exception as e:
229
- method_result["error"] = str(e)
230
-
231
- # Generate a recommendation based on what we found
232
- if "image_data" in results:
233
- results["recommendation"] = "At least one download method succeeded. Consider implementing the successful method in the main code."
234
- else:
235
- # Check if the error appears to be access-related
236
- access_errors = False
237
- for method in results["diagnostics"]["methods_tried"]:
238
- if method.get("status_code") in [401, 403, 503]:
239
- access_errors = True
240
-
241
- if access_errors:
242
- results["recommendation"] = "Authentication or authorization errors detected. Images may require direct Facebook authentication not possible via API."
243
- else:
244
- results["recommendation"] = "Network or other technical errors detected. Check URL expiration or CDN restrictions."
245
-
246
- return json.dumps(results, indent=2)
247
-
248
-
249
- @mcp_server.tool()
250
- @meta_api_tool
251
- async def save_ad_image_via_api(access_token: str = None, ad_id: str = None) -> str:
252
- """
253
- Try to save an ad image by using the Marketing API's attachment endpoints.
254
- This is an alternative approach when direct image download fails.
255
-
256
- Args:
257
- access_token: Meta API access token (optional - will use cached token if not provided)
258
- ad_id: Meta Ads ad ID
259
- """
260
- if not ad_id:
261
- return json.dumps({"error": "No ad ID provided"}, indent=2)
262
-
263
- # First get the ad's creative ID
264
- endpoint = f"{ad_id}"
265
- params = {
266
- "fields": "creative,account_id"
267
- }
268
-
269
- ad_data = await make_api_request(endpoint, access_token, params)
270
-
271
- if "error" in ad_data:
272
- return json.dumps({
273
- "error": "Could not get ad data",
274
- "details": ad_data
275
- }, indent=2)
276
-
277
- if "creative" not in ad_data or "id" not in ad_data["creative"]:
278
- return json.dumps({
279
- "error": "No creative ID found for this ad",
280
- "ad_data": ad_data
281
- }, indent=2)
282
-
283
- creative_id = ad_data["creative"]["id"]
284
- account_id = ad_data.get("account_id", "")
285
-
286
- # Now get the creative object
287
- creative_endpoint = f"{creative_id}"
288
- creative_params = {
289
- "fields": "id,name,thumbnail_url,image_hash,asset_feed_spec"
290
- }
291
-
292
- creative_data = await make_api_request(creative_endpoint, access_token, creative_params)
293
-
294
- if "error" in creative_data:
295
- return json.dumps({
296
- "error": "Could not get creative data",
297
- "details": creative_data
298
- }, indent=2)
299
-
300
- # Approach 1: Try to get image through adimages endpoint if we have image_hash
301
- image_hash = None
302
- if "image_hash" in creative_data:
303
- image_hash = creative_data["image_hash"]
304
- elif "asset_feed_spec" in creative_data and "images" in creative_data["asset_feed_spec"] and len(creative_data["asset_feed_spec"]["images"]) > 0:
305
- image_hash = creative_data["asset_feed_spec"]["images"][0].get("hash")
306
-
307
- result = {
308
- "ad_id": ad_id,
309
- "creative_id": creative_id,
310
- "attempts": []
311
- }
312
-
313
- if image_hash and account_id:
314
- attempt = {
315
- "method": "adimages endpoint with hash",
316
- "success": False
317
- }
318
- result["attempts"].append(attempt)
319
-
320
- try:
321
- image_endpoint = f"act_{account_id}/adimages"
322
- image_params = {
323
- "hashes": [image_hash]
324
- }
325
- image_data = await make_api_request(image_endpoint, access_token, image_params)
326
- attempt["response"] = image_data
327
-
328
- if "data" in image_data and len(image_data["data"]) > 0 and "url" in image_data["data"][0]:
329
- url = image_data["data"][0]["url"]
330
- attempt["url"] = url
331
-
332
- # Try to download the image
333
- image_bytes = await download_image(url)
334
- if image_bytes:
335
- attempt["success"] = True
336
- attempt["image_size"] = len(image_bytes)
337
-
338
- # Save the image
339
- resource_id = f"ad_creative_{ad_id}_method1"
340
- resource_info = create_resource_from_image(
341
- image_bytes,
342
- resource_id,
343
- f"Ad Creative for {ad_id} (Method 1)"
344
- )
345
-
346
- # Return success with resource info
347
- result.update(resource_info)
348
- result["success"] = True
349
- base64_sample = base64.b64encode(image_bytes[:100]).decode("utf-8") + "..."
350
- result["base64_sample"] = base64_sample
351
- except Exception as e:
352
- attempt["error"] = str(e)
353
-
354
- # Approach 2: Try directly with the thumbnails endpoint
355
- attempt = {
356
- "method": "thumbnails endpoint on creative",
357
- "success": False
358
- }
359
- result["attempts"].append(attempt)
360
-
361
- try:
362
- thumbnails_endpoint = f"{creative_id}/thumbnails"
363
- thumbnails_params = {}
364
- thumbnails_data = await make_api_request(thumbnails_endpoint, access_token, thumbnails_params)
365
- attempt["response"] = thumbnails_data
366
-
367
- if "data" in thumbnails_data and len(thumbnails_data["data"]) > 0:
368
- for thumbnail in thumbnails_data["data"]:
369
- if "uri" in thumbnail:
370
- url = thumbnail["uri"]
371
- attempt["url"] = url
372
-
373
- # Try to download the image
374
- image_bytes = await download_image(url)
375
- if image_bytes:
376
- attempt["success"] = True
377
- attempt["image_size"] = len(image_bytes)
378
-
379
- # Save the image if method 1 didn't already succeed
380
- if "success" not in result or not result["success"]:
381
- resource_id = f"ad_creative_{ad_id}_method2"
382
- resource_info = create_resource_from_image(
383
- image_bytes,
384
- resource_id,
385
- f"Ad Creative for {ad_id} (Method 2)"
386
- )
387
-
388
- # Return success with resource info
389
- result.update(resource_info)
390
- result["success"] = True
391
- base64_sample = base64.b64encode(image_bytes[:100]).decode("utf-8") + "..."
392
- result["base64_sample"] = base64_sample
393
-
394
- # No need to try more thumbnails if we succeeded
395
- break
396
- except Exception as e:
397
- attempt["error"] = str(e)
398
-
399
- # Approach 3: Try using the preview shareable link as an alternate source
400
- attempt = {
401
- "method": "preview_shareable_link",
402
- "success": False
403
- }
404
- result["attempts"].append(attempt)
405
-
406
- try:
407
- # Get ad details with preview link
408
- ad_preview_endpoint = f"{ad_id}"
409
- ad_preview_params = {
410
- "fields": "preview_shareable_link"
411
- }
412
- ad_preview_data = await make_api_request(ad_preview_endpoint, access_token, ad_preview_params)
413
-
414
- if "preview_shareable_link" in ad_preview_data:
415
- preview_link = ad_preview_data["preview_shareable_link"]
416
- attempt["preview_link"] = preview_link
417
-
418
- # We can't directly download the preview image, but let's note it for manual inspection
419
- attempt["note"] = "Preview link available for manual inspection in browser"
420
- except Exception as e:
421
- attempt["error"] = str(e)
422
-
423
- # Overall result
424
- if "success" in result and result["success"]:
425
- result["message"] = "Successfully retrieved ad image through one of the API methods"
426
- else:
427
- result["message"] = "Failed to retrieve ad image through any API method"
428
-
429
- return json.dumps(result, indent=2)
File without changes
File without changes
File without changes
File without changes
File without changes