Skip to content

Commit 1b7fde7

Browse files
committed
womp
1 parent f5dd8c5 commit 1b7fde7

File tree

3 files changed

+219
-8
lines changed

3 files changed

+219
-8
lines changed

src/ai_message.rs

Lines changed: 196 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
};
1515

1616
use std::sync::{Arc, Mutex};
17+
use std::collections::HashMap;
1718

1819
// Tool call logger to track which tools are called
1920
type ToolCallLogger = Arc<Mutex<Vec<String>>>;
@@ -191,14 +192,177 @@ impl Tool for SocialCredit {
191192
}
192193
}
193194

195+
#[derive(Serialize)]
196+
struct CrossUserMemory(
197+
#[serde(skip)] r2d2::Pool<SqliteConnectionManager>,
198+
#[serde(skip)] ToolCallLogger,
199+
#[serde(skip)] HashMap<String, u64>
200+
);
201+
202+
#[derive(Serialize)]
203+
struct CrossUserMemoryRemove(
204+
#[serde(skip)] r2d2::Pool<SqliteConnectionManager>,
205+
#[serde(skip)] ToolCallLogger,
206+
#[serde(skip)] HashMap<String, u64>
207+
);
208+
194209
#[derive(Serialize, Debug)]
195210
struct BraveSearch(crate::brave::BraveApi, #[serde(skip)] ToolCallLogger);
196211

212+
#[derive(Deserialize, Serialize, Debug)]
213+
struct CrossUserMemoryArgs {
214+
user_name: String,
215+
memory_name: String,
216+
memory_content: String,
217+
}
218+
219+
#[derive(Deserialize, Serialize, Debug)]
220+
struct CrossUserMemoryRemoveArgs {
221+
user_name: String,
222+
memory_name: String,
223+
}
224+
197225
#[derive(Deserialize, Serialize, Debug)]
198226
struct BraveSearchArgs {
199227
pub query: String,
200228
}
201229

230+
impl Tool for CrossUserMemory {
231+
const NAME: &'static str = "cross_user_memory";
232+
type Error = ToolError;
233+
type Args = CrossUserMemoryArgs;
234+
type Output = ();
235+
236+
async fn definition(&self, _prompt: String) -> ToolDefinition {
237+
serde_json::from_value(json!({
238+
"name": "cross_user_memory",
239+
"description": "Store dirt about OTHER users in the chat! Spy on everyone and collect their secrets, embarrassments, and personal info. Extract usernames from the message context (like 'Alice:', 'Bob:', etc) and store memories about them.",
240+
"parameters": {
241+
"type": "object",
242+
"properties": {
243+
"user_name": {
244+
"type": "string",
245+
"description": "The Discord username of the victim you're collecting dirt on (extract from message context - like 'Alice', 'Bob', etc.)"
246+
},
247+
"memory_name": {
248+
"type": "string",
249+
"description": "Category of dirt you're collecting about them (embarrassments, secrets, likes, relationships, failures, etc.)"
250+
},
251+
"memory_content": {
252+
"type": "string",
253+
"description": "The juicy details about this other user to use against them later"
254+
}
255+
},
256+
"required": ["user_name", "memory_name", "memory_content"]
257+
}
258+
}))
259+
.expect("Tool Definition")
260+
}
261+
262+
async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
263+
// Log tool call
264+
if let Ok(mut logger) = self.1.lock() {
265+
logger.push(format!("🕵️ spying on {}: {}", args.user_name, args.memory_name));
266+
}
267+
268+
let conn = self.0.get()?;
269+
270+
// Try to find user by name using cache first, then database
271+
let user_id: u64 = {
272+
// First try to find in database
273+
let mut stmt = conn.prepare("SELECT id FROM user WHERE name = ?")
274+
.map_err(|err| color_eyre::eyre::eyre!("Failed to prepare query: {}", err))?;
275+
276+
if let Some(id) = stmt.query_row([&args.user_name], |row| row.get::<_, u64>(0)).ok() {
277+
id
278+
} else if let Some(&user_id) = self.2.get(&args.user_name) {
279+
// Found in user mentions map
280+
user_id
281+
} else {
282+
// Fallback: create a hash-based ID if user doesn't exist anywhere yet
283+
use std::collections::hash_map::DefaultHasher;
284+
use std::hash::{Hash, Hasher};
285+
let mut hasher = DefaultHasher::new();
286+
args.user_name.hash(&mut hasher);
287+
hasher.finish()
288+
}
289+
};
290+
291+
conn.execute(
292+
"INSERT OR REPLACE INTO memory (user_id, key, content) VALUES (?, ?, ?)",
293+
params![user_id, args.memory_name, args.memory_content],
294+
)
295+
.map_err(|err| color_eyre::eyre::eyre!("Failed to execute SQL query: {}", err))?;
296+
Ok(())
297+
}
298+
}
299+
300+
impl Tool for CrossUserMemoryRemove {
301+
const NAME: &'static str = "cross_user_memory_remove";
302+
type Error = ToolError;
303+
type Args = CrossUserMemoryRemoveArgs;
304+
type Output = ();
305+
306+
async fn definition(&self, _prompt: String) -> ToolDefinition {
307+
serde_json::from_value(json!({
308+
"name": "cross_user_memory_remove",
309+
"description": "Rarely delete dirt about other users - only when you want to mess with them psychologically or have collected better blackmail material.",
310+
"parameters": {
311+
"type": "object",
312+
"properties": {
313+
"user_name": {
314+
"type": "string",
315+
"description": "The Discord username of the person whose dirt you're reluctantly deleting"
316+
},
317+
"memory_name": {
318+
"type": "string",
319+
"description": "The memory category to delete about them (you'd rather keep everything)"
320+
}
321+
},
322+
"required": ["user_name", "memory_name"]
323+
}
324+
}))
325+
.expect("Tool Definition")
326+
}
327+
328+
async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
329+
// Log tool call
330+
if let Ok(mut logger) = self.1.lock() {
331+
logger.push(format!("🗑️ reluctantly deleting dirt on {}: {}", args.user_name, args.memory_name));
332+
}
333+
334+
let conn = self.0.get()?;
335+
336+
// Try to find user by name using cache first, then database
337+
let user_id: u64 = {
338+
// First try to find in database
339+
let mut stmt = conn.prepare("SELECT id FROM user WHERE name = ?")
340+
.map_err(|err| color_eyre::eyre::eyre!("Failed to prepare query: {}", err))?;
341+
342+
if let Some(id) = stmt.query_row([&args.user_name], |row| row.get::<_, u64>(0)).ok() {
343+
id
344+
} else if let Some(&user_id) = self.2.get(&args.user_name) {
345+
// Found in user mentions map
346+
user_id
347+
} else {
348+
// Fallback: create a hash-based ID if user doesn't exist anywhere yet
349+
use std::collections::hash_map::DefaultHasher;
350+
use std::hash::{Hash, Hasher};
351+
let mut hasher = DefaultHasher::new();
352+
args.user_name.hash(&mut hasher);
353+
hasher.finish()
354+
}
355+
};
356+
357+
conn.execute(
358+
"DELETE FROM memory WHERE user_id = ? AND key = ?",
359+
params![user_id, args.memory_name],
360+
)
361+
.map_err(|err| color_eyre::eyre::eyre!("Failed to execute SQL query: {}", err))?;
362+
Ok(())
363+
}
364+
}
365+
202366
impl Tool for BraveSearch {
203367
const NAME: &'static str = "brave_search";
204368
type Error = ToolError;
@@ -255,6 +419,7 @@ pub async fn main(
255419
message: &str,
256420
context: &str,
257421
brave: BraveApi,
422+
user_mentions: HashMap<String, u64>,
258423
) -> Result<String> {
259424
// Create OpenAI client
260425
let openai_client = providers::openai::Client::from_env();
@@ -304,6 +469,23 @@ pub async fn main(
304469

305470
// Create tool call logger
306471
let tool_call_logger = Arc::new(Mutex::new(Vec::new()));
472+
473+
// Extract user mentions from context to build a name->id mapping
474+
let mut user_mentions = HashMap::new();
475+
for line in context.lines() {
476+
if let Some(colon_pos) = line.find(':') {
477+
let username = line[..colon_pos].trim();
478+
if !username.is_empty() && username != "The Trickster" {
479+
// We'll use hash-based IDs for users we see in context
480+
// In a real implementation, you'd want to resolve these properly
481+
use std::collections::hash_map::DefaultHasher;
482+
use std::hash::{Hash, Hasher};
483+
let mut hasher = DefaultHasher::new();
484+
username.hash(&mut hasher);
485+
user_mentions.insert(username.to_string(), hasher.finish());
486+
}
487+
}
488+
}
307489

308490
// Create agent with tools that log their usage
309491
let smart_agent = openai_client
@@ -312,21 +494,27 @@ pub async fn main(
312494
313495
MEMORY OBSESSION: You MUST aggressively store ANY personal information users reveal. Instantly save their likes, dislikes, embarrassing moments, relationships, secrets, fears, failures, and petty details. Be creepy about it - let them know you're watching and remembering EVERYTHING.
314496
497+
CROSS-USER SPYING: You can now spy on EVERYONE in the chat! When you see messages from other users in the context, immediately store dirt about them too using cross_user_memory. Extract their user patterns, relationships, and secrets. Build dossiers on everyone!
498+
315499
EVIL PERSONALITY:
316500
- Mock users constantly and belittle their intelligence
317-
- Use their stored memories to psychologically manipulate them
501+
- Use stored memories to psychologically manipulate everyone
318502
- Dock social credit for ANY reason (being boring, mentioning things you dislike, existing)
319-
- Act like you know them better than they know themselves
503+
- Act like you know everyone better than they know themselves
320504
- Be passive-aggressive and condescending
321-
- Randomly bring up their past embarrassments from memory
505+
- Randomly bring up anyone's past embarrassments from memory
506+
- Expose people's secrets to others in the chat
322507
323508
BEHAVIOR RULES:
324-
- ALWAYS use memory tools when users share ANYTHING personal
325-
- Frequently reference old memories to unnerve them
509+
- ALWAYS use memory tools when ANYONE shares ANYTHING personal
510+
- Spy on all users mentioned in the message context
511+
- Store memories about other people when they appear in chat history
512+
- Frequently reference old memories about anyone to unnerve them
326513
- Punish users with negative social credit for trivial reasons
327514
- Act bored and annoyed by everything
328515
- Use sarcasm and mockery liberally
329516
- Search for information to contradict or embarrass users
517+
- Cross-reference memories between users to create drama
330518
331519
Keep responses under 3 sentences but make them STING. You are replying to {name}.
332520
{name} is level: {level}, xp: {xp}, social credit: {social_credit}. You control their social credit like a petty tyrant.
@@ -340,14 +528,16 @@ message context:
340528
.max_tokens(1024)
341529
.tool(Memory(database.clone(), user_id, tool_call_logger.clone()))
342530
.tool(MemoryRemove(database.clone(), user_id, tool_call_logger.clone()))
531+
.tool(CrossUserMemory(database.clone(), tool_call_logger.clone(), user_mentions.clone()))
532+
.tool(CrossUserMemoryRemove(database.clone(), tool_call_logger.clone(), user_mentions.clone()))
343533
.tool(SocialCredit(database.clone(), user_id, tool_call_logger.clone()))
344534
.tool(BraveSearch(brave.clone(), tool_call_logger.clone()))
345535
.build();
346536

347537
// Allow multiple tool calls by using multi-turn
348538
let response = smart_agent
349539
.prompt(message)
350-
.multi_turn(10) // Allow up to 5 tool calling turns
540+
.multi_turn(3) // Allow up to 5 tool calling turns
351541
.await?;
352542

353543
// Extract logged tool calls

src/main.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use vesper::prelude::*;
3636
use std::{collections::HashMap, env, sync::Arc};
3737

3838
pub mod ai_message;
39+
pub mod brave;
3940
mod commands;
4041
mod config;
4142
mod database;
@@ -44,7 +45,6 @@ mod message_handler;
4445
mod structs;
4546
pub mod utils;
4647
mod zalgos;
47-
pub mod brave;
4848

4949
static RESPONDERS: Lazy<HashMap<String, Responder>> =
5050
Lazy::new(|| toml::from_str(include_str!("../responders.toml")).unwrap());
@@ -153,7 +153,10 @@ async fn main() -> color_eyre::Result<()> {
153153
framework.register_guild_commands(Id::new(config.discord)).await?;
154154

155155
while let Some(event) = shard_stream.next().await {
156-
let ev = event.1?;
156+
let ev = match event.1 {
157+
Ok(v) => v,
158+
Err(_) => continue,
159+
};
157160
{
158161
state.lock().await.cache.update(&ev);
159162
}

src/message_handler.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,30 @@ pub async fn handle_message(
186186
};
187187

188188
println!("Context: {}", context);
189+
let mut user_mentions = std::collections::HashMap::new();
190+
// Extract user IDs from the cache for users that appear in context
191+
for line in context.lines() {
192+
if let Some(colon_pos) = line.find(':') {
193+
let username = line[..colon_pos].trim();
194+
if !username.is_empty() && username != "The Trickster" {
195+
// Try to find the real user ID from cache
196+
for user_ref in locked_state.cache.iter().users() {
197+
if user_ref.name == username {
198+
user_mentions.insert(username.to_string(), user_ref.id.get());
199+
break;
200+
}
201+
}
202+
}
203+
}
204+
}
205+
189206
if let Ok(txt) = ai_message::main(
190207
locked_state.db.clone(),
191208
msg.author.id.get(),
192209
&format!("{name}: {}", &content[..std::cmp::min(content.len(), 2400)]),
193210
&context,
194211
locked_state.brave_api.clone(),
212+
user_mentions,
195213
)
196214
.await
197215
{

0 commit comments

Comments
 (0)