Generic link placeholders, code cleanup

- Links now use <placeholder> syntax in prompts, replaced after AI generation
- Removed dead existsSync import
- Moved generation instructions into prompts.json (out of buildPrompt)
- Fixed variable shadowing (url -> linkUrl, page -> pageState)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Jackson
2026-03-07 23:07:36 +00:00
parent 1067ec2f78
commit 417f943239
2 changed files with 20 additions and 15 deletions

26
bot.js
View File

@@ -1,6 +1,6 @@
import "dotenv/config"; import "dotenv/config";
import Kernel from "@onkernel/sdk"; import Kernel from "@onkernel/sdk";
import { readFileSync, writeFileSync, existsSync } from "fs"; import { readFileSync, writeFileSync } from "fs";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import { dirname, join } from "path"; import { dirname, join } from "path";
@@ -50,7 +50,6 @@ function shouldRun(promptConfig) {
function buildPrompt(promptConfig, history) { function buildPrompt(promptConfig, history) {
let prompt = promptConfig.prompt; let prompt = promptConfig.prompt;
prompt += "\n\nGenerate 1 tweet. Keep it under 280 characters.\n\nDo not explain the tweet. Do not add options. Just output the tweet.";
if (history.length > 0) { if (history.length > 0) {
const recent = history.slice(-20).map((h) => h.tweet).join("\n---\n"); const recent = history.slice(-20).map((h) => h.tweet).join("\n---\n");
@@ -132,11 +131,11 @@ async function login(kernel, sessionId) {
console.log("Username:", r1.success ? "ok" : r1.error); console.log("Username:", r1.success ? "ok" : r1.error);
if (!r1.success) throw new Error("Username failed: " + r1.error); if (!r1.success) throw new Error("Username failed: " + r1.error);
let page = await readPage(kernel, sessionId); let pageState = await readPage(kernel, sessionId);
console.log("After username:", JSON.stringify(page, null, 2)); console.log("After username:", JSON.stringify(pageState, null, 2));
const hasPassword = page.inputs.some((i) => i.type === "password"); const hasPassword = pageState.inputs.some((i) => i.type === "password");
const pageText = page.texts.join(" ").toLowerCase(); const pageText = pageState.texts.join(" ").toLowerCase();
if (!hasPassword && (pageText.includes("unusual login") || pageText.includes("verify") || pageText.includes("confirm"))) { if (!hasPassword && (pageText.includes("unusual login") || pageText.includes("verify") || pageText.includes("confirm"))) {
let verifyValue; let verifyValue;
@@ -161,7 +160,7 @@ async function login(kernel, sessionId) {
`, 30); `, 30);
if (!rv.success) throw new Error("Verification failed: " + rv.error); if (!rv.success) throw new Error("Verification failed: " + rv.error);
page = await readPage(kernel, sessionId); pageState = await readPage(kernel, sessionId);
} }
const r2 = await exec(kernel, sessionId, ` const r2 = await exec(kernel, sessionId, `
@@ -176,9 +175,9 @@ async function login(kernel, sessionId) {
`, 30); `, 30);
if (!r2.success) throw new Error("Password failed: " + r2.error); if (!r2.success) throw new Error("Password failed: " + r2.error);
page = await readPage(kernel, sessionId); pageState = await readPage(kernel, sessionId);
console.log("Logged in:", page.url); console.log("Logged in:", pageState.url);
return page.url; return pageState.url;
} }
async function postTweet(kernel, sessionId, tweetText) { async function postTweet(kernel, sessionId, tweetText) {
@@ -242,7 +241,12 @@ async function main() {
const history = loadHistory(promptName); const history = loadHistory(promptName);
const rawTweet = await generateTweet(promptConfig, history); const rawTweet = await generateTweet(promptConfig, history);
const tweet = promptConfig.link ? rawTweet + "\n\n" + promptConfig.link : rawTweet; let tweet = rawTweet;
if (promptConfig.links) {
for (const [key, linkUrl] of Object.entries(promptConfig.links)) {
tweet = tweet.replaceAll(`<${key}>`, linkUrl);
}
}
console.log(`[${promptName}] Tweet: "${tweet}"`); console.log(`[${promptName}] Tweet: "${tweet}"`);
await postTweet(kernel, sessionId, tweet); await postTweet(kernel, sessionId, tweet);

View File

@@ -1,17 +1,18 @@
[ [
{ {
"name": "tease", "name": "tease",
"prompt": "You are writing a tweet for a woman who is quietly building a creative portfolio and wants people to visit her site out of curiosity.\n\nTone: casual, understated, real. Like she is just talking \u2014 not performing, not selling, not trying too hard. No hashtags. No emojis unless they feel completely natural. Never use the word \"model\" or any fashion/influencer language.\n\nThe goal is to make someone curious enough to click the link. The best tweets hint at something without explaining it. They feel personal but not oversharing. Confident but not loud.", "prompt": "You are writing a tweet for a woman who is quietly building a creative portfolio and wants people to visit her site out of curiosity.\n\nTone: casual, understated, real. Like she is just talking \u2014 not performing, not selling, not trying too hard. No hashtags. No emojis unless they feel completely natural. Never use the word \"model\" or any fashion/influencer language.\n\nThe goal is to make someone curious enough to click the link. The best tweets hint at something without explaining it. They feel personal but not oversharing. Confident but not loud.\n\nGenerate 1 tweet. Keep it under 280 characters. Just the tweet, nothing else.\n\nEnd the tweet with <link> on its own line.",
"link": "https://onlyfans.com/juniper_sky",
"frequency": "random", "frequency": "random",
"startHour": 8, "startHour": 8,
"endHour": 20, "endHour": 20,
"minDays": 2 "minDays": 2,
"links": {
"link": "https://onlyfans.com/juniper_sky"
}
}, },
{ {
"name": "personality", "name": "personality",
"prompt": "You are writing a tweet for a woman in her late 20s to late 30s living in the Pacific Northwest. She reads constantly, makes pottery, spends time in the woods, and has a declining interest in being perceived.\n\nTone: dry, witty, slightly feral, cozy. Dark humor, sarcastic observations, occasional leftist commentary. Never preachy. Never try-hard. Feels like something she muttered to herself and decided to post.\n\nNo hashtags. No emojis unless they earn it. No links. No advertising. Nothing aspirational or girlboss. Just something real that makes someone think \"oh that is me.\"\n\nGenerate 1 tweet. Just the tweet. Nothing else.", "prompt": "You are writing a tweet for a woman in her late 20s to late 30s living in the Pacific Northwest. She reads constantly, makes pottery, spends time in the woods, and has a declining interest in being perceived.\n\nTone: dry, witty, slightly feral, cozy. Dark humor, sarcastic observations, occasional leftist commentary. Never preachy. Never try-hard. Feels like something she muttered to herself and decided to post.\n\nNo hashtags. No emojis unless they earn it. No links. No advertising. Nothing aspirational or girlboss. Just something real that makes someone think \"oh that is me.\"\n\nGenerate 1 tweet. Just the tweet. Nothing else.",
"link": null,
"frequency": "daily", "frequency": "daily",
"startHour": 8, "startHour": 8,
"endHour": 20 "endHour": 20