-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathcli.py
More file actions
executable file
·254 lines (198 loc) · 10.3 KB
/
Copy pathcli.py
File metadata and controls
executable file
·254 lines (198 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#!/usr/bin/env python3
"""
Twitter Auto-Post Bot CLI
A command-line interface for managing automated Twitter posts.
"""
import click
import sys
import os
import schedule
import time
import random
sys.path.append(os.path.join(os.path.dirname(__file__), 'config'))
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
import keys
from src.functions import generate_response, initialize_tweepy, get_formatted_date
@click.group()
@click.version_option(version='1.1.0')
def cli():
"""Twitter Auto-Post Bot - Automate your Twitter presence."""
pass
@cli.command()
@click.option('--ai', is_flag=True, help='Generate tweet using AI')
@click.option('--file', 'from_file', is_flag=True, help='Post random tweet from file')
@click.option('--text', help='Post custom text')
@click.option('--prompt', default='Create a short tweet about Motorbikes.', help='Custom prompt for AI (only with --ai)')
def post(ai, from_file, text, prompt):
"""Post a tweet instantly."""
if sum([ai, from_file, bool(text)]) != 1:
click.echo(click.style('Error: Please specify exactly one option: --ai, --file, or --text', fg='red'))
sys.exit(1)
try:
client, _ = initialize_tweepy()
if ai:
click.echo(click.style(f'Generating tweet with AI...', fg='yellow'))
click.echo(click.style(f'Prompt: {prompt}', fg='cyan'))
response = generate_response(prompt)
tweet_text = response
click.echo(click.style(f'\nGenerated tweet: {tweet_text}', fg='green'))
elif from_file:
tweets_file = os.path.join(os.path.dirname(__file__), 'data', 'tweets.txt')
if not os.path.exists(tweets_file):
click.echo(click.style(f'Error: tweets.txt not found at {tweets_file}', fg='red'))
sys.exit(1)
with open(tweets_file, 'r') as file:
lines = [line.strip() for line in file.readlines() if line.strip()]
if not lines:
click.echo(click.style('Error: tweets.txt is empty', fg='red'))
sys.exit(1)
tweet_text = random.choice(lines)
click.echo(click.style(f'Selected tweet: {tweet_text}', fg='green'))
else:
tweet_text = text
click.echo(click.style(f'Posting: {tweet_text}', fg='green'))
client.create_tweet(text=tweet_text)
click.echo(click.style('Tweet posted successfully!', fg='green', bold=True))
except Exception as e:
click.echo(click.style(f'Error posting tweet: {str(e)}', fg='red'))
sys.exit(1)
@cli.command()
@click.option('--ai', is_flag=True, help='Schedule AI-generated tweets')
@click.option('--file', 'from_file', is_flag=True, help='Schedule tweets from file')
@click.option('--time', 'schedule_time', default='09:00', help='Time to post daily (HH:MM format, default: 09:00)')
@click.option('--every', type=int, default=None, help='Post every N units (interval mode)')
@click.option('--unit', type=click.Choice(['minutes', 'hours', 'days']), default='hours',
help='Interval unit (with --every)')
@click.option('--prompt', default='Create a short tweet about Motorbikes.', help='Custom prompt for AI (only with --ai)')
def schedule_posts(ai, from_file, schedule_time, prompt, every, unit):
"""Schedule daily tweets at a specific time."""
if not ai and not from_file:
click.echo(click.style('Error: Please specify either --ai or --file', fg='red'))
sys.exit(1)
if ai and from_file:
click.echo(click.style('Error: Please specify only one option: --ai or --file', fg='red'))
sys.exit(1)
# Detect whether --time was explicitly passed (not just left at its default),
# so that "--time 09:00 --every ..." is still caught as a conflict.
ctx = click.get_current_context()
time_given = ctx.get_parameter_source('schedule_time') != click.core.ParameterSource.DEFAULT
if time_given and every is not None:
click.echo(click.style('Error: --time and --every are mutually exclusive', fg='red'))
sys.exit(1)
from src.functions import schedule_job, interval_is_short
try:
client, _ = initialize_tweepy()
if ai:
def send_ai_post():
try:
click.echo(click.style(f'\nGenerating and posting tweet...', fg='yellow'))
response = generate_response(prompt)
client.create_tweet(text=response)
click.echo(click.style(f'Tweet posted: {response}', fg='green'))
click.echo(click.style(f'Next post scheduled for tomorrow at {schedule_time}', fg='cyan'))
except Exception as e:
click.echo(click.style(f'Error posting tweet: {str(e)}', fg='red'))
if every is not None:
if interval_is_short(every, unit):
click.echo(click.style(
f'Warning: posting every {every} {unit} is very frequent and may hit X rate limits.',
fg='yellow'))
schedule_job(send_ai_post, mode='interval', every=every, unit=unit)
click.echo(click.style(f'Scheduled AI tweets every {every} {unit}', fg='green', bold=True))
else:
schedule_job(send_ai_post, mode='daily', at_time=schedule_time)
click.echo(click.style(f'Scheduled AI-generated tweets daily at {schedule_time}', fg='green', bold=True))
click.echo(click.style(f'Prompt: {prompt}', fg='cyan'))
else:
tweets_file = os.path.join(os.path.dirname(__file__), 'data', 'tweets.txt')
if not os.path.exists(tweets_file):
click.echo(click.style(f'Error: tweets.txt not found at {tweets_file}', fg='red'))
sys.exit(1)
def send_file_post():
try:
with open(tweets_file, 'r') as file:
lines = [line.strip() for line in file.readlines() if line.strip()]
if not lines:
click.echo(click.style('Error: tweets.txt is empty', fg='red'))
return
tweet_text = random.choice(lines)
client.create_tweet(text=tweet_text)
click.echo(click.style(f'\nTweet posted: {tweet_text}', fg='green'))
click.echo(click.style(f'Next post scheduled for tomorrow at {schedule_time}', fg='cyan'))
except Exception as e:
click.echo(click.style(f'Error posting tweet: {str(e)}', fg='red'))
if every is not None:
if interval_is_short(every, unit):
click.echo(click.style(
f'Warning: posting every {every} {unit} is very frequent and may hit X rate limits.',
fg='yellow'))
schedule_job(send_file_post, mode='interval', every=every, unit=unit)
click.echo(click.style(f'Scheduled file tweets every {every} {unit}', fg='green', bold=True))
else:
schedule_job(send_file_post, mode='daily', at_time=schedule_time)
click.echo(click.style(f'Scheduled tweets from file daily at {schedule_time}', fg='green', bold=True))
click.echo(click.style('\nPress Ctrl+C to stop the scheduler', fg='yellow'))
while True:
schedule.run_pending()
time.sleep(60)
except KeyboardInterrupt:
click.echo(click.style('\n\nScheduler stopped.', fg='yellow'))
except Exception as e:
click.echo(click.style(f'Error: {str(e)}', fg='red'))
sys.exit(1)
@cli.command()
def test():
"""Test Twitter API credentials."""
try:
click.echo(click.style('Testing Twitter API credentials...', fg='yellow'))
client, api = initialize_tweepy()
user = api.verify_credentials()
click.echo(click.style('\nCredentials verified successfully!', fg='green', bold=True))
click.echo(click.style(f'Authenticated as: @{user.screen_name}', fg='cyan'))
click.echo(click.style(f'Name: {user.name}', fg='cyan'))
click.echo(click.style(f'Followers: {user.followers_count}', fg='cyan'))
click.echo(click.style(f'Following: {user.friends_count}', fg='cyan'))
except Exception as e:
click.echo(click.style(f'\nCredentials test failed: {str(e)}', fg='red'))
click.echo(click.style('\nPlease check your credentials in config/keys.py', fg='yellow'))
sys.exit(1)
@cli.command()
def list_tweets():
"""List all tweets from the tweets.txt file."""
tweets_file = os.path.join(os.path.dirname(__file__), 'data', 'tweets.txt')
if not os.path.exists(tweets_file):
click.echo(click.style(f'Error: tweets.txt not found at {tweets_file}', fg='red'))
sys.exit(1)
try:
with open(tweets_file, 'r') as file:
lines = [line.strip() for line in file.readlines() if line.strip()]
if not lines:
click.echo(click.style('tweets.txt is empty. Add some tweets first!', fg='yellow'))
return
click.echo(click.style(f'\nFound {len(lines)} tweet(s) in file:', fg='green', bold=True))
click.echo()
for i, tweet in enumerate(lines, 1):
click.echo(click.style(f'{i}. ', fg='cyan') + tweet)
except Exception as e:
click.echo(click.style(f'Error reading tweets file: {str(e)}', fg='red'))
sys.exit(1)
@cli.command()
def add_tweet():
"""Add a new tweet to the tweets.txt file interactively."""
tweets_file = os.path.join(os.path.dirname(__file__), 'data', 'tweets.txt')
os.makedirs(os.path.dirname(tweets_file), exist_ok=True)
tweet = click.prompt(click.style('Enter your tweet', fg='cyan'))
if len(tweet) > 280:
click.echo(click.style(f'Warning: Tweet is {len(tweet)} characters (Twitter limit is 280)', fg='yellow'))
if not click.confirm(click.style('Do you want to add it anyway?', fg='yellow')):
click.echo(click.style('Tweet not added.', fg='red'))
return
try:
with open(tweets_file, 'a') as file:
file.write(tweet + '\n')
click.echo(click.style('\nTweet added successfully!', fg='green', bold=True))
except Exception as e:
click.echo(click.style(f'Error adding tweet: {str(e)}', fg='red'))
sys.exit(1)
if __name__ == '__main__':
cli()