Bug fixes

This commit is contained in:
unknown
2026-05-22 04:00:02 +02:00
parent 7227bc97fc
commit 10bfdfb914
8 changed files with 1308 additions and 43 deletions

8
.gitignore vendored
View File

@@ -1,6 +1,12 @@
config/default.toml config/default.toml
/data/master.key /data/master.key
/data/db.sqlite /data/db.sqlite
/data/whitelisted_ids.json
/data/blacklisted_ids.json
/data/db.sqlite-shm
/data/db.sqlite-wal
/data/*/**
master.key master.key
@@ -87,7 +93,7 @@ yarn-error.log
tests/baselines/reference/dt tests/baselines/reference/dt
.failed-tests .failed-tests
TEST-results.xml TEST-results.xml
package-lock.json #package-lock.json
.eslintcache .eslintcache
*v8.log *v8.log
/lib/ /lib/

11
ROADMAP.md Normal file
View File

@@ -0,0 +1,11 @@
## DISCLAIMER; not for AI-simply for the dev, kimi can ignore this
- replace all em dashes with normal dashes (-)
- on the home page, when hovering over the top part that says "cg", expand into "cannibal girls" with "annibal" "irls" expanded parts being shown with the same slightly green gradient used elsewhere
- do not make the software univerally tied to cg.cx, possibly switch to other cg.xx.xx or cg.xxx/cg.xx domain
- add other temp files from ./data dir to .gitignore
- resolve all issues that i might discover later
- add sign-in/sign-up on web
- add direct upload on web
- add extreme stability & error handling, so neither bot nor server can crash anymore
- do not expose API endpoints, ip's ect. in web/telegram

View File

@@ -124,8 +124,28 @@ struct BotContext {
sem: Arc<tokio::sync::Semaphore>, sem: Arc<tokio::sync::Semaphore>,
} }
#[tokio::main] fn main() {
async fn main() { // Windows default thread stack is 1MB; teloxide futures + large Telegram types
// (CallbackQuery/Message ~5KB each) can exhaust this during dptree dispatch.
// Spawn the tokio runtime on a thread with an 8MB stack.
let stack_size = 8 * 1024 * 1024;
std::thread::Builder::new()
.name("cgcx-bot-main".into())
.stack_size(stack_size)
.spawn(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(4)
.enable_all()
.build()
.expect("tokio runtime")
.block_on(run_bot());
})
.expect("spawn main thread")
.join()
.expect("main thread panicked");
}
async fn run_bot() {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let config = Arc::new(Config::load().expect("Failed to load config")); let config = Arc::new(Config::load().expect("Failed to load config"));
@@ -175,6 +195,7 @@ async fn main() {
.await; .await;
} }
#[inline(never)]
async fn handle_message( async fn handle_message(
bot: Bot, bot: Bot,
msg: Message, msg: Message,
@@ -285,27 +306,33 @@ async fn handle_message(
Ok(()) Ok(())
} }
#[inline(never)]
async fn handle_callback( async fn handle_callback(
bot: Bot, bot: Bot,
q: CallbackQuery, q: CallbackQuery,
storage: Arc<InMemStorage<BotState>>, storage: Arc<InMemStorage<BotState>>,
ctx: BotContext, ctx: BotContext,
) -> HandlerResult { ) -> HandlerResult {
let data = q.data.as_deref().unwrap_or(""); // CallbackQuery (and the Message it may contain) are very large structs.
let user = q.from; // Extract only the fields we need and drop q before the first .await so
let user_id = user.id.0 as i64; // the compiler can keep the async state machine small.
let callback_id = q.id.clone();
let data = q.data.as_deref().unwrap_or("").to_string();
let user_id = q.from.id.0 as i64;
let chat_id = q.message.as_ref().map(|m| m.chat().id).unwrap_or(ChatId(user_id));
let message_id = q.message.as_ref().map(|m| m.id());
drop(q);
if !ctx.moderation.is_allowed(user_id).await { if !ctx.moderation.is_allowed(user_id).await {
bot.answer_callback_query(&q.id).text("Not allowed").await?; bot.answer_callback_query(&callback_id).text("Not allowed").await?;
return Ok(()); return Ok(());
} }
let chat_id = q.message.as_ref().map(|m| m.chat().id).unwrap_or(ChatId(user_id));
let dialogue = BotDialogue { chat_id, storage }; let dialogue = BotDialogue { chat_id, storage };
let parts: Vec<&str> = data.split(':').collect(); let parts: Vec<&str> = data.split(':').collect();
if parts.len() < 3 || parts[0] != "v1" { if parts.len() < 3 || parts[0] != "v1" {
bot.answer_callback_query(&q.id).await?; bot.answer_callback_query(&callback_id).await?;
return Ok(()); return Ok(());
} }
@@ -314,14 +341,14 @@ async fn handle_callback(
"accept" => { "accept" => {
let user_repo = UserRepo::new(ctx.db.conn()); let user_repo = UserRepo::new(ctx.db.conn());
user_repo.set_accepted_terms(user_id).await?; user_repo.set_accepted_terms(user_id).await?;
if let Some(msg) = &q.message { if let Some(mid) = message_id {
bot.delete_message(chat_id, msg.id()).await.ok(); bot.delete_message(chat_id, mid).await.ok();
} }
send_main_menu(&bot, chat_id, &dialogue).await?; send_main_menu(&bot, chat_id, &dialogue).await?;
} }
"reject" => { "reject" => {
if let Some(msg) = &q.message { if let Some(mid) = message_id {
bot.delete_message(chat_id, msg.id()).await.ok(); bot.delete_message(chat_id, mid).await.ok();
} }
dialogue.reset().await?; dialogue.reset().await?;
} }
@@ -358,7 +385,7 @@ async fn handle_callback(
let state = dialogue.get_or_default().await?; let state = dialogue.get_or_default().await?;
if let BotState::UploadStaging { items, .. } = state { if let BotState::UploadStaging { items, .. } = state {
if items.is_empty() { if items.is_empty() {
bot.answer_callback_query(&q.id).text("No items to upload.").await?; bot.answer_callback_query(&callback_id).text("No items to upload.").await?;
} else { } else {
let options = UploadOptions { let options = UploadOptions {
allow_download: true, allow_download: true,
@@ -370,8 +397,8 @@ async fn handle_callback(
} }
} }
"cancel" => { "cancel" => {
if let Some(msg) = &q.message { if let Some(mid) = message_id {
bot.edit_message_text(chat_id, msg.id(), "Upload cancelled.").await.ok(); bot.edit_message_text(chat_id, mid, "Upload cancelled.").await.ok();
} }
dialogue.update(BotState::MainMenu).await?; dialogue.update(BotState::MainMenu).await?;
} }
@@ -429,7 +456,7 @@ async fn handle_callback(
_ => {} _ => {}
} }
bot.answer_callback_query(&q.id).await?; bot.answer_callback_query(&callback_id).await?;
Ok(()) Ok(())
} }
@@ -491,6 +518,7 @@ async fn send_staging_message(bot: &Bot, chat_id: ChatId, items: &[StagedItem],
Ok(()) Ok(())
} }
#[inline(never)]
async fn handle_staging_message( async fn handle_staging_message(
bot: &Bot, bot: &Bot,
msg: Message, msg: Message,
@@ -504,6 +532,8 @@ async fn handle_staging_message(
return Ok(()); return Ok(());
} }
let chat_id = msg.chat.id;
let caption = msg.caption().map(|s| s.to_string());
let mut new_item = None; let mut new_item = None;
match upload_type { match upload_type {
@@ -516,7 +546,7 @@ async fn handle_staging_message(
file_name: format!("photo_{}.jpg", items.len()), file_name: format!("photo_{}.jpg", items.len()),
mime_type: "image/jpeg".to_string(), mime_type: "image/jpeg".to_string(),
size: p.file.size as u64, size: p.file.size as u64,
caption: msg.caption().map(|s| s.to_string()), caption: caption.clone(),
}); });
} }
} else if let Some(video) = msg.video() { } else if let Some(video) = msg.video() {
@@ -525,7 +555,7 @@ async fn handle_staging_message(
file_name: video.file_name.clone().unwrap_or_else(|| format!("video_{}.mp4", items.len())), file_name: video.file_name.clone().unwrap_or_else(|| format!("video_{}.mp4", items.len())),
mime_type: "video/mp4".to_string(), mime_type: "video/mp4".to_string(),
size: video.file.size as u64, size: video.file.size as u64,
caption: msg.caption().map(|s| s.to_string()), caption: caption.clone(),
}); });
} else if let Some(audio) = msg.audio() { } else if let Some(audio) = msg.audio() {
new_item = Some(StagedItem { new_item = Some(StagedItem {
@@ -533,7 +563,7 @@ async fn handle_staging_message(
file_name: audio.file_name.clone().unwrap_or_else(|| format!("audio_{}.mp3", items.len())), file_name: audio.file_name.clone().unwrap_or_else(|| format!("audio_{}.mp3", items.len())),
mime_type: "audio/mpeg".to_string(), mime_type: "audio/mpeg".to_string(),
size: audio.file.size as u64, size: audio.file.size as u64,
caption: msg.caption().map(|s| s.to_string()), caption: caption.clone(),
}); });
} }
} }
@@ -544,7 +574,7 @@ async fn handle_staging_message(
file_name: doc.file_name.clone().unwrap_or_else(|| format!("file_{}", items.len())), file_name: doc.file_name.clone().unwrap_or_else(|| format!("file_{}", items.len())),
mime_type: doc.mime_type.clone().map(|m| m.to_string()).unwrap_or_else(|| "application/octet-stream".to_string()), mime_type: doc.mime_type.clone().map(|m| m.to_string()).unwrap_or_else(|| "application/octet-stream".to_string()),
size: doc.file.size as u64, size: doc.file.size as u64,
caption: msg.caption().map(|s| s.to_string()), caption: caption.clone(),
}); });
} }
} }
@@ -563,10 +593,12 @@ async fn handle_staging_message(
} }
} }
drop(msg);
if let Some(item) = new_item { if let Some(item) = new_item {
items.push(item); items.push(item);
dialogue.update(BotState::UploadStaging { items: items.clone(), upload_type }).await?; dialogue.update(BotState::UploadStaging { items: items.clone(), upload_type }).await?;
send_staging_message(bot, msg.chat.id, &items, upload_type).await?; send_staging_message(bot, chat_id, &items, upload_type).await?;
} }
Ok(()) Ok(())

View File

@@ -107,6 +107,19 @@ type AppResult<T> = Result<T, AppError>;
async fn main() -> cgcx_core::Result<()> { async fn main() -> cgcx_core::Result<()> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
// Log panics so we can diagnose 500s that CatchPanicLayer swallows.
std::panic::set_hook(Box::new(|info| {
let msg = if let Some(s) = info.payload().downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = info.payload().downcast_ref::<String>() {
s.clone()
} else {
"unknown panic payload".to_string()
};
let location = info.location().map(|l| format!("{}:{}", l.file(), l.line())).unwrap_or_default();
tracing::error!("PANIC at {}: {}", location, msg);
}));
let config = Arc::new(Config::load()?); let config = Arc::new(Config::load()?);
config.validate()?; config.validate()?;
@@ -161,24 +174,24 @@ async fn main() -> cgcx_core::Result<()> {
let static_service = ServeDir::new("frontend/dist") let static_service = ServeDir::new("frontend/dist")
.fallback(ServeFile::new("frontend/dist/index.html")); .fallback(ServeFile::new("frontend/dist/index.html"));
let base_url = config.server.base_url.clone(); let mut origins: Vec<HeaderValue> = vec![
config.server.base_url.parse().expect("invalid server.base_url"),
];
for origin in [
"http://127.0.0.1:5173",
"http://localhost:5173",
"http://127.0.0.1:8090",
"http://localhost:8090",
] {
if let Ok(hv) = origin.parse::<HeaderValue>() {
if !origins.contains(&hv) {
origins.push(hv);
}
}
}
let cors = CorsLayer::new() let cors = CorsLayer::new()
.allow_origin(AllowOrigin::predicate(move |origin: &HeaderValue, _request_parts: &_| { .allow_origin(AllowOrigin::list(origins))
if let Ok(origin_str) = origin.to_str() {
if origin_str == base_url {
return true;
}
// Allow localhost origins for development
if origin_str.starts_with("http://127.0.0.1:")
|| origin_str.starts_with("http://localhost:")
|| origin_str.starts_with("https://127.0.0.1:")
|| origin_str.starts_with("https://localhost:")
{
return true;
}
}
false
}))
.allow_methods([Method::GET, Method::POST, Method::HEAD, Method::OPTIONS]) .allow_methods([Method::GET, Method::POST, Method::HEAD, Method::OPTIONS])
.allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION, header::ACCEPT, header::ACCEPT_ENCODING, header::RANGE]) .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION, header::ACCEPT, header::ACCEPT_ENCODING, header::RANGE])
.allow_credentials(true) .allow_credentials(true)

View File

@@ -0,0 +1 @@
{"ids": [], "updated_at": ""}

View File

@@ -0,0 +1 @@
{"ids": [], "updated_at": ""}

1201
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,12 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^4.0.0", "@sveltejs/vite-plugin-svelte": "^7.1.2",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"vite": "^5.0.0" "vite": "^8.0.14"
}, },
"dependencies": { "dependencies": {
"marked": "^12.0.0", "dompurify": "^3.0.0",
"dompurify": "^3.0.0" "marked": "^12.0.0"
} }
} }