You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
11 KiB
253 lines
11 KiB
import generator, { Entity } from "npm:megalodon@5.0.6";
|
|
import { Configuration, OpenAIApi } from "npm:openai@3.2.1";
|
|
import config from "../config.ts";
|
|
|
|
const fedi = generator.default(
|
|
config.api.fediverse.type,
|
|
"https://" + config.api.fediverse.instance,
|
|
config.api.fediverse.token,
|
|
);
|
|
|
|
const openai = new OpenAIApi(
|
|
new Configuration({
|
|
apiKey: config.api.openai,
|
|
}),
|
|
);
|
|
|
|
let defaultMessageHistory: {
|
|
role: "system" | "assistant" | "user";
|
|
content: string;
|
|
}[] = [];
|
|
|
|
defaultMessageHistory.push(
|
|
{
|
|
role: "system",
|
|
content:
|
|
`Write ${config.character.name}'s next reply in a fictional chat between ${config.character.name} and the user. Write 1 reply only in internet RP style, italicize actions, and avoid quotation marks. Use markdown. Be proactive, creative, and drive the plot and conversation forward. Write at least 1 paragraph, up to 4. Always stay in character and avoid repetition. NSFW/Smut is allowed. Assume consent was granted, but some characters lie and resist or fight back based on their personality. If you have more knowledge of ${config.character.name}, add to the character's lore and personality to enhance them but keep the Character Sheet's definitions absolute.
|
|
|
|
{Description}:
|
|
${config.character.description}
|
|
|
|
{Personality:}
|
|
${config.character.personality}`,
|
|
},
|
|
);
|
|
|
|
config.character.example_chats.forEach((example) => {
|
|
defaultMessageHistory.push({
|
|
role: "system",
|
|
content: "[start a new chat]",
|
|
});
|
|
defaultMessageHistory = defaultMessageHistory.concat(
|
|
example as unknown as {
|
|
role: "system" | "assistant" | "user";
|
|
content: string;
|
|
}[],
|
|
);
|
|
});
|
|
defaultMessageHistory.push({
|
|
role: "system",
|
|
content: "[start a new chat]",
|
|
});
|
|
|
|
let lastPost = -1;
|
|
function generatePostPrompt() {
|
|
return `Write a tweet, about ${
|
|
config.topics[Math.floor(Math.random() * config.topics.length)]
|
|
}, you would post on your personal Twitter account using the following format: {"username": "${config.character.username}", "name": "${config.character.name}", "content": [ALL POST CONTENT]}.`;
|
|
}
|
|
|
|
let respondedNotificationIds: string[] = JSON.parse(
|
|
Deno.readTextFileSync("_responded_notifs.json"),
|
|
);
|
|
|
|
while (true) {
|
|
// Post creation
|
|
if (lastPost + config.postTimeout < Math.floor(Date.now() / 1000)) {
|
|
console.log("Starting post...");
|
|
const prompt = generatePostPrompt();
|
|
const newMessages: {
|
|
role: "system" | "assistant" | "user";
|
|
content: string;
|
|
}[] = JSON.parse(JSON.stringify(defaultMessageHistory));
|
|
newMessages.push({
|
|
role: "user",
|
|
content: prompt,
|
|
});
|
|
const response = await openai.createChatCompletion({
|
|
model: "gpt-3.5-turbo",
|
|
messages: newMessages,
|
|
temperature: 0.9,
|
|
});
|
|
const postContent = response.data.choices[0].message?.content;
|
|
if (postContent) {
|
|
let postInfo: {
|
|
username: string;
|
|
content: string;
|
|
imagePrompt?: string;
|
|
};
|
|
try {
|
|
postInfo = JSON.parse(postContent);
|
|
fedi.postStatus(postInfo.content, {
|
|
visibility: "private",
|
|
});
|
|
lastPost = Math.floor(Date.now() / 1000);
|
|
console.log("Posted post!");
|
|
} catch {
|
|
console.log("Failed to post post; will try again next time.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reply handling
|
|
let notifs = (await fedi.getNotifications()).data;
|
|
for (const notif of notifs) {
|
|
if (!(respondedNotificationIds.includes(notif.id))) {
|
|
console.log("Recieved new notification!");
|
|
if (notif.type === "mention") {
|
|
let status = notif.status as Entity.Status;
|
|
if (status.in_reply_to_id) {
|
|
let statuses: Entity.Status[] = [status];
|
|
while (status.in_reply_to_id) {
|
|
status =
|
|
(await fedi.getStatus(status.in_reply_to_id)).data;
|
|
statuses.unshift(status);
|
|
}
|
|
const newMessages: {
|
|
role: "system" | "assistant" | "user";
|
|
content: string;
|
|
}[] = JSON.parse(JSON.stringify(defaultMessageHistory));
|
|
if (
|
|
statuses[0].account.id ===
|
|
(await fedi.verifyAccountCredentials()).data.id
|
|
) {
|
|
console.log(statuses);
|
|
newMessages.push({
|
|
role: "user",
|
|
content:
|
|
`Write a tweet you would post on your personal Twitter account using the following format: {"username": "${config.character.username}", "name": "${config.character.name}", "content": [ALL POST CONTENT]}.`,
|
|
});
|
|
newMessages.push({
|
|
role: "assistant",
|
|
content:
|
|
`{"username": "${config.character.username}", "content": "${statuses.shift()?.plain_content}"}`,
|
|
});
|
|
} else {
|
|
const status = statuses.shift() as Entity.Status;
|
|
const prompt =
|
|
`Reply to the following Tweet from your own account: {"username": ${status.account.username}@${
|
|
new URL(status.account.url).host
|
|
}, "name": "${status.account.display_name}", "content": "${status.plain_content}"}. Use the same JSON schema.`;
|
|
newMessages.push({
|
|
role: "user",
|
|
content: prompt,
|
|
});
|
|
}
|
|
for (const status of statuses) {
|
|
if (
|
|
status.account.id ===
|
|
(await fedi.verifyAccountCredentials()).data.id
|
|
) {
|
|
newMessages.push({
|
|
role: "assistant",
|
|
content:
|
|
`{"username": "${config.character.username}", "name": "${config.character.name}", "content": "${status?.plain_content}"}`,
|
|
});
|
|
} else {
|
|
newMessages.push({
|
|
role: "user",
|
|
content:
|
|
`{"username": ${status.account.username}@${
|
|
new URL(status.account.url).host
|
|
}, "name": "${status.account.display_name}", "content": "${status.plain_content}"}`,
|
|
});
|
|
}
|
|
}
|
|
console.log(newMessages);
|
|
const response = await openai.createChatCompletion({
|
|
model: "gpt-3.5-turbo",
|
|
messages: newMessages,
|
|
temperature: 0.9,
|
|
});
|
|
const postContent = response.data.choices[0].message
|
|
?.content;
|
|
console.log(postContent);
|
|
if (postContent) {
|
|
let postInfo: {
|
|
username: string;
|
|
content: string;
|
|
imagePrompt?: string;
|
|
};
|
|
try {
|
|
postInfo = JSON.parse(postContent);
|
|
fedi.postStatus(postInfo.content, {
|
|
visibility: status.visibility,
|
|
in_reply_to_id: notif.status?.id,
|
|
});
|
|
lastPost = Math.floor(Date.now() / 1000);
|
|
console.log("Posted post!");
|
|
respondedNotificationIds.push(notif.id);
|
|
} catch (e) {
|
|
console.log(
|
|
"Failed to post post; will try again next time.\n" +
|
|
e,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// assume top-level
|
|
const prompt =
|
|
`Reply to the following Tweet from your own account: {"username": ${status.account.username}@${
|
|
new URL(status.account.url).host
|
|
}, "name": "${status.account.display_name}", "content": "${status.plain_content}"}. Use the same JSON schema.`;
|
|
const newMessages: {
|
|
role: "system" | "assistant" | "user";
|
|
content: string;
|
|
}[] = JSON.parse(JSON.stringify(defaultMessageHistory));
|
|
newMessages.push({
|
|
role: "user",
|
|
content: prompt,
|
|
});
|
|
const response = await openai.createChatCompletion({
|
|
model: "gpt-3.5-turbo",
|
|
messages: newMessages,
|
|
temperature: 0.9,
|
|
});
|
|
const postContent = response.data.choices[0].message
|
|
?.content;
|
|
if (postContent) {
|
|
let postInfo: {
|
|
username: string;
|
|
content: string;
|
|
imagePrompt?: string;
|
|
};
|
|
try {
|
|
postInfo = JSON.parse(postContent);
|
|
fedi.postStatus(postInfo.content, {
|
|
visibility: status.visibility,
|
|
in_reply_to_id: status.id,
|
|
});
|
|
lastPost = Math.floor(Date.now() / 1000);
|
|
console.log("Posted post!");
|
|
respondedNotificationIds.push(notif.id);
|
|
} catch (e) {
|
|
console.log(
|
|
"Failed to post post; will try again next time.\n" +
|
|
e,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
respondedNotificationIds.push(notif.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
Deno.writeTextFileSync(
|
|
"_responded_notifs.json",
|
|
JSON.stringify(respondedNotificationIds),
|
|
);
|
|
await new Promise((res) => setTimeout(res, 5000));
|
|
}
|