use std::ffi::{c_char, CStr, CString};
use std::ptr;
use crate::{Geeked, RiskType};
#[repr(C)]
pub struct GeekedResult {
pub error_code: i32,
pub error_message: *mut c_char,
pub captcha_id: *mut c_char,
pub lot_number: *mut c_char,
pub pass_token: *mut c_char,
pub gen_time: *mut c_char,
pub captcha_output: *mut c_char,
}
impl GeekedResult {
fn success(
captcha_id: String,
lot_number: String,
pass_token: String,
gen_time: String,
captcha_output: String,
) -> Self {
Self {
error_code: 0,
error_message: ptr::null_mut(),
captcha_id: string_to_ptr(captcha_id),
lot_number: string_to_ptr(lot_number),
pass_token: string_to_ptr(pass_token),
gen_time: string_to_ptr(gen_time),
captcha_output: string_to_ptr(captcha_output),
}
}
fn error(code: i32, message: String) -> Self {
Self {
error_code: code,
error_message: string_to_ptr(message),
captcha_id: ptr::null_mut(),
lot_number: ptr::null_mut(),
pass_token: ptr::null_mut(),
gen_time: ptr::null_mut(),
captcha_output: ptr::null_mut(),
}
}
}
fn string_to_ptr(s: String) -> *mut c_char {
CString::new(s)
.map(|cs| cs.into_raw())
.unwrap_or(ptr::null_mut())
}
unsafe fn ptr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
CStr::from_ptr(ptr).to_str().ok().map(|s| s.to_string())
}
fn parse_risk_type(s: &str) -> Option<RiskType> {
match s.to_lowercase().as_str() {
"slide" => Some(RiskType::Slide),
"gobang" => Some(RiskType::Gobang),
"icon" => Some(RiskType::Icon),
"ai" | "invisible" => Some(RiskType::Ai),
"svg" => Some(RiskType::Svg),
_ => None,
}
}
#[no_mangle]
pub unsafe extern "C" fn geeked_solve(
captcha_id: *const c_char,
risk_type: *const c_char,
proxy: *const c_char,
user_info: *const c_char,
) -> GeekedResult {
let captcha_id = match ptr_to_string(captcha_id) {
Some(s) if !s.is_empty() => s,
_ => return GeekedResult::error(1, "captcha_id is required".to_string()),
};
let risk_type_str = match ptr_to_string(risk_type) {
Some(s) => s,
None => return GeekedResult::error(2, "risk_type is required".to_string()),
};
let risk_type = match parse_risk_type(&risk_type_str) {
Some(rt) => rt,
None => {
return GeekedResult::error(
3,
format!(
"Invalid risk_type '{}'. Valid values: slide, gobang, icon, ai, svg",
risk_type_str
),
)
}
};
let proxy = ptr_to_string(proxy);
let user_info = ptr_to_string(user_info);
let runtime = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(rt) => rt,
Err(e) => return GeekedResult::error(4, format!("Failed to create runtime: {}", e)),
};
runtime.block_on(async {
let mut builder = Geeked::builder(&captcha_id, risk_type);
if let Some(p) = proxy {
builder = builder.proxy(p);
}
if let Some(ui) = user_info {
builder = builder.user_info(ui);
}
let solver = match builder.build().await {
Ok(s) => s,
Err(e) => return GeekedResult::error(5, format!("Failed to build solver: {}", e)),
};
match solver.solve().await {
Ok(result) => GeekedResult::success(
result.captcha_id,
result.lot_number,
result.pass_token,
result.gen_time,
result.captcha_output,
),
Err(e) => GeekedResult::error(6, format!("Solve failed: {}", e)),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn geeked_solve_json(
captcha_id: *const c_char,
risk_type: *const c_char,
proxy: *const c_char,
user_info: *const c_char,
) -> *mut c_char {
let result = geeked_solve(captcha_id, risk_type, proxy, user_info);
let json = if result.error_code == 0 {
let captcha_id = ptr_to_string(result.captcha_id).unwrap_or_default();
let lot_number = ptr_to_string(result.lot_number).unwrap_or_default();
let pass_token = ptr_to_string(result.pass_token).unwrap_or_default();
let gen_time = ptr_to_string(result.gen_time).unwrap_or_default();
let captcha_output = ptr_to_string(result.captcha_output).unwrap_or_default();
geeked_free_result(result);
serde_json::json!({
"success": true,
"captcha_id": captcha_id,
"lot_number": lot_number,
"pass_token": pass_token,
"gen_time": gen_time,
"captcha_output": captcha_output
})
.to_string()
} else {
let error =
ptr_to_string(result.error_message).unwrap_or_else(|| "Unknown error".to_string());
geeked_free_result(result);
serde_json::json!({
"success": false,
"error": error
})
.to_string()
};
string_to_ptr(json)
}
#[no_mangle]
pub unsafe extern "C" fn geeked_free_result(result: GeekedResult) {
if !result.error_message.is_null() {
let _ = CString::from_raw(result.error_message);
}
if !result.captcha_id.is_null() {
let _ = CString::from_raw(result.captcha_id);
}
if !result.lot_number.is_null() {
let _ = CString::from_raw(result.lot_number);
}
if !result.pass_token.is_null() {
let _ = CString::from_raw(result.pass_token);
}
if !result.gen_time.is_null() {
let _ = CString::from_raw(result.gen_time);
}
if !result.captcha_output.is_null() {
let _ = CString::from_raw(result.captcha_output);
}
}
#[no_mangle]
pub unsafe extern "C" fn geeked_free_string(s: *mut c_char) {
if !s.is_null() {
let _ = CString::from_raw(s);
}
}
#[no_mangle]
pub extern "C" fn geeked_version() -> *const c_char {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
}