PP2API / index.js
Niansuh's picture
Upload 11 files
aff3448 verified
const express = require("express");
const { io } = require("socket.io-client");
const { v4: uuidv4 } = require("uuid");
const { ProxyAgent } = require("proxy-agent");
const agent = new ProxyAgent();
const crypto = require('crypto');
const app = express();
const port = process.env.PORT || 8081;
var opts = {
agent: agent,
auth: {
jwt: "anonymous-ask-user",
},
reconnection: false,
transports: ["websocket"],
path: "/socket.io",
hostname: "www.perplexity.ai",
secure: true,
port: "443",
extraHeaders: {
Cookie: process.env.PPLX_COOKIE,
"User-Agent": process.env.USER_AGENT,
Accept: "*/*",
priority: "u=1, i",
Referer: "https://www.perplexity.ai/",
},
};
// Add API token validation middleware
function validateApiToken(req, res, next) {
const apiToken = req.headers['authorization'];
if (!apiToken || !apiToken.startsWith('Bearer ') || apiToken.split(' ')[1] !== process.env.API_TOKEN) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
// Use the validation middleware
app.use(validateApiToken);
// 添加更详细的日志记录
app.use((req, res, next) => {
console.log(`Received ${req.method} request to ${req.url}`);
next();
});
app.post("/v1/chat/completions", (req, res) => {
console.log("Received request to /v1/chat/completions");
req.rawBody = "";
req.setEncoding("utf8");
req.on("data", function (chunk) {
req.rawBody += chunk;
});
req.on("end", async () => {
try {
let jsonBody = JSON.parse(req.rawBody);
console.log("Parsed request body:", jsonBody);
if (jsonBody.stream !== true) {
// 处理非流式请求
res.json({
id: "chatcmpl-" + crypto.randomBytes(16).toString('hex'),
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model: "claude-3-opus-20240229",
choices: [
{
index: 0,
message: {
role: "assistant",
content: "This API only supports streaming responses. Please set 'stream' to true in your request.",
},
finish_reason: "stop"
}
],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
});
} else {
// 处理流式请求
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// 将OpenAI API格式的消息历史转换为Perplexity AI可以理解的格式
let previousMessages = jsonBody.messages
.map((msg) => msg.content)
.join("\n\n");
var socket = io("wss://www.perplexity.ai/", opts);
socket.on("connect", function () {
console.log(" > [Connected]");
socket
.emitWithAck("perplexity_ask", previousMessages, {
"version": "2.9",
"source": "default",
"attachments": [],
"language": "en-GB",
"timezone": "Europe/London",
"search_focus": "writing",
"frontend_uuid": uuidv4(),
"mode": "concise",
"is_related_query": false,
"is_default_related_query": false,
"visitor_id": uuidv4(),
"frontend_context_uuid": uuidv4(),
"prompt_source": "user",
"query_source": "home"
})
.then((response) => {
console.log(response);
sendFinalChunk(res);
}).catch((error) => {
if(error.message != "socket has been disconnected"){
console.log(error);
}
sendFinalChunk(res);
});
});
socket.on("query_progress", (data) => {
if(data.text){
var text = JSON.parse(data.text)
var chunk = text.chunks[text.chunks.length - 1];
if(chunk){
sendChunk(res, chunk);
}
}
});
socket.on("disconnect", function () {
console.log(" > [Disconnected]");
sendFinalChunk(res);
});
socket.on("error", (error) => {
console.log(error);
sendErrorChunk(res, "Error occurred while fetching output. Please refer to the log for more information.");
sendFinalChunk(res);
});
socket.on("connect_error", function (error) {
console.log(error);
sendErrorChunk(res, "Failed to connect to Perplexity.ai. Please refer to the log for more information.");
sendFinalChunk(res);
});
res.on("close", function () {
console.log(" > [Client closed]");
socket.disconnect();
});
}
} catch (e) {
console.error("Error processing request:", e);
res.status(400).json({ error: e.message });
}
});
});
function sendChunk(res, content) {
const chunk = {
id: "chatcmpl-" + crypto.randomBytes(16).toString('hex'),
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: "claude-3-opus-20240229",
choices: [{
index: 0,
delta: {
content: content
},
finish_reason: null
}]
};
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
function sendErrorChunk(res, errorMessage) {
const chunk = {
id: "chatcmpl-" + crypto.randomBytes(16).toString('hex'),
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: "claude-3-opus-20240229",
choices: [{
index: 0,
delta: {
content: errorMessage
},
finish_reason: "stop"
}]
};
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
function sendFinalChunk(res) {
const finalChunk = {
id: "chatcmpl-" + crypto.randomBytes(16).toString('hex'),
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: "claude-3-opus-20240229",
choices: [{
index: 0,
delta: {},
finish_reason: "stop"
}]
};
res.write(`data: ${JSON.stringify(finalChunk)}\n\n`);
res.write("data: [DONE]\n\n");
res.end();
}
// 修改404处理
app.use((req, res, next) => {
console.log(`404 Not Found: ${req.method} ${req.url}`);
res.status(404).json({ error: "Not Found" });
});
// 添加全局错误处理
app.use((err, req, res, next) => {
console.error("Unhandled error:", err);
res.status(500).json({ error: "Internal Server Error" });
});
// handle other
app.use((req, res, next) => {
res.status(404).send("Not Found");
});
app.listen(port, () => {
console.log(`Perplexity proxy listening on port ${port}`);
});
// eventStream util
function createEvent(event, data) {
// if data is object, stringify it
if (typeof data === "object") {
data = JSON.stringify(data);
}
return `event: ${event}\ndata: ${data}\n\n`;
}