小网站,大量调用会空回
付费模型加个鉴权参数也能2,不过没claude就不搞了
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
import { crypto } from "https://deno.land/std@0.208.0/crypto/mod.ts";
const MODEL_MAP: Record<string, number> = {
"llama-3.3": 1,
"claude-3-haiku": 2,
"gpt-4o-mini": 3,
"deepseek-v3": 4,
"deepseek-r1": 5,
"gemini-2.0-flash": 6,
"gemini-2.5-flash": 7,
"kimi-k2": 10,
"qwen3-235b": 11,
};
function md5(text: string): string {
const hash = crypto.subtle.digestSync("MD5", new TextEncoder().encode(text));
return Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
function generateVisitorId(): string {
const randomBytes = crypto.getRandomValues(new Uint8Array(16));
return Array.from(randomBytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
function generateSign(params: Record<string, any>, visitorId: string) {
const timestamp = String(Date.now() * 1_000_000);
const sortDict = (d: any): any => {
if (typeof d !== "object" || d === null) return d;
return Object.keys(d).sort().reduce((acc, k) => {
acc[k] = sortDict(d[k]);
return acc;
}, {} as any);
};
const serialize = (prefix: string, value: any, result: string[]) => {
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
Object.entries(value).forEach(([k, v]) => serialize(`${prefix}[${k}]`, v, result));
} else if (Array.isArray(value)) {
value.forEach((item, i) => serialize(`${prefix}[${i}]`, item, result));
} else {
result.push(`${prefix}=${value}`);
}
};
const sorted = sortDict(params);
sorted.appKey = "G!JLAE6jg*m&C2m&";
sorted.timestamp = timestamp;
const result: string[] = [];
Object.entries(sorted).forEach(([key, value]) => serialize(key, value, result));
const signString = `${visitorId}${result.join("&")}${visitorId}`;
const md5First = md5(signString).split("").reverse().join("").slice(0, 16);
const finalSign = md5(md5First);
return { sign: finalSign, timestamp };
}
function processMessages(messages: any[]): string {
if (!messages || messages.length === 0) return "";
if (messages.length === 1) {
return messages[0].content || "";
}
return messages
.map(msg => {
const role = msg.role || "user";
const content = msg.content || "";
return `${role}:${content}`;
})
.join("\n");
}
function streamToOpenAI(stream: ReadableStream<Uint8Array>, chatId: string, model: string) {
const reader = stream.getReader();
const decoder = new TextDecoder();
const created = Math.floor(Date.now() / 1000);
return new ReadableStream({
async start(controller) {
let buffer = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
try {
const data = JSON.parse(line.slice(6));
if (data.code === 200) {
const answerData = JSON.parse(data.data);
const content = answerData.answer || "";
if (content) {
const chunk = {
id: chatId,
object: "chat.completion.chunk",
created,
model,
choices: [{
index: 0,
delta: { content },
finish_reason: null,
}],
};
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`));
}
}
} catch (_) {
continue;
}
}
}
const finalChunk = {
id: chatId,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
};
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(finalChunk)}\n\n`));
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
} finally {
controller.close();
}
},
});
}
async function collectStream(stream: ReadableStream<Uint8Array>): Promise<string> {
const reader = stream.getReader();
const decoder = new TextDecoder();
let fullContent = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
const lines = text.split("\n");
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
if (line.includes("[DONE]")) continue;
try {
const json = JSON.parse(line.slice(6));
const content = json.choices?.[0]?.delta?.content;
if (content) fullContent += content;
} catch (_) {
continue;
}
}
}
return fullContent;
}
async function handleRequest(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/v1/models") {
return Response.json({
object: "list",
data: Object.keys(MODEL_MAP).map((id) => ({
id,
object: "model",
created: 1700000000,
owned_by: "easemate",
})),
});
}
if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
const body = await req.json();
const model = body.model || "gpt-4o-mini";
const messages = body.messages || [];
const stream = body.stream !== false;
const userInput = processMessages(messages);
const modelId = MODEL_MAP[model] || 3;
const sessionId = parseInt(`100${Math.floor(Math.random() * 8000000 + 2000000)}`);
const visitorId = generateVisitorId();
const payload = {
model_id: modelId,
session_id: sessionId,
operation_info: {
operation: userInput,
id: 10000,
},
parameters: JSON.stringify({ webSearch: false, isThinking: false }),
};
const signs = generateSign(payload, visitorId);
const response = await fetch("https://api.easemate.ai/api2/stream/exec_operation", {
method: "POST",
headers: {
"content-type": "application/json;charset=UTF-8",
"device-uuid": visitorId,
"sign": signs.sign,
"timestamp": signs.timestamp,
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
return Response.json({ error: "Upstream API error" }, { status: 502 });
}
const chatId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
if (stream) {
const transformedStream = streamToOpenAI(response.body!, chatId, model);
return new Response(transformedStream, {
headers: { "Content-Type": "text/event-stream" },
});
} else {
const transformedStream = streamToOpenAI(response.body!, chatId, model);
const content = await collectStream(transformedStream);
return Response.json({
id: chatId,
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model,
choices: [{
index: 0,
message: { role: "assistant", content },
finish_reason: "stop",
}],
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
});
}
}
return Response.json({ error: "Not found" }, { status: 404 });
}
serve(handleRequest, { port: 80 });
console.log("Server running on http://localhost:80");
