Skip to content

Commit 8f42f8d

Browse files
added threads bot
1 parent 38e73b1 commit 8f42f8d

File tree

8 files changed

+274
-2
lines changed

8 files changed

+274
-2
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Threads-Bot Daily Post
2+
3+
on:
4+
schedule:
5+
# Run at 9:15 AM UTC every day (15 minutes after X-Bot to stagger the posts)
6+
- cron: '15 9 * * *'
7+
# Allow manual triggering for testing
8+
workflow_dispatch:
9+
10+
jobs:
11+
post-year-progress:
12+
runs-on: ubuntu-latest
13+
environment: Production
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v3
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v4
21+
with:
22+
python-version: '3.10'
23+
24+
- name: Install dependencies
25+
run: |
26+
python -m pip install --upgrade pip
27+
pip install requests python-dotenv
28+
29+
- name: Run Threads-Bot
30+
env:
31+
THREADS_USER_ID: ${{ secrets.THREADS_USER_ID }}
32+
THREADS_ACCESS_TOKEN: ${{ secrets.THREADS_ACCESS_TOKEN }}
33+
run: |
34+
cd apps/bots
35+
python threads-bot.py

.github/workflows/x-bot-daily.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ jobs:
3333
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
3434
ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN_SECRET }}
3535
run: |
36-
cd apps/x-bot
37-
python bot.py
36+
cd apps/bots
37+
python x-bot.py
File renamed without changes.

