CharacterAI Node.js Puppeteer Request Library
const puppeteer = require('puppeteer-extra');/nconst StealthPlugin = require('puppeteer-extra-plugin-stealth')/nconst fs = require('fs');/n/nlet chromiumPath = process.platform === 'linux' ? '/usr/bin/chromium-browser' : null;/nif (chromiumPath && !fs.existsSync(chromiumPath)) console.log('[node_characterai] Puppeteer - Warning: the specified Chromium path for puppeteer could not be located. If the script does not work properly, you may need to specify a path to the Chromium binary file/executable.');/n/nclass Requester {/n browser = undefined;/n page = undefined;/n/n #initialized = false;/n #hasDisplayed = false;/n/n #headless = 'new';/n puppeteerPath = undefined;/n puppeteerLaunchArgs = [/n '--fast-start',/n '--disable-extensions',/n '--no-sandbox',/n '--disable-setuid-sandbox',/n '--no-gpu',/n '--disable-background-timer-throttling',/n '--disable-renderer-backgrounding',/n '--override-plugin-power-saver-for-testing=never',/n '--disable-extensions-http-throttling',/n '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.3'/n ];/n puppeteerNoDefaultTimeout = false;/n puppeteerProtocolTimeout = 0;/n usePlus = false;/n forceWaitingRoom = false;/n/n constructor() {}/n isInitialized() {/n return this.#initialized;/n }/n async waitForWaitingRoom(page) {/n // Enable force waiting room to ensure you check for waiting room even on C.AI+/n if (!this.usePlus || (this.usePlus && this.forceWaitingRoom)) {/n return new Promise(async (resolve) => {/n try {/n let interval;/n let pass = true;/n/n const minute = 60000; // Update every minute/n/n // Keep waiting until false/n async function check() {/n if (pass) {/n pass = false;/n/n const waitingRoomTimeLeft = await page.evaluate(async() => {/n try {/n const contentContainer = document.querySelector('.content-container');/n const sections = contentContainer.querySelectorAll('section');/n const h2Element = sections[1].querySelector('h2');/n const h2Text = h2Element.innerText;/n const regex = ///d+/g;/n const matches = h2Text.match(regex);/n/n if (matches) return matches[0];/n } catch (error) {/n return;/n }/n }, minute);/n/n const waiting = (waitingRoomTimeLeft != null);/n if (waiting) {/n console.log([node_characterai] Puppeteer - Currently in cloudflare's waiting room. Time left: ${waitingRoomTimeLeft});/n } else {/n resolve();/n clearInterval(interval);/n }/n pass = true;/n };/n }/n/n interval = setInterval(check, minute);/n await check();/n } catch (error) {/n console.log('[node_characterai] Puppeteer - There was a fatal error while checking for cloudflare/'s waiting room');/n console.log(error);/n }/n });/n }/n }/n async initialize() {/n if (!this.isInitialized());/n/n // Handle chromium tabs cleanup/n process.on('exit', () => {/n this.uninitialize();/n });/n/n console.log('[node_characterai] Puppeteer - This is an experimental feature. Please report any issues on github.');/n/n puppeteer.use(StealthPlugin())/n const browser = await puppeteer.launch({/n headless: this.#headless,/n args: this.puppeteerLaunchArgs,/n protocolTimeout: this.puppeteerProtocolTimeout || 0, // Props to monckey100/n executablePath: this.puppeteerPath || null/n });/n this.browser = browser;/n/n let page = await browser.pages();/n page = page[0];/n this.page = page;/n/n await page.setRequestInterception(false);/n/n page.setViewport({/n width: 1920 + Math.floor(Math.random() * 100),/n height: 3000 + Math.floor(Math.random() * 100),/n deviceScaleFactor: 1,/n hasTouch: false,/n isLandscape: false,/n isMobile: false,/n });/n await page.setJavaScriptEnabled(true);/n await page.setDefaultNavigationTimeout(0);/n/n const userAgent = 'CharacterAI/1.0.0 (iPhone; iOS 14.4.2; Scale/3.00)';/n await page.setUserAgent(userAgent);/n/n // Special thanks to @Parking-Master for this fix/n await page.deleteCookie();/n const client = await page.target().createCDPSession();/n await client.send('Network.clearBrowserCookies');/n await client.send('Network.clearBrowserCache');/n await page.goto('https://' + (this.usePlus ? 'plus' : 'beta') + '.character.ai');/n await page.evaluate(() => localStorage.clear());/n/n // If there is no waiting room, the script will continue anyway/n await this.waitForWaitingRoom(page);/n/n console.log('[node_characterai] Puppeteer - Done with setup');/n }/n/n async request(url, options) {/n const page = this.page;/n/n const method = options.method;/n/n const body = (method == 'GET' ? {} : options.body);/n const headers = options.headers;/n/n let response;/n/n try {/n const payload = {/n method: method,/n headers: headers,/n body: body/n }/n/n await page.setRequestInterception(false);/n if (!this.#hasDisplayed) {/n console.log('[node_characterai] Puppeteer - Eval-fetching is an experimental feature and may be slower. Please report any issues on github')/n this.#hasDisplayed = true;/n }/n/n if (url.endsWith('/streaming/')) {/n // Bless @roogue & @drizzle-mizzle for the code here!/n response = await page.evaluate(async (payload, url) => {/n const response = await fetch(url, payload);/n/n const data = await response.text();/n const matches = data.match(//{.*/}/g);/n const responseText = matches[matches.length - 1];/n/n let result = {/n code: 500/n };/n/n if (!matches) result = null;/n else {/n result.code = 200;/n result.response = responseText;/n };/n return result;/n }, payload, url);/n/n response.status = () => response.code; // compatibilty reasons/n response.text = () => response.response; // compatibilty reasons/n } else {/n await page.setRequestInterception(true);/n let initialRequest = true;/n/n page.once('request', request => {/n var data = {/n 'method': method,/n 'postData': body,/n 'headers': headers/n };/n/n if (request.isNavigationRequest() && !initialRequest) {/n return request.abort();/n }/n/n try {/n initialRequest = false;/n request.continue(data);/n } catch (error) {/n console.log('[node_characterai] Puppeteer - Non fatal error: ' + error);/n }/n });/n response = await page.goto(url, { waitUntil: 'domcontentloaded' });/n }/n } catch (error) {/n console.log('[node_characterai] Puppeteer - ' + error)/n }/n/n return response;/n }/n/n async imageUpload(url, headers, localFile = false) {/n const page = this.page;/n/n let response/n/n try {/n await page.setRequestInterception(false);/n if (!this.#hasDisplayed) {/n console.log('[node_characterai] Puppeteer - Eval-fetching is an experimental feature and may be slower. Please report any issues on github')/n this.#hasDisplayed = true;/n }/n/n // The end of this repeated 2 times, is it intentional?/n // Can it be in the same spot at the end to avoid code repetition?/n if (localFile) {/n let dataUrl = fs.readFileSync(url, 'base64');/n response = await page.evaluate(/n async (uploadHeaders, dataUrl) => {/n var result = {/n code: 500/n };/n/n const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {/n const byteCharacters = atob(b64Data); // <- Watch out, this is deprecated!/n const byteArrays = [];/n/n for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {/n const slice = byteCharacters.slice(offset, offset + sliceSize);/n/n const byteNumbers = new Array(slice.length);/n for (let i = 0; i < slice.length; i++) {/n byteNumbers[i] = slice.charCodeAt(i);/n }/n/n const byteArray = new Uint8Array(byteNumbers);/n byteArrays.push(byteArray);/n }/n/n const blob = new Blob(byteArrays, {/n type: contentType/n });/n return blob;/n }/n/n const blob = b64toBlob(dataUrl.includes('base64,') ? dataUrl.split('base64,')[1] : dataUrl);/n const file = new File([blob], 'image');/n const formData = new FormData();/n formData.append('image', file);/n/n let head = uploadHeaders;/n delete head['Content-Type'];/n // ^ Is this even being used?/n/n const uploadResponse = await fetch('https://beta.character.ai/chat/upload-image/', {/n headers: uploadHeaders,/n method: 'POST',/n body: formData/n })/n/n if (uploadResponse.status == 200) {/n result.code = 200;/n/n let uploadResponseJSON = await uploadResponse.json();/n result.response = uploadResponseJSON.value;/n }/n/n return result;/n },/n headers, dataUrl/n );/n } else {/n response = await page.evaluate(/n async (uploadHeaders, url) => {/n var result = {/n code: 500/n };/n/n const uploadResponse = await fetch(url).then(uploadResponse => uploadResponse.blob()).then(async (blob) => {/n const file = new File([blob], 'image');/n const formData = new FormData();/n formData.append('image', file);/n/n let head = uploadHeaders;/n delete head['Content-Type'];/n // ^ Is this even being used?/n/n const uploadResponse = await fetch('https://beta.character.ai/chat/upload-image/', {/n headers: uploadHeaders,/n method: 'POST',/n body: formData/n })/n return uploadResponse;/n }).then()/n/n if (uploadResponse.status == 200) {/n result.code = 200;/n/n let uploadResponseJSON = await uploadResponse.json();/n result.response = uploadResponseJSON.value;/n }/n/n return result;/n },/n headers, url/n );/n }/n/n response.status = () => response.code; // compatibilty reasons/n response.body = () => response.response; // compatibilty reasons/n } catch (error) {/n console.log('[node_characterai] Puppeteer - ' + error)/n }/n/n return response;/n }/n/n async uninitialize() {/n // Handle chromium tabs cleanup/n try {/n this.browser.close();/n } catch {}/n }/n};/n/nmodule.exports = Requester;
原文地址: https://www.cveoy.top/t/topic/mnl 著作权归作者所有。请勿转载和采集!