import generator , { Entity } from "npm:megalodon@5.0.6" ;
import { Configuration , OpenAIApi } from "npm:openai@3.2.1" ;
import emojiConverter from "npm:discord-emoji-converter@1.2.8" ;
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 ] } . ` ;
}
async function postStatus (
messagesArray : {
role : "system" | "assistant" | "user" ;
content : string ;
} [ ] ,
visibility : "public" | "unlisted" | "private" | "direct" ,
reply_id? : string ,
successCallback ? : ( ) = > void ,
) {
const response = await openai . createChatCompletion ( {
model : "gpt-3.5-turbo" ,
messages : messagesArray ,
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 ( emojiConverter . emojify ( postInfo . content ) , {
visibility ,
in_reply_to_id : reply_id ,
} ) ;
console . log ( "Posted post!" ) ;
( successCallback ? ? ( ( ) = > { } ) ) ( ) ;
} catch {
console . log ( "Failed to post post; will try again next time." ) ;
}
}
}
const 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 ,
} ) ;
await postStatus ( newMessages , "public" ) ;
lastPost = Math . floor ( Date . now ( ) / 1000 ) ;
}
// Reply handling
let notifs = ( await fedi . getNotifications ( ) ) . data ;
for ( const notif of notifs ) {
if ( ! ( respondedNotificationIds . includes ( notif . id ) ) ) {
console . log ( ` Recieved new notification! (ID: ${ notif . id } ) ` ) ;
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
) {
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}" } ` ,
} ) ;
}
}
await postStatus (
newMessages ,
status . visibility ,
notif . status ? . id ,
( ) = > respondedNotificationIds . push ( notif . id ) ,
) ;
} 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 ,
} ) ;
await postStatus (
newMessages ,
status . visibility ,
status . id ,
( ) = > respondedNotificationIds . push ( notif . id ) ,
) ;
}
} else {
respondedNotificationIds . push ( notif . id ) ;
}
}
}
Deno . writeTextFileSync (
"_responded_notifs.json" ,
JSON . stringify ( respondedNotificationIds ) ,
) ;
await new Promise ( ( res ) = > setTimeout ( res , 30000 ) ) ;
}