@@ -4,6 +4,7 @@ import WebSocket from 'ws';
44import Keyv from 'keyv' ;
55import { ProxyAgent } from 'undici' ;
66import { HttpsProxyAgent } from 'https-proxy-agent' ;
7+ import { BingImageCreator } from '@timefox/bic-sydney' ;
78
89/**
910 * https://stackoverflow.com/a/58326357
@@ -39,21 +40,53 @@ export default class BingAIClient {
3940 this . options = {
4041 ...options ,
4142 host : options . host || 'https://www.bing.com' ,
43+ xForwardedFor : this . constructor . getValidIPv4 ( options . xForwardedFor ) ,
44+ features : {
45+ genImage : options ?. features ?. genImage || false ,
46+ } ,
4247 } ;
4348 }
4449 this . debug = this . options . debug ;
50+ if ( this . options . features . genImage ) {
51+ this . bic = new BingImageCreator ( this . options ) ;
52+ }
53+ }
54+
55+ static getValidIPv4 ( ip ) {
56+ const match = ! ip
57+ || ip . match ( / ^ ( ( 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) \. ) { 3 } ( 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) ( \/ ( [ 0 - 9 ] | [ 1 - 2 ] [ 0 - 9 ] | 3 [ 0 - 2 ] ) ) ? $ / ) ;
58+ if ( match ) {
59+ if ( match [ 5 ] ) {
60+ const mask = parseInt ( match [ 5 ] , 10 ) ;
61+ let [ a , b , c , d ] = ip . split ( '.' ) . map ( x => parseInt ( x , 10 ) ) ;
62+ // eslint-disable-next-line no-bitwise
63+ const max = ( 1 << ( 32 - mask ) ) - 1 ;
64+ const rand = Math . floor ( Math . random ( ) * max ) ;
65+ d += rand ;
66+ c += Math . floor ( d / 256 ) ;
67+ d %= 256 ;
68+ b += Math . floor ( c / 256 ) ;
69+ c %= 256 ;
70+ a += Math . floor ( b / 256 ) ;
71+ b %= 256 ;
72+ return `${ a } .${ b } .${ c } .${ d } ` ;
73+ }
74+ return ip ;
75+ }
76+ return undefined ;
4577 }
4678
4779 async createNewConversation ( ) {
4880 const fetchOptions = {
4981 headers : {
5082 accept : 'application/json' ,
5183 'accept-language' : 'en-US,en;q=0.9' ,
52- 'sec-ch-ua' : '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"' ,
84+ 'content-type' : 'application/json' ,
85+ 'sec-ch-ua' : '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"' ,
5386 'sec-ch-ua-arch' : '"x86"' ,
5487 'sec-ch-ua-bitness' : '"64"' ,
55- 'sec-ch-ua-full-version' : '"115 .0.1866.1 "' ,
56- 'sec-ch-ua-full-version-list' : '"Not/A)Brand ";v="99 .0.0.0 ", "Microsoft Edge ";v="115 .0.1866.1 ", "Chromium ";v="115 .0.5767 .0"' ,
88+ 'sec-ch-ua-full-version' : '"113 .0.1774.50 "' ,
89+ 'sec-ch-ua-full-version-list' : '"Microsoft Edge ";v="113 .0.1774.50 ", "Chromium ";v="113 .0.5672.127 ", "Not-A.Brand ";v="24 .0.0 .0"' ,
5790 'sec-ch-ua-mobile' : '?0' ,
5891 'sec-ch-ua-model' : '""' ,
5992 'sec-ch-ua-platform' : '"Windows"' ,
@@ -65,11 +98,13 @@ export default class BingAIClient {
6598 'sec-ms-gec-version' : '1-115.0.1866.1' ,
6699 'x-ms-client-request-id' : crypto . randomUUID ( ) ,
67100 'x-ms-useragent' : 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32' ,
101+ 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50' ,
102+ cookie : this . options . cookies || ( this . options . userToken ? `_U=${ this . options . userToken } ` : undefined ) ,
68103 Referer : 'https://www.bing.com/search?q=Bing+AI&showconv=1' ,
69104 'Referrer-Policy' : 'origin-when-cross-origin' ,
70- cookie : this . options . cookies || ( this . options . userToken ? `_U=${ this . options . userToken } ` : undefined ) ,
71105 // Workaround for request being blocked due to geolocation
72- 'x-forwarded-for' : '1.1.1.1' ,
106+ // 'x-forwarded-for': '1.1.1.1', // 1.1.1.1 seems to no longer work.
107+ ...( this . options . xForwardedFor ? { 'x-forwarded-for' : this . options . xForwardedFor } : { } ) ,
73108 } ,
74109 } ;
75110 if ( this . options . proxy ) {
@@ -310,6 +345,7 @@ export default class BingAIClient {
310345 'cricinfov2' ,
311346 'dv3sugg' ,
312347 'nojbfedge' ,
348+ ...( ( toneStyle === 'creative' && this . options . features . genImage ) ? [ 'gencontentv3' ] : [ ] ) ,
313349 ] ,
314350 sliceIds : [
315351 '222dtappid' ,
@@ -378,7 +414,8 @@ export default class BingAIClient {
378414 reject ( new Error ( 'Request aborted' ) ) ;
379415 } ) ;
380416
381- ws . on ( 'message' , ( data ) => {
417+ let bicIframe ;
418+ ws . on ( 'message' , async ( data ) => {
382419 const objects = data . toString ( ) . split ( '' ) ;
383420 const events = objects . map ( ( object ) => {
384421 try {
@@ -400,6 +437,19 @@ export default class BingAIClient {
400437 if ( ! messages ?. length || messages [ 0 ] . author !== 'bot' ) {
401438 return ;
402439 }
440+ if ( messages [ 0 ] ?. contentType === 'IMAGE' ) {
441+ // You will never get a message of this type without 'gencontentv3' being on.
442+ bicIframe = this . bic . genImageIframeSsr (
443+ messages [ 0 ] . text ,
444+ messages [ 0 ] . messageId ,
445+ progress => ( progress ?. contentIframe ? onProgress ( progress ?. contentIframe ) : null ) ,
446+ ) . catch ( ( error ) => {
447+ onProgress ( error . message ) ;
448+ bicIframe . isError = true ;
449+ return error . message ;
450+ } ) ;
451+ return ;
452+ }
403453 const updatedText = messages [ 0 ] . text ;
404454 if ( ! updatedText || updatedText === replySoFar ) {
405455 return ;
@@ -424,7 +474,7 @@ export default class BingAIClient {
424474 return ;
425475 }
426476 const messages = event . item ?. messages || [ ] ;
427- const eventMessage = messages . length ? messages [ messages . length - 1 ] : null ;
477+ let eventMessage = messages . length ? messages [ messages . length - 1 ] : null ;
428478 if ( event . item ?. result ?. error ) {
429479 if ( this . debug ) {
430480 console . debug ( event . item . result . value , event . item . result . message ) ;
@@ -469,6 +519,23 @@ export default class BingAIClient {
469519 // delete useless suggestions from moderation filter
470520 delete eventMessage . suggestedResponses ;
471521 }
522+ if ( bicIframe ) {
523+ // the last messages will be a image creation event if bicIframe is present.
524+ let i = messages . length - 1 ;
525+ while ( eventMessage ?. contentType === 'IMAGE' && i > 0 ) {
526+ eventMessage = messages [ i -= 1 ] ;
527+ }
528+
529+ // wait for bicIframe to be completed.
530+ // since we added a catch, we do not need to wrap this with a try catch block.
531+ const imgIframe = await bicIframe ;
532+ if ( ! imgIframe ?. isError ) {
533+ eventMessage . adaptiveCards [ 0 ] . body [ 0 ] . text += imgIframe ;
534+ } else {
535+ eventMessage . text += `<br>${ imgIframe } ` ;
536+ eventMessage . adaptiveCards [ 0 ] . body [ 0 ] . text = eventMessage . text ;
537+ }
538+ }
472539 resolve ( {
473540 message : eventMessage ,
474541 conversationExpiryTime : event ?. item ?. conversationExpiryTime ,
@@ -485,6 +552,11 @@ export default class BingAIClient {
485552 return ;
486553 }
487554 default :
555+ if ( event ?. error ) {
556+ clearTimeout ( messageTimeout ) ;
557+ this . constructor . cleanupWebSocketConnection ( ws ) ;
558+ reject ( new Error ( `Event Type('${ event . type } '): ${ event . error } ` ) ) ;
559+ }
488560 // eslint-disable-next-line no-useless-return
489561 return ;
490562 }
0 commit comments