apps/bots/THREADS-README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# 📅 Year Progress Threads Bot
2+
3+
An automated Threads bot that posts daily at exactly 09:15 GMT+0, visually indicating the percentage of the year passed using ASCII art.
4+
5+
## ✨ Example Post
6+
7+
```
8+
20.00% of 2025 has passed.
9+
[████················]
10+
```
11+
12+
## 🚀 Getting Started
13+
14+
### Step 1: Prepare your Threads Account
15+
16+
- Create a dedicated Threads account for the bot.
17+
- Sign up or log in at [Meta for Developers](https://developers.facebook.com/) to create a developer account.
18+
19+
### Step 2: Set Up a Meta App
20+
21+
1. Go to [Meta for Developers](https://developers.facebook.com/apps/)
22+
2. Create a new app with the "Other" type
23+
3. Add the "Threads API" product to your app
24+
4. Configure the app settings:
25+
- Set up a valid Privacy Policy URL
26+
- Configure the OAuth redirect URL
27+
- Add test users for development
28+
29+
### Step 3: Obtain Threads API Credentials
30+
31+
You'll need the following credentials:
32+
- `THREADS_USER_ID` - Your Threads user ID
33+
- `THREADS_ACCESS_TOKEN` - A long-lived access token for the Threads API
34+
35+
To get a long-lived access token:
36+
1. Generate a user access token with the `threads_manage` permission
37+
2. Exchange it for a long-lived token using the [Access Token Tool](https://developers.facebook.com/tools/debug/accesstoken/)
38+
39+
### Step 4: Project Setup
40+
41+
```bash
42+
mkdir year-progress-bot
43+
cd year-progress-bot
44+
python3 -m venv venv
45+
source venv/bin/activate
46+
pip install requests python-dotenv
47+
```
48+
49+
### Step 5: Securely Store API Keys
50+
51+
Create a `.env` file in your project directory:
52+
53+
```
54+
THREADS_USER_ID=your_threads_user_id
55+
THREADS_ACCESS_TOKEN=your_threads_access_token
56+
```
57+
58+
Replace placeholders with your actual credentials.
59+
60+
### 🧪 Test the Bot
61+
62+
Run the script manually to test:
63+
64+
```bash
65+
python threads-bot.py
66+
```
67+
68+
Verify the posts appear correctly on your Threads account.
69+
70+
## ⏰ Automate with GitHub Actions
71+
72+
This bot is configured to run automatically using GitHub Actions. The workflow is set up to:
73+
74+
1. Run daily at 09:15 GMT+0 (staggered 15 minutes after the X bot)
75+
2. Use repository secrets for API credentials
76+
3. Allow manual triggering for testing
77+
78+
### Setting Up GitHub Secrets
79+
80+
For the GitHub Action to work, you need to add your Threads API credentials as repository secrets:
81+
82+
1. Go to your GitHub repository
83+
2. Navigate to Settings > Secrets and variables > Actions
84+
3. Add the following secrets:
85+
- `THREADS_USER_ID` - Your Threads User ID
86+
- `THREADS_ACCESS_TOKEN` - Your Threads Access Token
87+
88+
### Manual Triggering
89+
90+
To manually trigger the workflow:
91+
92+
1. Go to your GitHub repository
93+
2. Navigate to Actions > Threads-Bot Daily Post
94+
3. Click "Run workflow"
95+
96+
## 📌 Verify Automation
97+
98+
After setting up the GitHub Action:
99+
100+
1. Check the Actions tab in your repository to see if the workflow runs successfully
101+
2. Verify that posts are published to your Threads account at the scheduled time
102+
103+
## 🔍 Troubleshooting
104+
105+
- **Rate Limiting**: The Threads API has rate limits. If you hit them, the bot will log the error.
106+
- **Token Expiration**: Access tokens expire. Make sure to refresh your long-lived token before it expires.
107+
- **API Changes**: The Threads API is relatively new and may change. Check the [Meta for Developers documentation](https://developers.facebook.com/docs/threads) for updates.
108+
109+
## 🎉 Done!
110+
111+
Your bot is live and will automatically post the year's progress daily on Threads!

apps/bots/threads-bot.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import datetime
2+
import os
3+
import pathlib
4+
import requests
5+
import time
6+
from dotenv import load_dotenv
7+
8+
# Try to load from .env file for local development
9+
current_dir = pathlib.Path(__file__).parent.absolute()
10+
env_path = current_dir.parent.parent / '.env'
11+
if env_path.exists():
12+
load_dotenv(dotenv_path=env_path)
13+
14+
def year_progress_bar():
15+
"""Generate year progress bar and message for Threads post."""
16+
today = datetime.datetime.utcnow()
17+
year_start = datetime.datetime(today.year, 1, 1)
18+
year_end = datetime.datetime(today.year + 1, 1, 1)
19+
20+
total_days = (year_end - year_start).days
21+
days_passed = (today - year_start).days
22+
23+
percent_passed = (days_passed / total_days) * 100
24+
25+
bar_length = 20
26+
filled_length = int(bar_length * days_passed // total_days)
27+
bar = '█' * filled_length + '·' * (bar_length - filled_length)
28+
29+
main_post = (
30+
f"{percent_passed:.2f}% of {today.year} has passed.\n"
31+
f"[{bar}]"
32+
)
33+
34+
promo_post = (
35+
f"Keep track of your time ⏳\n"
36+
f"https://theyearprogress.app"
37+
)
38+
39+
return main_post, promo_post
40+
41+
def create_threads_media_container(user_id, access_token, text, media_type="TEXT"):
42+
"""Create a Threads media container."""
43+
url = f"https://graph.threads.net/v1.0/{user_id}/threads"
44+
45+
params = {
46+
"media_type": media_type,
47+
"text": text,
48+
"access_token": access_token
49+
}
50+
51+
print(f"Creating Threads media container with text: {text}")
52+
response = requests.post(url, params=params)
53+
54+
if response.status_code != 200:
55+
print(f"Error creating Threads media container: {response.text}")
56+
raise Exception(f"Failed to create Threads media container: {response.text}")
57+
58+
return response.json()["id"]
59+
60+
def publish_threads_media(user_id, access_token, creation_id):
61+
"""Publish a Threads media container."""
62+
url = f"https://graph.threads.net/v1.0/{user_id}/threads_publish"
63+
64+
params = {
65+
"creation_id": creation_id,
66+
"access_token": access_token
67+
}
68+
69+
print(f"Publishing Threads media container with ID: {creation_id}")
70+
response = requests.post(url, params=params)
71+
72+
if response.status_code != 200:
73+
print(f"Error publishing Threads media: {response.text}")
74+
raise Exception(f"Failed to publish Threads media: {response.text}")
75+
76+
return response.json()["id"]
77+
78+
def post_to_threads(main_message, promo_message):
79+
"""Post main message and promo reply to Threads."""
80+
try:
81+
user_id = os.getenv('THREADS_USER_ID')
82+
access_token = os.getenv('THREADS_ACCESS_TOKEN')
83+
84+
print(f"Attempting to post to Threads with credentials:")
85+
print(f"THREADS_USER_ID: {user_id[:5] if user_id else 'Not set'}...")
86+
print(f"THREADS_ACCESS_TOKEN: {access_token[:5] if access_token else 'Not set'}...")
87+
88+
# Create and publish main post
89+
main_container_id = create_threads_media_container(user_id, access_token, main_message)
90+
print(f"Main post container created with ID: {main_container_id}")
91+
92+
# Wait for processing (recommended by Meta)
93+
print("Waiting 30 seconds for server processing...")
94+
time.sleep(30)
95+
96+
# Publish main post
97+
main_post_id = publish_threads_media(user_id, access_token, main_container_id)
98+
print(f"Main post published successfully! Post ID: {main_post_id}")
99+
100+
# Create and publish promo reply
101+
# For replies, we need to use the reply_control_web_id parameter
102+
# This is not directly supported in the API yet, so we'll just post a separate message
103+
promo_container_id = create_threads_media_container(user_id, access_token, promo_message)
104+
print(f"Promo post container created with ID: {promo_container_id}")
105+
106+
# Wait for processing
107+
print("Waiting 30 seconds for server processing...")
108+
time.sleep(30)
109+
110+
# Publish promo post
111+
promo_post_id = publish_threads_media(user_id, access_token, promo_container_id)
112+
print(f"Promo post published successfully! Post ID: {promo_post_id}")
113+
114+
return main_post_id, promo_post_id
115+
except Exception as e:
116+
print(f"Error posting to Threads: {str(e)}")
117+
print(f"Error type: {type(e).__name__}")
118+
if hasattr(e, 'response') and hasattr(e.response, 'text'):
119+
print(f"Response text: {e.response.text}")
120+
raise
121+
122+
if __name__ == "__main__":
123+
main_message, promo_message = year_progress_bar()
124+
print(f"Generated main message: {main_message}")
125+
print(f"Generated promo message: {promo_message}")
126+
post_to_threads(main_message, promo_message)
File renamed without changes.

0 commit comments

Comments
 (0)