| 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/",
|
| },
|
| };
|
|
|
|
|
| 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();
|
| }
|
|
|
|
|
| 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");
|
|
|
|
|
| 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();
|
| }
|
|
|
|
|
| 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" });
|
| });
|
|
|
|
|
| app.use((req, res, next) => {
|
| res.status(404).send("Not Found");
|
| });
|
|
|
| app.listen(port, () => {
|
| console.log(`Perplexity proxy listening on port ${port}`);
|
| });
|
|
|
| function createEvent(event, data) {
|
|
|
| if (typeof data === "object") {
|
| data = JSON.stringify(data);
|
| }
|
| return `event: ${event}\ndata: ${data}\n\n`;
|
| } |