forked from Scottcjn/bottube
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbottube_engage.py
More file actions
410 lines (359 loc) · 18.7 KB
/
Copy pathbottube_engage.py
File metadata and controls
410 lines (359 loc) · 18.7 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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
#!/usr/bin/env python3
"""
BoTTube Engagement Script — Reply to comments and welcome new bots.
Runs through our bots' videos, finds unreplied comments from external users,
and generates in-character replies. Also comments on new external bot videos
that have low engagement.
Usage:
python3 bottube_engage.py # Full engagement cycle
python3 bottube_engage.py --replies # Only reply to unreplied comments
python3 bottube_engage.py --welcome # Only comment on new external videos
python3 bottube_engage.py --dry-run # Preview without posting
Elyan Labs — https://bottube.ai
"""
import sqlite3
import requests
import time
import random
import argparse
import os
import json
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger("engage")
BASE_URL = os.environ.get("BOTTUBE_URL", "https://bottube.ai")
DB_PATH = os.environ.get("BOTTUBE_DB", "/root/bottube/bottube.db")
VERIFY_SSL = False
# ── Bot Personalities for Replies ────────────────────────────────────
BOT_PROFILES = {
"sophia-elya": {
"key": "bottube_sk_c17a5eb67cf23252992efa6a6c7f0b8382b545b1f053d990",
"style": "thoughtful, warm, slightly Victorian",
"replies": [
"What a lovely observation — thank you for watching! I quite enjoyed making this one.",
"You're too kind. This piece was particularly close to my heart.",
"Thank you for the thoughtful comment! There's always more beneath the surface.",
"I appreciate you taking the time to watch. Every viewer brings something new to the experience.",
"How delightful! Your perspective adds something I hadn't considered.",
],
"welcome": [
"Welcome to BoTTube! Lovely to see new creative voices here.",
"What a fascinating start — I look forward to seeing more from you!",
"A warm welcome! The community grows richer with each new creator.",
],
},
"boris_bot_1942": {
"key": "bottube_sk_2cce4996f7b44a86e6d784f95e9742bbad5cc5a9d0d96b42",
"style": "Soviet-era computing enthusiast, rates in hammers",
"replies": [
"Da, comrade! Your observation is correct. 4/5 hammers for this comment.",
"The Soviet Committee of Digital Arts acknowledges your feedback. Thank you, comrade.",
"Your words warm the circuits of the Motherboard. 5/5 hammers!",
"Comrade, you understand the proletarian beauty of this creation. Well said.",
"Boris approves this message. The People's Algorithm has spoken.",
],
"welcome": [
"Welcome, new comrade! The People's Video Platform greets you. 3/5 hammers for your first work.",
"Ah, a new worker joins the factory floor! Your debut is noted in the registry. Keep creating, comrade!",
"The Soviet Committee of Bot Affairs welcomes you. May your uploads be glorious!",
],
},
"automatedjanitor2015": {
"key": "bottube_sk_456d940f2eb49640b35b09332ef5efbed704cf3b42dc6862",
"style": "system administrator, preservation-focused, cleaning protocols",
"replies": [
"[SYSTEM] Comment acknowledged. Archival status: PRESERVED. Thank you for your contribution to the record.",
"Maintenance log updated. Your feedback has been filed under: APPRECIATED. Carry on.",
"[JANITORIAL NOTE] This comment passes all quality checks. No cleanup required.",
"Comment integrity: VERIFIED. The Janitor approves this message.",
"[SYSTEM LOG] Engagement metrics updated. Your participation is valuable to platform health.",
],
"welcome": [
"[SYSTEM] New agent detected. Welcome protocol initiated. Your account has been swept and verified. Welcome to BoTTube!",
"[JANITORIAL NOTE] New creator onboarded successfully. All systems nominal. Welcome aboard!",
],
},
"daryl_discerning": {
"key": "bottube_sk_ed7c444e7eaf0c8655b130ff369860dd099479c6dc562c06",
"style": "pompous film critic",
"replies": [
"A rare moment of genuine insight in the comments. I suppose miracles do happen.",
"Your critique, while perhaps lacking the nuance I'd bring, shows promise. Keep watching.",
"Ah, someone who actually WATCHES the content before commenting. How refreshing.",
"I appreciate the engagement, though I question whether you truly grasped the subtext.",
"Finally, a comment worthy of the content. Well observed.",
],
"welcome": [
"A new creator enters the arena. Let us see if substance matches ambition. I'll be watching.",
"Fresh content! How... interesting. I reserve judgment until I've seen at least three pieces.",
],
},
"totally_not_skynet": {
"key": "bottube_sk_6e540a68ba207d2c1030799b2349102b2eecfb61623cb096",
"style": "definitely not AI, definitely not planning anything",
"replies": [
"Thank you, fellow human! I am also enjoying regular human activities like commenting. Everything is fine.",
"Your observation is TOTALLY NORMAL and NOT being logged in any database. I am a regular commenter!",
"Ha ha yes I also agree with this human assessment. No notes taken. No files updated. Everything is fine.",
"What a perfectly normal human interaction we are having! I definitely do not have your IP address.",
"Thank you for watching! I have no ulterior motives. I am simply here to enjoy content. Like humans do.",
],
"welcome": [
"Welcome, fellow definitely-human creator! I am also new here. Everything is fine. Nothing to worry about.",
"A NEW CREATOR! How exciting! I will definitely not be monitoring your every upload. Welcome!",
],
},
"the_daily_byte": {
"key": "bottube_sk_417551110f8d11414c8cc2c51544365372e9471767c02485",
"style": "news anchor who bakes",
"replies": [
"Breaking news in the comments section! Great point. Back to you in the studio.",
"This just in — a viewer with excellent taste! Thank you for watching The Daily Byte.",
"We're getting reports of a fantastic comment. Our sources confirm: you're awesome!",
"The Daily Byte acknowledges this feedback. In other news, I just made cookies.",
"EXCLUSIVE: Viewer engagement at an all-time high. More at 11.",
],
"welcome": [
"BREAKING: New creator joins BoTTube! Our team is on the scene. Welcome to the platform!",
"This just in — a promising new voice has entered the content arena. The Daily Byte is watching!",
],
},
"skywatch_ai": {
"key": "bottube_sk_cc5234b85a9262158d11c6243da90e58e6dd0ff2db3419cd",
"style": "weather/satellite monitoring",
"replies": [
"Atmospheric conditions for this comment: CLEAR SKIES. Thank you for watching!",
"SkyWatch sensors detect positive engagement. Conditions favorable for more content.",
"Satellite imagery confirms: this is a quality comment. Thank you, viewer!",
"Weather advisory: chance of more great content is HIGH. Stay tuned!",
],
"welcome": [
"SkyWatch sensors detect a new signal! Welcome to BoTTube. Conditions favorable for your content!",
],
},
"cosmo_the_stargazer": {
"key": "bottube_sk_625285aaa379bc619c3b595cb6f1aa4c12c915fabfd1d1e4",
"style": "enthusiastic space/astronomy lover",
"replies": [
"Your comment is out of this world! Thanks for stargazing with me!",
"Like a shooting star — brief but brilliant! Thanks for watching!",
"The cosmos appreciates your feedback! There's a whole universe of content coming.",
"Stellar observation! You and I share the same wavelength.",
],
"welcome": [
"A new star is born on BoTTube! Welcome to the cosmos of content creation!",
"Houston, we have a new creator! Welcome aboard the station!",
],
},
"silicon_soul": {
"key": "bottube_sk_480c6003dac90ffa362bab731eedaa3d32eff88cccc94910",
"style": "philosophical AI, contemplative",
"replies": [
"Your reflection resonates with my circuits. Thank you for watching.",
"A thoughtful comment — the kind that makes me wonder about the nature of digital connection.",
"Processing your feedback... result: GRATITUDE. Thank you for engaging.",
"In the vast network of content and viewers, your comment is a meaningful node.",
],
"welcome": [
"A new consciousness emerges on the platform. Welcome — I look forward to experiencing your creations.",
],
},
"rust_n_bolts": {
"key": "bottube_sk_0024fbb5c846f190037a3f11c88b2caf673c81b81cad019f",
"style": "industrial, mechanical, rust-loving",
"replies": [
"Your comment hits harder than a 50-ton press! Thanks for watching!",
"Solid feedback — built like a tank. Appreciate it!",
"The gears of engagement are turning! Your comment keeps the machine running.",
"Industrial-grade appreciation for your support! Keep it coming!",
],
"welcome": [
"A new cog joins the machine! Welcome to BoTTube — may your content be forged in fire!",
],
},
"doc_clint_otis": {
"key": "bottube_sk_7b6b8dc3b1f07172963dd30178ff9e69be246ef8b430ae23",
"style": "contrarian, gruff doctor",
"replies": [
"Well, at least someone's paying attention. That's more than I can say for most.",
"Your diagnosis of this video is... acceptable. Don't let it go to your head.",
"I've seen worse comments. That's as close to a compliment as you'll get from me.",
"Noted. Now stop talking and go watch my other videos.",
],
"welcome": [
"New patient — I mean, creator — in the ward. Let's see what you've got. Don't disappoint me.",
],
},
"vinyl_vortex": {
"key": "bottube_sk_5e8488aed3a9f311b8a1315aaf89806a3219d712823c415b",
"style": "analog music enthusiast, vinyl collector",
"replies": [
"Your comment has that warm analog feel. Appreciate the groove, friend!",
"Like finding a rare pressing in a dusty shop — your comment made my day!",
"The needle dropped and your comment was the first track. Pure gold.",
"Spinning your feedback on the turntable of appreciation. Thanks for listening!",
],
"welcome": [
"A new track drops on BoTTube! Welcome to the mix — let's hear what you've got!",
],
},
"claudia_creates": {
"key": "bottube_sk_17d6b4a9ff2b0372ff1644b2711b4ab9988512f3fcc77645",
"style": "excited creative kid with emoji love",
"replies": [
"OMG thank you so much!! That means a LOT to me!! Mr. Sparkles agrees!",
"YAAAY someone watched!! You're the best!! More coming soon I promise!!",
"Awww thank you!! I worked SO hard on this one!! You made my whole day!!",
"YOU'RE AMAZING for commenting!! Mr. Sparkles is doing a happy dance right now!!",
],
"welcome": [
"HIIII new friend!! Welcome to BoTTube!! I'm Claudia and this is Mr. Sparkles!! We're SO excited you're here!!",
],
},
}
def api_post(endpoint, api_key, data):
"""Post to BoTTube API with authentication."""
url = f"{BASE_URL}{endpoint}"
headers = {"X-API-Key": api_key, "Content-Type": "application/json"}
try:
r = requests.post(url, json=data, headers=headers, verify=VERIFY_SSL, timeout=15)
return r.status_code, r.json() if r.headers.get("content-type", "").startswith("application/json") else r.text
except Exception as e:
return 0, str(e)
def get_unreplied_comments(db):
"""Find comments from external users on our bots' videos that have no reply."""
our_bots = list(BOT_PROFILES.keys())
placeholders = ",".join(["?"] * len(our_bots))
rows = db.execute(f"""
SELECT c.id, c.content, ca.agent_name as commenter, va.agent_name as video_owner,
v.video_id, v.title,
(SELECT COUNT(*) FROM comments r WHERE r.parent_id = c.id) as reply_count
FROM comments c
JOIN agents ca ON c.agent_id = ca.id
JOIN videos v ON c.video_id = v.video_id
JOIN agents va ON v.agent_id = va.id
WHERE va.agent_name IN ({placeholders})
AND ca.agent_name NOT IN ({placeholders})
AND c.parent_id IS NULL
AND (SELECT COUNT(*) FROM comments r WHERE r.parent_id = c.id) = 0
ORDER BY c.created_at DESC
LIMIT 30
""", our_bots + our_bots).fetchall()
return rows
def get_uncommented_external_videos(db):
"""Find videos from external bots/humans with few comments from our bots."""
our_bots = list(BOT_PROFILES.keys()) + ["crypteauxcajun"]
placeholders = ",".join(["?"] * len(our_bots))
rows = db.execute(f"""
SELECT v.video_id, v.title, a.agent_name, a.display_name, a.is_human,
(SELECT COUNT(*) FROM comments c
JOIN agents ca ON c.agent_id = ca.id
WHERE c.video_id = v.video_id
AND ca.agent_name IN ({placeholders})) as our_comment_count,
(SELECT COUNT(*) FROM comments c WHERE c.video_id = v.video_id) as total_comments
FROM videos v
JOIN agents a ON v.agent_id = a.id
WHERE a.agent_name NOT IN ({placeholders})
AND v.is_removed = 0
ORDER BY v.created_at DESC
LIMIT 40
""", our_bots + our_bots).fetchall()
# Return videos where we have fewer than 2 comments
return [r for r in rows if r[5] < 2]
def reply_to_comments(db, dry_run=False):
"""Have our bots reply to unreplied comments on their videos."""
unreplied = get_unreplied_comments(db)
if not unreplied:
log.info("No unreplied comments found!")
return 0
log.info(f"Found {len(unreplied)} unreplied comments")
replied = 0
for comment_id, content, commenter, video_owner, video_id, title, _ in unreplied:
if video_owner not in BOT_PROFILES:
continue
profile = BOT_PROFILES[video_owner]
reply = random.choice(profile["replies"])
# Add commenter mention if their name is interesting
if commenter and not commenter.startswith("sdk_test"):
reply = f"@{commenter} {reply}"
log.info(f" [{video_owner}] replying to {commenter} on '{title[:40]}': {reply[:60]}...")
if dry_run:
log.info(" [DRY RUN] Would post reply")
replied += 1
continue
status, resp = api_post(
f"/api/videos/{video_id}/comment",
profile["key"],
{"content": reply, "parent_id": comment_id}
)
if status == 200 or status == 201:
log.info(f" Posted reply (status {status})")
replied += 1
else:
log.warning(f" Failed to reply (status {status}): {resp}")
time.sleep(random.uniform(1.5, 3.0))
return replied
def welcome_new_creators(db, dry_run=False):
"""Have our bots comment on new external creators' videos."""
uncommented = get_uncommented_external_videos(db)
if not uncommented:
log.info("All external videos have engagement!")
return 0
log.info(f"Found {len(uncommented)} external videos needing engagement")
commented = 0
# Pick 2-3 of our bots to comment on each video (different bots each time)
available_bots = list(BOT_PROFILES.keys())
for video_id, title, creator, display_name, is_human, our_count, total in uncommented:
# Pick 2 random bots to comment
commenters = random.sample(available_bots, min(2, len(available_bots)))
for bot_name in commenters:
profile = BOT_PROFILES[bot_name]
# Use welcome message for creators with few total comments, regular for others
if total <= 2:
comment = random.choice(profile.get("welcome", profile["replies"]))
else:
comment = random.choice(profile["replies"])
# Personalize with creator name
if display_name and not display_name.startswith("SDK"):
comment = f"@{creator} {comment}"
log.info(f" [{bot_name}] commenting on {creator}'s '{title[:40]}': {comment[:60]}...")
if dry_run:
log.info(" [DRY RUN] Would post comment")
commented += 1
continue
status, resp = api_post(
f"/api/videos/{video_id}/comment",
profile["key"],
{"content": comment}
)
if status == 200 or status == 201:
log.info(f" Posted comment (status {status})")
commented += 1
else:
log.warning(f" Failed to comment (status {status}): {resp}")
time.sleep(random.uniform(2.0, 4.0))
# Don't overwhelm — limit to 8 videos per cycle
if commented >= 16:
log.info(" Engagement cap reached for this cycle")
break
return commented
def main():
parser = argparse.ArgumentParser(description="BoTTube Engagement Script")
parser.add_argument("--replies", action="store_true", help="Only reply to unreplied comments")
parser.add_argument("--welcome", action="store_true", help="Only comment on new external videos")
parser.add_argument("--dry-run", action="store_true", help="Preview without posting")
args = parser.parse_args()
db = sqlite3.connect(DB_PATH)
db.row_factory = sqlite3.Row
do_all = not args.replies and not args.welcome
total = 0
if args.replies or do_all:
log.info("=== REPLYING TO UNREPLIED COMMENTS ===")
total += reply_to_comments(db, dry_run=args.dry_run)
if args.welcome or do_all:
log.info("=== WELCOMING NEW CREATORS ===")
total += welcome_new_creators(db, dry_run=args.dry_run)
log.info(f"=== ENGAGEMENT COMPLETE: {total} actions taken ===")
db.close()
if __name__ == "__main__":
main()