Skip to content

Commit

Permalink
[Add] 🎉 BingAPI Image Support CFWK & Vercel
Browse files Browse the repository at this point in the history
  • Loading branch information
Harry-zklcdc committed Mar 7, 2024
1 parent abfb047 commit a1089ff
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 35 deletions.
18 changes: 17 additions & 1 deletion api/v1/vercel.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bingapiChat, bingapiModel, bingapiModels } from '../../cloudflare/bingapi.js'
import { bingapiChat, bingapiImage, bingapiModel, bingapiModels } from '../../cloudflare/bingapi.js'

export const config = {
runtime: 'edge',
Expand Down Expand Up @@ -40,5 +40,21 @@ export default function ChatHandler(request) {
}
return bingapiChat(request, CUSTOM_OPTIONS);
}
if (currentUrl.pathname.startsWith('/v1/images/generations') || currentUrl.pathname.startsWith('/api/v1/images/generations')) {
if (request.method == 'OPTIONS') {
return Response.json({ code: 200, message: 'OPTIONS', data: null }, {
headers: {
"Allow": "POST, OPTIONS",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, Cookie",
}
});
}
if (request.method != 'POST') {
return Response.json({ code: 405, message: 'Method Not Allowed', data: null }, { status: 405 });
}
return bingapiImage(request, Object.assign({ cookie: cookie }, CUSTOM_OPTIONS));
}
return Response.json({ code: 404, message: 'API No Found', data: null }, { status: 404 });
}
158 changes: 128 additions & 30 deletions cloudflare/bingapi.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const AUTHOR = 'Harry-zklcdc/go-proxy-bingai';

const SPILT = '\x1e';
const BING_ORIGIN = 'https://www.bing.com';
const SYDNEY_ORIGIN = 'https://sydney.bing.com';
const BING_ORIGIN = 'http://localhost:8080';
const SYDNEY_ORIGIN = 'http://localhost:8080';

