diff --git a/README.md b/README.md index a85cc636..9a31c3c7 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,6 @@ If you do not want your website to appear here, please raise an issue and I will | openai | too much | | jasper | gpt-3.5-turbo, gpt-4 | | pap | | -| myshell | gpt-3.5-turbo, gpt-4 | | acytoo | gpt-3.5-turbo | | google | search | | www | url | diff --git a/README_zh.md b/README_zh.md index 432c757b..88ce0bd1 100644 --- a/README_zh.md +++ b/README_zh.md @@ -69,7 +69,6 @@ | openai | too much | | jasper | gpt-3.5-turbo, gpt-4 | | pap | | -| myshell | gpt-3.5-turbo, gpt-4 | | acytoo | gpt-3.5-turbo | | google | search | | www | url | diff --git a/model/base.ts b/model/base.ts index 49721d84..517c928d 100644 --- a/model/base.ts +++ b/model/base.ts @@ -80,7 +80,6 @@ export enum Site { OneAPI = 'oneapi', Jasper = 'jasper', Pap = 'pap', - MyShell = 'myshell', AcyToo = 'acytoo', Google = 'google', WWW = 'www', diff --git a/model/index.ts b/model/index.ts index 64d08be1..5580ed11 100644 --- a/model/index.ts +++ b/model/index.ts @@ -27,7 +27,6 @@ import { SinCode } from './sincode'; import { OpenAI } from './openai'; import { OneAPI } from './oneapi'; import { Jasper } from './jasper'; -import { MyShell } from './myshell'; import { AcyToo } from './acytoo'; import { Google } from './google'; import { WWW } from './www'; @@ -82,7 +81,6 @@ export class ChatModelFactory { this.modelMap.set(Site.SinCode, new SinCode({ name: Site.SinCode })); this.modelMap.set(Site.OpenAI, new OpenAI({ name: Site.OpenAI })); this.modelMap.set(Site.Jasper, new Jasper({ name: Site.Jasper })); - this.modelMap.set(Site.MyShell, new MyShell({ name: Site.MyShell })); this.modelMap.set(Site.AcyToo, new AcyToo({ name: Site.AcyToo })); this.modelMap.set(Site.Google, new Google({ name: Site.Google })); this.modelMap.set(Site.WWW, new WWW({ name: Site.WWW })); diff --git a/model/myshell/index.ts b/model/myshell/index.ts deleted file mode 100644 index 53a4ba88..00000000 --- a/model/myshell/index.ts +++ /dev/null @@ -1,485 +0,0 @@ -import { - Chat, - ChatOptions, - ChatRequest, - ChatResponse, - ModelType, -} from '../base'; -import { Browser, Page, Protocol } from 'puppeteer'; -import { BrowserPool, BrowserUser, simplifyPage } from '../../utils/puppeteer'; -import { - DoneData, - ErrorData, - Event, - EventStream, - MessageData, - parseJSON, - shuffleArray, - sleep, -} from '../../utils'; -import { v4 } from 'uuid'; - -import fs from 'fs'; -import moment from 'moment'; -import { - CreateEmail, - TempEmailType, - TempMailMessage, -} from '../../utils/emailFactory'; -import { CreateAxiosProxy, WSS } from '../../utils/proxyAgent'; -import { match } from 'assert'; - -const ModelMap: Partial> = { - [ModelType.GPT4]: '01c8de4fbfc548df903712b0922a4e01', - [ModelType.GPT3p5Turbo]: '8077335db7cd47e29f7de486612cc7fd', -}; - -const MaxFailedTimes = 10; - -type Account = { - id: string; - email?: string; - login_time?: string; - last_use_time: number; - failedCnt: number; - battery: number; - token: string; - visitorID: string; -}; - -interface ReplyMessage { - id: number; - uid: string; - userId: number; - userUid: string; - type: string; - botId: number; - replyUid: string; - status: string; - text: string | null; - handled: boolean; - translation: string | null; - voiceUrl: string | null; - createdDate: string; - updatedDate: string; - botUid: string; -} - -interface TextStreamData { - replyMessage: ReplyMessage; - index: number; - text: string; - isFinal: boolean; -} - -interface TextStream { - reqId: string; - traceId: string; - data: TextStreamData; -} - -class PoeAccountPool { - private pool: Account[] = []; - private using = new Set(); - private readonly account_file_path = './run/account_myshell.json'; - - constructor() { - if (fs.existsSync(this.account_file_path)) { - const accountStr = fs.readFileSync(this.account_file_path, 'utf-8'); - this.pool = parseJSON(accountStr, [] as Account[]); - if (!Array.isArray(this.pool)) { - this.pool = []; - this.syncfile(); - } - } else { - fs.mkdirSync('./run', { recursive: true }); - this.syncfile(); - } - for (const v of this.pool) { - v.failedCnt = 0; - v.battery = - v.battery + - Math.floor((moment().unix() - v.last_use_time) / 60 / 60) * 8; - } - console.log( - `read myshell old account total:${Object.keys(this.pool).length}`, - ); - this.syncfile(); - } - - public syncfile() { - fs.writeFileSync(this.account_file_path, JSON.stringify(this.pool)); - } - - public release(id: string) { - this.using.delete(id); - } - - public getByID(id: string) { - for (const item in this.pool) { - if (this.pool[item].id === id) { - return this.pool[item]; - } - } - } - - public delete(id: string) { - for (const v in this.pool) { - const vv = this.pool[v]; - } - this.using.delete(id); - this.syncfile(); - } - - public get(): Account { - for (const vv of shuffleArray(this.pool)) { - if (this.using.has(vv.id)) { - continue; - } - if (vv.battery < 30) { - continue; - } - this.using.add(vv.id); - vv.failedCnt = 0; - return vv; - } - console.log('myshell account run out, register new now!'); - const newV: Account = { - id: v4(), - failedCnt: 0, - battery: 0, - token: '', - visitorID: '', - last_use_time: moment().unix(), - }; - this.pool.push(newV); - return newV; - } -} - -export class MyShell extends Chat implements BrowserUser { - private pagePool: BrowserPool; - private accountPool: PoeAccountPool; - private wssMap: Record = {}; - - constructor(options?: ChatOptions) { - super(options); - this.accountPool = new PoeAccountPool(); - this.pagePool = new BrowserPool( - +(process.env.MYSHELL_POOL_SIZE || 0), - this, - false, - -1, - false, - ); - } - - support(model: ModelType): number { - switch (model) { - case ModelType.GPT4: - return 1500; - case ModelType.GPT3p5Turbo: - return 1500; - default: - return 0; - } - } - - async preHandle(req: ChatRequest): Promise { - return super.preHandle(req, { token: true, countPrompt: true }); - } - - deleteID(id: string): void { - this.accountPool.delete(id); - } - - release(id: string): void { - this.accountPool.release(id); - } - - newID(): string { - const account = this.accountPool.get(); - return account.id; - } - - async init( - id: string, - browser: Browser, - ): Promise<[Page | undefined, Account]> { - const account = this.accountPool.getByID(id); - if (!account) { - await sleep(10 * 24 * 60 * 60 * 1000); - return [] as any; - } - const page = await browser.newPage(); - await page.setViewport({ width: 1920, height: 1080 }); - await simplifyPage(page); - try { - if (!account.token) { - await page.goto('https://app.myshell.ai/'); - // await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0'); - await page.waitForSelector( - '.tw-block > #sidebar > .tw-overflow-hidden > .tw-justify-center > .chakra-button', - ); - await page.click( - '.tw-block > #sidebar > .tw-overflow-hidden > .tw-justify-center > .chakra-button', - ); - - await page.waitForSelector(`.chakra-form-control > div`); - await page.click(`.chakra-form-control > div`); - const emailBox = CreateEmail( - (process.env.MYSHELL_MAIL_TYPE as TempEmailType) || - TempEmailType.TempMailLOL, - ); - const emailAddress = await emailBox.getMailAddress(); - await page.keyboard.type(emailAddress); - - await page.waitForSelector(`.chakra-form-control > button`); - await page.click(`.chakra-form-control > button`); - - const msgs = (await emailBox.waitMails()) as TempMailMessage[]; - let validateURL: string | undefined; - for (const msg of msgs) { - validateURL = (msg as any).body.match(/(\d{6})/i)?.[1]; - if (validateURL) { - break; - } - } - if (!validateURL) { - throw new Error('Error while obtaining verfication URL!'); - } - this.logger.info(validateURL); - const frame = await page.waitForFrame( - (v) => v.url().indexOf('particle') > -1, - ); - await sleep(10000); - await frame.waitForSelector('.react-input-code > .input-code-item'); - await frame.click('.react-input-code > .input-code-item'); - await frame.focus('.react-input-code > .input-code-item'); - // await sleep(10 * 60 * 1000); - - await page.keyboard.type(validateURL, { delay: 10 }); - - await page.waitForSelector('.chakra-form-control > div > input'); - await page.click('.chakra-form-control > div > input'); - await page.keyboard.type('e97145', { delay: 10 }); - - await page.waitForSelector('.chakra-form-control > button'); - await page.click('.chakra-form-control > button'); - - await sleep(3000); - await page.waitForSelector('.chakra-modal__content > * > * > button'); - await page.click('.chakra-modal__content > * > * > button'); - this.logger.info('visit earn shell points'); - - await page.waitForSelector( - '#redemption > div > * > * > * > div > button', - ); - await page.click('#redemption > div > * > * > * > div > button'); - - await page.waitForSelector('.chakra-modal__body > * > * > * > button'); - await page.click('.chakra-modal__body > * > * > * > button'); - - await page.waitForSelector('.chakra-modal__body > div > div > button'); - await page.click('.chakra-modal__body > div > div > button'); - await page.goto(`https://app.myshell.ai/chat`); - await this.getChatBooster(page); - account.token = await this.getToken(page); - account.visitorID = await this.getVisitorId(page); - account.battery = (await this.getBattery(account.token)).energy; - this.accountPool.syncfile(); - } - const wss = await this.initWSS( - account.token, - account.visitorID, - (v: WSS) => { - this.wssMap[account.id] = v; - }, - ); - this.wssMap[account.id] = wss; - this.logger.info(`init ok! ${account.id}`); - await browser.close(); - return [page, account]; - } catch (e: any) { - await page.screenshot({ path: `./run/error_${account.id}.png` }); - await browser.close(); - this.logger.warn(`account:${account?.id}, something error happened.`, e); - return [] as any; - } - } - - async getBattery(token: string) { - const res = await CreateAxiosProxy({}).get( - `https://api.myshell.ai/user/getUserEnergyInfo`, - { - headers: { - Authorization: 'Bearer ' + token, - }, - }, - ); - return res.data as { energy: number; dailyEnergy: number }; - } - - async initWSS( - token: string, - visitorID: string, - recreate: (wss: WSS) => void, - ): Promise { - return new Promise((resolve, reject) => { - const ws = new WSS('wss://api.myshell.ai/ws/?EIO=4&transport=websocket', { - onOpen: () => { - resolve(ws); - ws.send(`40/chat,{"token":"${token}","visitorId":"${visitorID}"}\t`); - }, - onMessage: (data: any) => { - if (data === '2') { - ws.send('3'); - } - }, - onClose: async () => { - recreate(await this.initWSS(token, visitorID, recreate)); - }, - onError: (e: any) => { - reject(e); - }, - }); - }); - } - - async getChatBooster(page: Page) { - try { - // go to reward - await page.goto('https://app.myshell.ai/rewards-center'); - - await page.waitForSelector( - '#tabs-sidebar--tabpanel-2 > div > div > div > a:nth-child(4)', - ); - await page.click( - '#tabs-sidebar--tabpanel-2 > div > div > div > a:nth-child(4)', - ); - - await sleep(2000); - // got reward - await page.waitForSelector('#Rewards > div > div > div > div > * > img', { - timeout: 10000, - }); - await page.click('#Rewards > div > div > div > div > * > img'); - - // use reward - await page.waitForSelector( - '.chakra-modal__body > div > div > div > .chakra-button', - ); - await page.click( - '.chakra-modal__body > div > div > div > .chakra-button', - ); - - // got it - await page.waitForSelector( - '.chakra-modal__footer > div > .chakra-button', - ); - await page.click('.chakra-modal__footer > div > .chakra-button'); - await this.getChatBooster(page); - } catch (e) { - this.logger.error('getChatBooster error', e); - } - } - - async getToken(page: Page) { - const token = await page.evaluate(() => localStorage.getItem('token')); - if (!token) { - throw new Error('get token failed'); - } - return token; - } - - async getVisitorId(page: Page) { - const visitorId = await page.evaluate(() => - localStorage.getItem('mix_visitorId'), - ); - if (!visitorId) { - throw new Error('get visitorId failed'); - } - return visitorId; - } - - public async askStream(req: ChatRequest, stream: EventStream) { - const [page, account, done, destroy] = this.pagePool.get(); - if (!account) { - stream.write(Event.error, { error: 'please retry later!' }); - stream.write(Event.done, { content: '' }); - stream.end(); - return; - } - const ws = this.wssMap[account.id]; - - const tt = setTimeout(() => { - stream.write(Event.error, { error: 'timeout!' }); - stream.write(Event.done, { content: '' }); - stream.end(); - done(account); - }, 5000); - const remove = ws.onData((data) => { - if (data.indexOf('42/chat,') === -1) { - return; - } - const str = data.replace('42/chat,', ''); - const [event, msg] = parseJSON<[string, TextStream]>(str, [ - '', - {}, - ] as any); - switch (event) { - case 'no_enough_energy': - stream.write(Event.error, { error: 'no_enough_energy' }); - stream.write(Event.done, { content: '' }); - stream.end(); - ws.close(); - destroy(); - return; - case 'text_stream': - tt.refresh(); - stream.write(Event.message, { content: msg.data.text }); - break; - case 'message_replied': - remove(); - clearTimeout(tt); - stream.write(Event.done, { content: '' }); - stream.end(); - if (account.battery < 30) { - destroy(); - return; - } - done(account); - break; - case 'energy_info': - account.battery = msg.data as any; - this.accountPool.syncfile(); - break; - case 'message_sent': - this.logger.info('message_sent'); - break; - case 'reply_message_created': - this.logger.info('reply_message_created'); - break; - case 'need_verify_captcha': - this.logger.warn('need_verify_captcha'); - destroy(); - ws.close(); - stream.write(Event.error, { error: 'need_verify_captcha' }); - stream.end(); - break; - default: - this.logger.warn("unknown event: '" + event + "' " + str); - break; - } - }); - const content = JSON.stringify({ - reqId: v4(), - botUid: ModelMap[req.model], - text: req.prompt, - sourceFrom: 'myshellWebsite', - }); - ws.send(`42/chat,["text_chat", ${content}]`); - account.last_use_time = moment().unix(); - this.accountPool.syncfile(); - } -}