Skip to content

Commit c52ace5

Browse files
committed
import os
import requests from typing import Dict PINTEREST_ACCESS_TOKEN = os.getenv("PINTEREST_ACCESS_TOKEN") PINTEREST_BOARD_ID = os.getenv("PINTEREST_BOARD_ID") PINTEREST_PINS_URL = "https://api.pinterest.com/v5/pins" def create_pin(image_url: str, title: str, description: str, alt_text: str) -> Dict: """ Create a Pinterest pin using the v5 API. """ if not PINTEREST_ACCESS_TOKEN: raise RuntimeError("PINTEREST_ACCESS_TOKEN environment variable is not set") if not PINTEREST_BOARD_ID: raise RuntimeError("PINTEREST_BOARD_ID environment variable is not set") headers = { "Authorization": f"Bearer {PINTEREST_ACCESS_TOKEN}", "Content-Type": "application/json" } payload = { "board_id": PINTEREST_BOARD_ID, "title": title[:100], "description": description[:500], "alt_text": alt_text[:500], "media_source": { "source_type": "image_url", "url": image_url } } response = requests.post( PINTEREST_PINS_URL, headers=headers, json=payload, timeout=30 ) response.raise_for_status() return response.json()
1 parent de20bf7 commit c52ace5

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Auto-download images from Pexels and create a Pin on Pinterest.
4+
Configuration via environment variables:
5+
PEXELS_API_KEY
6+
PINTEREST_ACCESS_TOKEN
7+
PINTEREST_BOARD_ID
8+
Optional:
9+
IMGUR_CLIENT_ID - if set the script will upload downloaded images to Imgur and use that URL
10+
Usage:
11+
pip install requests
12+
export PEXELS_API_KEY=...
13+
export PINTEREST_ACCESS_TOKEN=...
14+
export PINTEREST_BOARD_ID=...
15+
python upload_pexels_to_pinterest.py --query "nature" --count 1
16+
"""
17+
import os
18+
import sys
19+
import argparse
20+
import requests
21+
import time
22+
23+
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
24+
PINTEREST_ACCESS_TOKEN = os.getenv("PINTEREST_ACCESS_TOKEN")
25+
PINTEREST_BOARD_ID = os.getenv("PINTEREST_BOARD_ID")
26+
IMGUR_CLIENT_ID = os.getenv("IMGUR_CLIENT_ID")
27+
28+
HEADERS_PEXELS = {"Authorization": PEXELS_API_KEY} if PEXELS_API_KEY else {}
29+
30+
PEXELS_SEARCH_URL = "https://api.pexels.com/v1/search"
31+
PINTEREST_PINS_URL = "https://api.pinterest.com/v5/pins"
32+
IMGUR_UPLOAD_URL = "https://api.imgur.com/3/image"
33+
34+
def search_pexels(query, per_page=1):
35+
params = {"query": query, "per_page": per_page}
36+
r = requests.get(PEXELS_SEARCH_URL, headers=HEADERS_PEXELS, params=params, timeout=30)
37+
r.raise_for_status()
38+
return r.json().get("photos", [])
39+
40+
def download_file(url, dest_path):
41+
r = requests.get(url, stream=True, timeout=60)
42+
r.raise_for_status()
43+
with open(dest_path, "wb") as f:
44+
for chunk in r.iter_content(chunk_size=8192):
45+
if not chunk:
46+
break
47+
f.write(chunk)
48+
return dest_path
49+
50+
def upload_to_imgur(filepath):
51+
if not IMGUR_CLIENT_ID:
52+
raise RuntimeError("IMGUR_CLIENT_ID not set")
53+
headers = {"Authorization": f"Client-ID {IMGUR_CLIENT_ID}"}
54+
with open(filepath, "rb") as f:
55+
r = requests.post(IMGUR_UPLOAD_URL, headers=headers, files={"image": f}, timeout=60)
56+
r.raise_for_status()
57+
data = r.json()
58+
if not data.get("success"):
59+
raise RuntimeError(f"Imgur upload failed: {data}")
60+
return data["data"]["link"]
61+
62+
def create_pin(image_url, title, description, alt_text=None):
63+
if not PINTEREST_ACCESS_TOKEN or not PINTEREST_BOARD_ID:
64+
raise RuntimeError("PINTEREST_ACCESS_TOKEN or PINTEREST_BOARD_ID not set")
65+
payload = {
66+
"board_id": PINTEREST_BOARD_ID,
67+
"title": title,
68+
"description": description,
69+
"media_source": {
70+
"source_type": "image_url",
71+
"url": image_url
72+
}
73+
}
74+
if alt_text:
75+
payload["alt_text"] = alt_text
76+
headers = {
77+
"Authorization": f"Bearer {PINTEREST_ACCESS_TOKEN}",
78+
"Content-Type": "application/json"
79+
}
80+
r = requests.post(PINTEREST_PINS_URL, json=payload, headers=headers, timeout=30)
81+
r.raise_for_status()
82+
return r.json()
83+
84+
def main():
85+
parser = argparse.ArgumentParser(description="Download from Pexels and post to Pinterest")
86+
parser.add_argument("--query", required=True, help="Search query for Pexels")
87+
parser.add_argument("--count", type=int, default=1, help="Number of items to fetch")
88+
parser.add_argument("--use-hosting", action="store_true",
89+
help="Download then upload to Imgur (requires IMGUR_CLIENT_ID). If not set, uses Pexels image URL directly.")
90+
parser.add_argument("--include-attribution", action="store_true",
91+
help="Append photographer and Pexels link to pin description")
92+
args = parser.parse_args()
93+
94+
photos = search_pexels(args.query, per_page=args.count)
95+
if not photos:
96+
print("No photos found.", file=sys.stderr)
97+
sys.exit(1)
98+
for i, p in enumerate(photos, 1):
99+
photographer = p.get("photographer")
100+
pexels_url = p.get("url") # page url
101+
src = p.get("src", {})
102+
image_url = src.get("original") or src.get("large") or src.get("medium")
103+
if not image_url:
104+
print("No usable image src found for photo", p.get("id"), file=sys.stderr)
105+
continue
106+
107+
final_image_url = image_url
108+
if args.use_hosting:
109+
local_name = f"pexels_{p.get('id')}.jpg"
110+
print("Downloading", image_url)
111+
download_file(image_url, local_name)
112+
print("Uploading to Imgur...")
113+
final_image_url = upload_to_imgur(local_name)
114+
print("Imgur URL:", final_image_url)
115+
try:
116+
os.remove(local_name)
117+
except Exception:
118+
pass
119+
120+
title = f"Pexels: {p.get('id')}"
121+
description = p.get("alt") or p.get("photographer") or ""
122+
if args.include_attribution:
123+
description += f"\n\nPhoto by {photographer} on Pexels: {pexels_url}"
124+
125+
print(f"Creating pin {i}/{len(photos)} with image {final_image_url} ...")
126+
resp = create_pin(final_image_url, title, description, alt_text=p.get("alt"))
127+
print("Pin created:", resp.get("id"))
128+
time.sleep(1)
129+
130+
if __name__ == "__main__":
131+
main()

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"githubPullRequests.ignoredPullRequestBranches": [
3+
"release"
4+
]
5+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Auto-download images from Pexels and create a Pin on Pinterest.
4+
Configuration via environment variables:
5+
PEXELS_API_KEY
6+
PINTEREST_ACCESS_TOKEN
7+
PINTEREST_BOARD_ID
8+
Optional:
9+
IMGUR_CLIENT_ID - if set the script will upload downloaded images to Imgur and use that URL
10+
Usage:
11+
pip install requests
12+
export PEXELS_API_KEY=...
13+
export PINTEREST_ACCESS_TOKEN=...
14+
export PINTEREST_BOARD_ID=...
15+
python upload_pexels_to_pinterest.py --query "nature" --count 1
16+
"""
17+
import os
18+
import sys
19+
import argparse
20+
import requests
21+
import time
22+
23+
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
24+
PINTEREST_ACCESS_TOKEN = os.getenv("PINTEREST_ACCESS_TOKEN")
25+
PINTEREST_BOARD_ID = os.getenv("PINTEREST_BOARD_ID")
26+
IMGUR_CLIENT_ID = os.getenv("IMGUR_CLIENT_ID")
27+
28+
HEADERS_PEXELS = {"Authorization": PEXELS_API_KEY} if PEXELS_API_KEY else {}
29+
30+
PEXELS_SEARCH_URL = "https://api.pexels.com/v1/search"
31+
PINTEREST_PINS_URL = "https://api.pinterest.com/v5/pins"
32+
IMGUR_UPLOAD_URL = "https://api.imgur.com/3/image"
33+
34+
def search_pexels(query, per_page=1):
35+
params = {"query": query, "per_page": per_page}
36+
r = requests.get(PEXELS_SEARCH_URL, headers=HEADERS_PEXELS, params=params, timeout=30)
37+
r.raise_for_status()
38+
return r.json().get("photos", [])
39+
40+
def download_file(url, dest_path):
41+
r = requests.get(url, stream=True, timeout=60)
42+
r.raise_for_status()
43+
with open(dest_path, "wb") as f:
44+
for chunk in r.iter_content(chunk_size=8192):
45+
if not chunk:
46+
break
47+
f.write(chunk)
48+
return dest_path
49+
50+
def upload_to_imgur(filepath):
51+
if not IMGUR_CLIENT_ID:
52+
raise RuntimeError("IMGUR_CLIENT_ID not set")
53+
headers = {"Authorization": f"Client-ID {IMGUR_CLIENT_ID}"}
54+
with open(filepath, "rb") as f:
55+
r = requests.post(IMGUR_UPLOAD_URL, headers=headers, files={"image": f}, timeout=60)
56+
r.raise_for_status()
57+
data = r.json()
58+
if not data.get("success"):
59+
raise RuntimeError(f"Imgur upload failed: {data}")
60+
return data["data"]["link"]
61+
62+
def create_pin(image_url, title, description, alt_text=None):
63+
if not PINTEREST_ACCESS_TOKEN or not PINTEREST_BOARD_ID:
64+
raise RuntimeError("PINTEREST_ACCESS_TOKEN or PINTEREST_BOARD_ID not set")
65+
payload = {
66+
"board_id": PINTEREST_BOARD_ID,
67+
"title": title,
68+
"description": description,
69+
"media_source": {
70+
"source_type": "image_url",
71+
"url": image_url
72+
}
73+
}
74+
if alt_text:
75+
payload["alt_text"] = alt_text
76+
headers = {
77+
"Authorization": f"Bearer {PINTEREST_ACCESS_TOKEN}",
78+
"Content-Type": "application/json"
79+
}
80+
r = requests.post(PINTEREST_PINS_URL, json=payload, headers=headers, timeout=30)
81+
r.raise_for_status()
82+
return r.json()
83+
84+
def main():
85+
parser = argparse.ArgumentParser(description="Download from Pexels and post to Pinterest")
86+
parser.add_argument("--query", required=True, help="Search query for Pexels")
87+
parser.add_argument("--count", type=int, default=1, help="Number of items to fetch")
88+
parser.add_argument("--use-hosting", action="store_true",
89+
help="Download then upload to Imgur (requires IMGUR_CLIENT_ID). If not set, uses Pexels image URL directly.")
90+
parser.add_argument("--include-attribution", action="store_true",
91+
help="Append photographer and Pexels link to pin description")
92+
args = parser.parse_args()
93+
Final Answer (Short)
94+
95+
Inside this project, the APIs used are:
96+
97+
Pinterest API (for uploading pins)
98+
99+
Pexels API (for fetching photos/videos)
100+
101+
If you want, I can:
102+
103+
open and explain the exact API endpoints line by line
104+
105+
help you create Pinterest Developer App
106+
107+
fix errors like 401 / invalid_scope / redirect_uri
108+
109+
convert this into a Node.js or server API
110+
111+
Just say the word 🚀

0 commit comments

Comments
 (0)