export const bingchatModel = {
PRECISE: 'Precise', // 精准
Expand Down Expand Up @@ -136,35 +136,17 @@ export async function bingapiChat(request, options) {
}

// Get CCT Cookie
const IG = crypto.randomUUID().replace(/-/g, '').toUpperCase();
let newReq = new Request(options.BYPASS_SERVER, {
method: 'POST',
headers: {
cookie: options.cookie,
},
body: JSON.stringify({
cookies: options.cookie,
iframeid: 'local-gen-' + crypto.randomUUID(),
IG: IG,
T: await aesEncrypt(AUTHOR, IG),
}),
});
let res = await fetch(newReq);
if (!res.ok) {
return helperResponseJson({ error: 'Get CCT Cookie Error' }, 500);
}
let resBody = await res.json();
const cctCookie = resBody.result.cookies;
const cctCookie = options.cookie + '; ' + (await getCctCookie(options));

// Get New Conversation
newReq = new Request(BING_ORIGIN + '/turing/conversation/create?bundleVersion=1.1467.6', {
let newReq = new Request(BING_ORIGIN + '/turing/conversation/create?bundleVersion=1.1467.6', {
headers: getNewHeaders(cctCookie),
})
res = await fetch(newReq);
let res = await fetch(newReq);
if (!res.ok) {
return helperResponseJson({ error: 'Get New Conversation Error' }, 500);
}
resBody = await res.json();
let resBody = await res.json();
const chatHub = {
conversationId: resBody.conversationId,
clientId: resBody.clientId,
Expand Down Expand Up @@ -531,14 +513,85 @@ export async function bingapiChat(request, options) {
}
};

const imageResponse = {
created: 1687579610,
data: []
}

const imageStruct = {
url: ''
}

/**
* BingAPI Image
* @param {Request} request
* @param {Object} options
* @returns {Response}
*/
export function bingapiImage(request, options) {
// TODO
export async function bingapiImage(request, options) {
const resq = await toJSON(request.body);

const authApiKey = request.headers.get('Authorization');
if (authApiKey != 'Bearer ' + options.APIKEY && options.APIKEY != '') {
return helperResponseJson({ error: 'Unauthorized' }, 401);
}

if (resq.prompt == '' || resq.prompt == undefined || resq.prompt == null) {
return helperResponseJson({ error: 'Prompt is required' }, 400);
}

const cctCookie = options.cookie + '; ' + (await getCctCookie(options));

const headers = getNewHeaders(cctCookie);
headers.set('Content-Type', 'application/x-www-form-urlencoded');
let newReq = new Request(BING_ORIGIN + '/images/create?q=' + encodeURIComponent(resq.prompt) + '&rt=4&FORM=GENCRE', {
method: 'POST',
headers: headers,
body: 'q=' + encodeURIComponent(resq.prompt) + '&qs=ds',
redirect: 'manual'
})
let res = await fetch(newReq);
if (res.status != 302) {
return helperResponseJson({ error: 'Generate Image Error' }, 500);
}

const imageUrl = new URL(BING_ORIGIN + res.headers.get('Location'));
const id = imageUrl.searchParams.get('id')
newReq = new Request(BING_ORIGIN + res.headers.get('Location'), {
headers: headers,
});
res = await fetch(newReq);
if (!res.ok) {
return helperResponseJson({ error: 'Generate Image Error' }, 500);
}

let i = 0, resBody = '';
for (i = 0; i < 120; i ++) {
await sleep(1000);
newReq = new Request(BING_ORIGIN + '/images/create/async/results/' + id, {
headers: headers,
});
res = await fetch(newReq);
resBody = await res.text();
if (res.headers.get('Content-Type').indexOf('text/html') != -1 && resBody.length > 1) {
break
}
}

if (i >= 120) {
return helperResponseJson({ error: 'Generate Image Timeout' }, 500);
}

const regResults = resBody.match(/<img.*\/>/g)
const respData = Object.assign({}, imageResponse);
regResults.forEach(v => {
const url = v.match(/src="([^"]*)"/)[1].split('?')[0];
if (url.indexOf('/rp/') == -1) {
respData.data.push(Object.assign({}, imageStruct, { url: url }));
}
})

return helperResponseJson(respData);
};

const modelInfo = {
Expand Down Expand Up @@ -620,6 +673,42 @@ function helperResponseJson(response, statusCode = 200) {
});
}

/**
* Get CCT Cookie
* @param {string} cookie
* @returns {string}
*/
async function getCctCookie(options) {
const IG = crypto.randomUUID().replace(/-/g, '').toUpperCase();
let newReq = new Request(options.BYPASS_SERVER, {
method: 'POST',
headers: {
cookie: options.cookie,
},
body: JSON.stringify({
cookies: options.cookie,
iframeid: 'local-gen-' + crypto.randomUUID(),
IG: IG,
T: await aesEncrypt(AUTHOR, IG),
}),
});
let res = await fetch(newReq);
if (!res.ok) {
return cookie;
}
let resBody = await res.json();
return resBody.result.cookies;
}

/**
* Sleep
* @param {number} ms
* @returns {Promise}
*/
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

/**
* AES Encrypt
* @param {string} e
Expand Down Expand Up @@ -765,11 +854,11 @@ function getNewHeaders(cookie) {
}

/**
* to JSON
* to String
* @param {ReadableStream} body
* @returns {Promise<Object>}
* @returns {Promise<string>}
*/
async function toJSON(body) {
async function toString(body) {
const reader = body.getReader(); // `ReadableStreamDefaultReader`
const decoder = new TextDecoder();
const chunks = [];
Expand All @@ -778,14 +867,23 @@ async function toJSON(body) {

// all chunks have been read?
if (done) {
return JSON.parse(chunks.join(''));
return chunks.join('');
}

const chunk = decoder.decode(value, { stream: true });
chunks.push(chunk);
return read(); // read the next chunk
}
return read();
}

/**
* to JSON
* @param {ReadableStream} body
* @returns {Promise<Object>}
*/
async function toJSON(body) {
return JSON.parse(await toString(body));
};

/**
Expand Down
24 changes: 20 additions & 4 deletions cloudflare/worker.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { brotli_decode } from "./bjs.js"
import { bingapiModels, bingapiModel, bingapiChat, getRandomIP } from "./bingapi.js"
import { bingapiModels, bingapiModel, bingapiChat, bingapiImage, getRandomIP } from "./bingapi.js"

// 同查找 _U 一样, 查找 KievRPSSecAuth 的值并替换下方的xxx
const CUSTOM_OPTIONS = {
Expand Down Expand Up @@ -386,10 +386,10 @@ const bingapi = async (request, cookie) => {
if ((currentUrl.pathname.startsWith('/v1/models/')) || (currentUrl.pathname.startsWith('/api/v1/models/'))) {
return bingapiModel(request, Object.assign({ cookie: cookie }, CUSTOM_OPTIONS));
}
if ((currentUrl.pathname === '/v1/models') || (currentUrl.pathname === '/api/v1/models')) {
if (currentUrl.pathname.startsWith('/v1/models') || currentUrl.pathname.startsWith('/api/v1/models')) {
return bingapiModels(request, Object.assign({ cookie: cookie }, CUSTOM_OPTIONS));
}
if ((currentUrl.pathname === '/v1/chat/completions') || (currentUrl.pathname === '/api/v1/chat/completions')) {
if (currentUrl.pathname.startsWith('/v1/chat/completions') || currentUrl.pathname.startsWith('/api/v1/chat/completions')) {
if (request.method == 'OPTIONS') {
return Response.json({ code: 200, message: 'OPTIONS', data: null }, {
headers: {
Expand All @@ -405,6 +405,22 @@ const bingapi = async (request, cookie) => {
}
return bingapiChat(request, Object.assign({ cookie: cookie }, CUSTOM_OPTIONS));
}
if (currentUrl.pathname.startsWith('/v1/images/generations') || currentUrl.pathname.startsWith('/api/v1/images/generations')) {
if (request.method == 'OPTIONS') {
return Response.json({ code: 200, message: 'OPTIONS', data: null }, {
headers: {
"Allow": "POST, OPTIONS",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, Cookie",
}
});
}
if (request.method != 'POST') {
return Response.json({ code: 405, message: 'Method Not Allowed', data: null }, { status: 405 });
}
return bingapiImage(request, Object.assign({ cookie: cookie }, CUSTOM_OPTIONS));
}
return Response.json({ code: 404, message: 'API No Found', data: null }, { status: 404 });
};

Expand Down Expand Up @@ -512,7 +528,7 @@ export default {
if (currentUrl.pathname === '/challenge/verify') {
return verify(request, cookies);
}
if (currentUrl.pathname.indexOf('/v1') === 0 || currentUrl.pathname.indexOf('/api/v1') === 0) {
if (currentUrl.pathname.startsWith('/v1') || currentUrl.pathname.startsWith('/api/v1')) {
return bingapi(request, cookies);
}
if (currentUrl.pathname === '/pass') {
Expand Down

0 comments on commit a1089ff

Please sign in to comment.