| | 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`;
|
| | } |