├── .gitignore ├── logo.png ├── examples ├── demo.gif ├── images.php ├── chat.php └── multi.php ├── src ├── Chat │ ├── Tone.php │ ├── MessageType.php │ ├── Prompt.php │ ├── Message.php │ └── Conversation.php ├── BingAI.php ├── Tools.php └── Images │ └── ImageCreator.php ├── composer.json ├── LICENSE ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ 3 | .DS_Store -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximerenou/php-bing-ai/HEAD/logo.png -------------------------------------------------------------------------------- /examples/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximerenou/php-bing-ai/HEAD/examples/demo.gif -------------------------------------------------------------------------------- /src/Chat/Tone.php: -------------------------------------------------------------------------------- 1 | =7.1", 18 | "ratchet/pawl": "^0.4.1", 19 | "ext-curl": "*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Chat/MessageType.php: -------------------------------------------------------------------------------- 1 | text = $text; 17 | } 18 | 19 | public function withPreferences($locale = 'en-US', $market = 'en-US', $region = 'US') 20 | { 21 | $this->locale = $locale; 22 | $this->market = $market; 23 | $this->region = $region; 24 | return $this; 25 | } 26 | 27 | public function withoutCache() 28 | { 29 | $this->cache = false; 30 | return $this; 31 | } 32 | 33 | public function withImage($image, $is_rawdata = false) 34 | { 35 | if (! $is_rawdata) { 36 | $image = file_get_contents($image); 37 | } 38 | 39 | $this->image = $image; 40 | return $this; 41 | } 42 | } -------------------------------------------------------------------------------- /examples/images.php: -------------------------------------------------------------------------------- 1 | getImageCreator()->getRemainingBoosts(); 16 | 17 | echo "You have $boosts remaining boosts." . PHP_EOL; 18 | echo 'Type "q" to quit' . PHP_EOL; 19 | 20 | echo PHP_EOL . "> "; 21 | $text = rtrim(fgets(STDIN)); 22 | 23 | if ($text == 'q') 24 | exit(0); 25 | 26 | $creator = $ai->createImages($text); 27 | 28 | echo 'Generating...' . PHP_EOL; 29 | 30 | $creator->wait(); 31 | 32 | if (! $creator->hasFailed()) { 33 | $images = $creator->getImages(); 34 | 35 | foreach ($images as $image) { 36 | echo "- $image" . PHP_EOL; 37 | } 38 | } 39 | else { 40 | echo 'Generation failed' . PHP_EOL; 41 | exit(1); 42 | } 43 | 44 | exit(0); 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Maxime Renou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/BingAI.php: -------------------------------------------------------------------------------- 1 | cookie = $cookie; 15 | } 16 | 17 | public function createChatConversation($data = null) 18 | { 19 | return new Conversation($this->cookie, $data, 0); 20 | } 21 | 22 | public function resumeChatConversation($data = null, $invocations = 1) 23 | { 24 | return new Conversation($this->cookie, $data, $invocations); 25 | } 26 | 27 | public function getImageCreator() 28 | { 29 | return new ImageCreator($this->cookie); 30 | } 31 | 32 | public function createImages($prompt) 33 | { 34 | return $this->getImageCreator()->create($prompt); 35 | } 36 | 37 | public function checkCookie() 38 | { 39 | $html = Tools::request('https://www.bing.com', [ 40 | 'cookie: _U=' . $this->cookie, 41 | 'method: GET', 42 | 'accept: text/html' 43 | ]); 44 | 45 | return strpos($html, '') === false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Tools.php: -------------------------------------------------------------------------------- 1 | createChatConversation(); 18 | 19 | echo 'Type "q" to quit' . PHP_EOL; 20 | 21 | while (true) { 22 | echo PHP_EOL . "> "; 23 | $text = rtrim(fgets(STDIN)); 24 | 25 | if ($text == 'q') 26 | break; 27 | 28 | if (strpos($text, '$image') !== false) { 29 | $text = str_replace('$image', '', $text); 30 | $prompt = new \MaximeRenou\BingAI\Chat\Prompt($text); 31 | echo PHP_EOL . "Image path: "; 32 | $image = rtrim(fgets(STDIN)); 33 | $prompt->withImage($image); 34 | } else { 35 | $prompt = new \MaximeRenou\BingAI\Chat\Prompt($text); 36 | } 37 | 38 | $padding = 0; 39 | 40 | list($text, $cards) = $conversation->ask($prompt, function ($text, $cards) use (&$padding) { 41 | // Erase last line 42 | echo str_repeat(chr(8), $padding); 43 | 44 | // Print partial answer 45 | echo "- $text"; 46 | $padding = mb_strlen($text) + 2; 47 | }); 48 | 49 | // Erase last line 50 | echo str_repeat(chr(8), $padding); 51 | 52 | // Print final answer 53 | echo "- $text" . PHP_EOL; 54 | 55 | if ($conversation->kicked()) { 56 | echo "[Conversation ended]" . PHP_EOL; 57 | break; 58 | } 59 | 60 | $remaining = $conversation->getRemainingMessages(); 61 | 62 | if ($remaining != 0) { 63 | echo "[$remaining remaining messages]" . PHP_EOL; 64 | } else { 65 | echo "[Limit reached]" . PHP_EOL; 66 | break; 67 | } 68 | } 69 | 70 | exit(0); 71 | -------------------------------------------------------------------------------- /examples/multi.php: -------------------------------------------------------------------------------- 1 | createChatConversation() 16 | ->withTone(\MaximeRenou\BingAI\Chat\Tone::Creative); 17 | 18 | echo 'Type "q" to quit' . PHP_EOL; 19 | 20 | while (true) { 21 | echo PHP_EOL . "> "; 22 | $text = rtrim(fgets(STDIN)); 23 | 24 | if ($text == 'q') 25 | break; 26 | 27 | $prompt = new \MaximeRenou\BingAI\Chat\Prompt($text); 28 | $padding = 0; 29 | 30 | list($text, $cards) = $conversation->ask($prompt, function ($text, $cards) use (&$padding) { 31 | // Erase last line 32 | echo str_repeat(chr(8), $padding); 33 | 34 | $text = trim($text); 35 | 36 | // Print partial answer 37 | echo "- $text"; 38 | $padding = mb_strlen($text) + 2; 39 | }); 40 | 41 | // Erase last line 42 | echo str_repeat(chr(8), $padding); 43 | 44 | // Print final answer 45 | echo "- $text" . PHP_EOL; 46 | 47 | // Generative cards 48 | foreach ($cards as $card) { 49 | if ($card->type == \MaximeRenou\BingAI\Chat\MessageType::GenerateQuery && $card->data['contentType'] == 'IMAGE') { 50 | $loader = "Generating: {$card->text}..."; 51 | echo $loader; 52 | 53 | // Create the image 54 | $creator = $ai->createImages($card->text); 55 | $creator->wait(); 56 | 57 | echo str_repeat(chr(8), strlen($loader)); 58 | 59 | if ($creator->hasFailed()) { 60 | echo "[Image generation failed]" . PHP_EOL; 61 | } 62 | else { 63 | foreach ($creator->getImages() as $image) { 64 | echo "* $image" . PHP_EOL; 65 | } 66 | 67 | $remaining = $creator->getRemainingBoosts(); 68 | echo "[$remaining remaining boosts]" . PHP_EOL; 69 | } 70 | } 71 | } 72 | 73 | if ($conversation->ended()) { 74 | echo "[Conversation ended]" . PHP_EOL; 75 | break; 76 | } 77 | } 78 | 79 | exit(0); 80 | -------------------------------------------------------------------------------- /src/Chat/Message.php: -------------------------------------------------------------------------------- 1 | text = $prompt->text; 11 | $message->type = MessageType::Prompt; 12 | return $message; 13 | } 14 | 15 | public static function fromData($data) 16 | { 17 | $message = new self($data); 18 | $message->id = $data['messageId']; 19 | 20 | switch ($data['messageType'] ?? '') { 21 | case 'InternalSearchQuery': 22 | $message->type = MessageType::InternalSearchQuery; 23 | break; 24 | case 'InternalSearchResult': 25 | $message->type = MessageType::SearchResult; 26 | break; 27 | case 'InternalLoaderMessage': 28 | $message->type = MessageType::Loader; 29 | break; 30 | case 'SemanticSerp': 31 | $message->type = MessageType::SemanticSerp; 32 | break; 33 | case 'Disengaged': 34 | $message->type = MessageType::Disengaged; 35 | break; 36 | case 'AdsQuery': 37 | $message->type = MessageType::AdsQuery; 38 | break; 39 | case 'ActionRequest': 40 | $message->type = MessageType::ActionRequest; 41 | break; 42 | case 'RenderCardRequest': 43 | $message->type = MessageType::RenderRequest; 44 | break; 45 | case 'SearchQuery': 46 | $message->type = MessageType::SearchQuery; 47 | break; 48 | case 'GenerateContentQuery': 49 | $message->type = MessageType::GenerateQuery; 50 | break; 51 | break; 52 | case 'Context': 53 | $message->type = MessageType::Context; 54 | break; 55 | break; 56 | case 'Progress': 57 | $message->type = MessageType::Progress; 58 | break; 59 | default: 60 | $message->type = $data['author'] == 'user' ? MessageType::Prompt : MessageType::Answer; 61 | } 62 | 63 | if (! empty($data['text'])) 64 | $message->text = $data['text']; 65 | elseif (! empty($data['hiddenText'])) 66 | $message->text = $data['hiddenText']; 67 | 68 | return $message; 69 | } 70 | 71 | public $id; 72 | public $text; 73 | public $type; 74 | public $data; 75 | 76 | public function __construct($data = null) 77 | { 78 | $this->data = $data; 79 | } 80 | 81 | public function toText() 82 | { 83 | if (! empty($this->text)) { 84 | return preg_replace('/\[\^[0-9]+\^]/', '', $this->text); 85 | } 86 | 87 | return false; 88 | } 89 | 90 | public function toArray() 91 | { 92 | return [ 93 | 'id' => $this->id, 94 | 'text' => $this->text, 95 | 'formatted_text' => $this->toText(), 96 | 'type' => $this->type, 97 | 'data' => $this->data 98 | ]; 99 | } 100 | 101 | public function jsonSerialize(): mixed 102 | { 103 | return $this->toArray(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Images/ImageCreator.php: -------------------------------------------------------------------------------- 1 | cookie = $cookie; 19 | } 20 | 21 | public function resume($generation_id, $prompt) 22 | { 23 | $this->generation_id = $generation_id; 24 | $this->prompt = $prompt; 25 | $this->generating = true; 26 | 27 | return $this; 28 | } 29 | 30 | public function create($prompt) 31 | { 32 | $this->prompt = $prompt; 33 | 34 | $prompt_encoded = urlencode($prompt); 35 | $url = "https://www.bing.com/images/create?q=$prompt_encoded&rt=4&FORM=GENCRE"; 36 | $headers = [ 37 | 'cookie: _U=' . $this->cookie, 38 | 'method: POST', 39 | 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 40 | "accept-language: en;q=0.9", 41 | 'content-type: application/x-www-form-urlencoded', 42 | 'referer: https://www.bing.com/images/create?FORM=GENILP' 43 | ]; 44 | 45 | list($data, $request, $url) = Tools::request($url, $headers, "q=$prompt_encoded&qs=ds", true); 46 | 47 | $query = parse_url($url, PHP_URL_QUERY); 48 | $params = []; 49 | 50 | Tools::debug("Query $query"); 51 | 52 | if (! empty($query)) { 53 | parse_str($query, $params); 54 | 55 | if (isset($params['id'])) { 56 | Tools::debug("ID {$params['id']}"); 57 | 58 | $this->generation_id = $params['id']; 59 | $this->generating = true; 60 | 61 | return $this; 62 | } 63 | } 64 | 65 | $this->failed = true; 66 | 67 | return $this; 68 | } 69 | 70 | public function hasFailed() 71 | { 72 | return $this->failed; 73 | } 74 | 75 | public function isGenerating() 76 | { 77 | if ($this->hasFailed()) 78 | return false; 79 | 80 | $prompt_encoded = urlencode($this->prompt); 81 | $url = "https://www.bing.com/images/create/async/results/{$this->generation_id}?q={$prompt_encoded}"; 82 | 83 | $data = Tools::request($url, [ 84 | 'cookie: _U=' . $this->cookie, 85 | 'method: GET', 86 | 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 87 | "accept-language: en;q=0.9", 88 | 'content-type: application/x-www-form-urlencoded', 89 | 'referer: https://www.bing.com/images/create?FORM=GENILP' 90 | ]); 91 | 92 | if (! is_string($data)) 93 | return $this->generating; 94 | 95 | if (preg_match_all('/(https:\/\/th.bing\.com\/th\/id\/[A-Za-z0-9._-]+)\?/', $data, $matches)) { 96 | $this->images = $matches[1]; 97 | $this->generating = false; 98 | Tools::debug("Images: " . implode(", ", $this->images)); 99 | } 100 | elseif ($data = json_decode($data, true)) { 101 | if (! empty($data["errorMessage"])) { 102 | $this->failed = true; 103 | Tools::debug("Error: {$data["errorMessage"]}"); 104 | } 105 | } 106 | 107 | return $this->generating; 108 | } 109 | 110 | public function getRemainingBoosts() 111 | { 112 | $data = Tools::request("https://www.bing.com/images/create", [ 113 | 'cookie: _U=' . $this->cookie, 114 | 'method: GET', 115 | 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 116 | "accept-language: en;q=0.9", 117 | ]); 118 | 119 | if (! is_string($data) || ! preg_match('/
([0-9]+)<\/div>/', $data, $matches)) 120 | return 0; 121 | 122 | return intval($matches[1]); 123 | } 124 | 125 | public function wait() 126 | { 127 | while (! $this->hasFailed()) { 128 | Tools::debug("Waiting for images generation..."); 129 | sleep(1); 130 | 131 | if (! $this->isGenerating()) 132 | break; 133 | } 134 | } 135 | 136 | public function getImages() 137 | { 138 | return $this->images; 139 | } 140 | 141 | public function getGenerationId() 142 | { 143 | return $this->generation_id; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __New feature: send images in chat 🔥__ 2 | 3 | ![Bing + PHP](logo.png) 4 | 5 | # Bing AI client 6 | 7 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT) 8 | [![Latest Stable Version](https://img.shields.io/github/v/release/maximerenou/php-bing-ai)](https://packagist.org/packages/maximerenou/bing-ai) 9 | [![PHP version](https://img.shields.io/packagist/dependency-v/maximerenou/bing-ai/php)](https://packagist.org/packages/maximerenou/bing-ai) 10 | [![cURL extension required](https://img.shields.io/packagist/dependency-v/maximerenou/bing-ai/ext-curl)](https://packagist.org/packages/maximerenou/bing-ai) 11 | 12 | This is an unofficial PHP client for **Bing AI**, including **Chat (GPT-4)** and **Image Creator (DALL-E)**. 13 | 14 | ## Installation 15 | 16 | composer require maximerenou/bing-ai 17 | 18 | ## Demo 19 | 20 | This demo program uses both Chat and Image Creator: 21 | 22 | ![Demo](examples/demo.gif) 23 | 24 | Source: `examples/multi.php`. 25 | 26 | ## Usage 27 | 28 | - [Chat AI](#chat-ai) 29 | - [Image Creator AI](#image-creator) 30 | 31 | First, you need to sign in on bing.com and get your `_U` cookie. 32 | 33 | > ❗ Make sure you send a first message to Bing Chat before using your cookie (CAPTCHA bypass) 34 | 35 |
36 | How to get this cookie? 37 | 38 | 1. Navigate to bing.com 39 | 2. Sign in using your Microsoft account 40 | 3. Back on bing.com, go to Bing Chat page and send a message (CAPTCHA verification occurs sometimes) 41 | 4. Then, right-click and select "Inspect" - the browser console appears 42 | 4. Go to "Application" tab 43 | 5. Select "Cookies" > "https://www.bing.com" in the sidebar 44 | 6. Search for "_U" cookie 45 | 7. Copy its content 46 | 47 |
48 | 49 |
50 | How to check if my cookie is working properly? 51 | 52 | ```php 53 | use MaximeRenou\BingAI\BingAI; 54 | 55 | // $cookie - your "_U" cookie from bing.com 56 | $ai = new BingAI($cookie); 57 | $valid = $ai->checkCookie(); 58 | ``` 59 | 60 |
61 | 62 | --------------------------------------- 63 | 64 | ### Chat AI 65 | 66 | **Demo**: clone this repo, edit and run `examples/chat.php`. 67 | 68 | ```php 69 | use MaximeRenou\BingAI\BingAI; 70 | use MaximeRenou\BingAI\Chat\Prompt; 71 | 72 | // $cookie - your "_U" cookie from bing.com 73 | $ai = new BingAI($cookie); 74 | 75 | $conversation = $ai->createChatConversation(); 76 | 77 | // $text - Text-only version of Bing's answer 78 | // $cards - Message objects array 79 | list($text, $cards) = $conversation->ask(new Prompt("Hello World")); 80 | ``` 81 | 82 | > `$cards` contains all "messages" exchanged with Bing AI. It can be text (prompt or answer), signals, suggestions, image generation requests, etc. Check `Message.php` to learn more 83 | about its format. 84 | 85 |
86 | 🔥 Image analysis 87 | 88 | You may attach an image to your message: 89 | 90 | ```php 91 | $prompt = new Prompt("How cute is this animal?"); 92 | $prompt->withImage('/path/to/panda.png'); 93 | //or: $prompt->withImage($raw_image_data, true); 94 | 95 | $conversation->ask($prompt, ...); 96 | ``` 97 | 98 | > **Try it!** Type _`$image`_ at the end of your message with `examples/chat.php`. 99 | 100 |
101 | 102 |
103 | Real-time / progressive answer 104 | 105 | You may pass a function as second argument to get real-time progression: 106 | 107 | ```php 108 | // $text - Incomplete text version 109 | // $cards - Incomplete messages fleet 110 | list($final_text, $final_cards) = $conversation->ask($prompt, function ($text, $cards) { 111 | echo $text; 112 | }); 113 | ``` 114 | 115 |
116 | 117 |
118 | Locale and location preferences 119 | 120 | ```php 121 | $conversation = $ai->createChatConversation() 122 | ->withLocation($latitude, $longitude, $radius) // Optional 123 | ->withPreferences('fr-FR', 'fr-FR', 'FR'); // Optional 124 | ``` 125 | 126 |
127 | 128 |
129 | Tone choice 130 | 131 | ```php 132 | use MaximeRenou\BingAI\Chat\Tone; 133 | 134 | $conversation = $ai->createChatConversation() 135 | ->withTone(Tone::Creative); // Optional 136 | 137 | // Choices: 138 | // Tone::Balanced (default) 139 | // Tone::Creative 140 | // Tone::Precise 141 | ``` 142 | 143 |
144 | 145 |
146 | Resume a conversation 147 | 148 | If you want to resume a previous conversation, you can retrieve its identifiers: 149 | 150 | ```php 151 | // Get current identifiers 152 | $identifiers = $conversation->getIdentifiers(); 153 | 154 | // ... 155 | // Resume conversation with $identifiers parameter, and number of previous questions asked 156 | $conversation = $ai->resumeChatConversation($identifiers, 1); 157 | ``` 158 | 159 |
160 | 161 |
162 | Text generation 163 | 164 | ```php 165 | $subject = "Internet memes"; 166 | $tone = 'funny'; 167 | $type = 'blog post'; 168 | $length = 'short'; 169 | 170 | $prompt = new Prompt("Please write a *$length* *$type* in a *$tone* style about `$subject`. Please wrap the $type in a markdown codeblock."); 171 | 172 | $conversation->ask($prompt->withoutCache(), ...) 173 | ``` 174 | 175 | > To prevent answers like "I have already written \[...]", you can disable cache for your prompt with `withoutCache()`. 176 | 177 |
178 | 179 |
180 | Handle throttling and kicks 181 | 182 | Bing is limiting messages count per conversations. You can monitor it by calling `getRemainingMessages()` after every interaction. 183 | 184 | ```php 185 | $remaining = $conversation->getRemainingMessages(); 186 | 187 | if ($remaining === 0) { 188 | // You reached the limit 189 | } 190 | ``` 191 | 192 | After every interaction, you should also check if you have been kicked from the conversation: 193 | 194 | ```php 195 | if ($conversation->kicked()) { 196 | // You have been kicked, you should start a new conversation 197 | } 198 | ``` 199 | 200 | You may combine both checks with: 201 | 202 | ```php 203 | if ($conversation->ended()) { 204 | // You reached the limit or have been kicked 205 | } 206 | ``` 207 | 208 |
209 | 210 | --------------------------------------- 211 | 212 | ### Image Creator 213 | 214 | **Demo**: clone this repo, edit and run `examples/images.php`. 215 | 216 | ```php 217 | use MaximeRenou\BingAI\BingAI; 218 | 219 | // $cookie - your "_U" cookie from bing.com 220 | $ai = new BingAI($cookie); 221 | 222 | $creator = $ai->createImages("A 3D teddy bear"); 223 | 224 | $creator->wait(); 225 | 226 | // Finally, get images URLs 227 | if (! $creator->hasFailed()) { 228 | $images = $creator->getImages(); 229 | } 230 | ``` 231 | 232 | > Image generation can become slower after consuming all of your "boosts". Check the section below to stay aware of your remaining boosts. 233 | 234 |
235 | Check remaining boosts 236 | 237 | ```php 238 | $creator = $ai->getImageCreator(); 239 | 240 | $remaining_boosts = $creator->getRemainingBoosts(); 241 | ``` 242 | 243 |
244 | 245 |
246 | Asynchronous generation 247 | You may quit after calling `createImages()` and check generation later using its ID: 248 | 249 | ```php 250 | $prompt = "A 3D teddy bear"; 251 | $creator = $ai->createImages($prompt); 252 | $generation_id = $creator->getGenerationId(); 253 | 254 | // ... 255 | 256 | $creator = $ai->getImageCreator(); 257 | $creator->resume($generation_id, $prompt); 258 | ``` 259 | 260 |
261 | 262 |
263 | Manually wait 264 | Instead of calling `$creator->wait();` you can loop by yourself: 265 | 266 | ```php 267 | do { 268 | sleep(1); 269 | } while ($creator->isGenerating()); 270 | ``` 271 | 272 |
273 | 274 | --------------------------------------- 275 | 276 | #### Disclaimer 277 | 278 | Using Bing AI outside bing.com may violate Bing terms. Use it at your own risk. 279 | Bing is a trademark of Microsoft. 280 | -------------------------------------------------------------------------------- /src/Chat/Conversation.php: -------------------------------------------------------------------------------- 1 | cookie = $cookie; 37 | 38 | if (! is_array($identifiers)) 39 | $identifiers = $this->createIdentifiers(); 40 | 41 | $this->id = $identifiers['conversationId']; 42 | $this->client_id = $identifiers['clientId']; 43 | $this->signature = $identifiers['conversationSignature']; 44 | $this->invocations = $invocations - 1; 45 | } 46 | 47 | public function getIdentifiers() 48 | { 49 | return [ 50 | 'conversationId' => $this->id, 51 | 'clientId' => $this->client_id, 52 | 'conversationSignature' => $this->signature 53 | ]; 54 | } 55 | 56 | public function withTone($tone) 57 | { 58 | $this->tone = $tone; 59 | return $this; 60 | } 61 | 62 | public function withPreferences($locale = 'en-US', $market = 'en-US', $region = 'US') 63 | { 64 | $this->locale = $locale; 65 | $this->market = $market; 66 | $this->region = $region; 67 | return $this; 68 | } 69 | 70 | public function withLocation($latitude, $longitude, $radius = 1000) 71 | { 72 | $this->geolocation = [$latitude, $longitude, $radius]; 73 | return $this; 74 | } 75 | 76 | public function getRemainingMessages() 77 | { 78 | if (is_null($this->max_messages_count)) 79 | return 1; 80 | 81 | return $this->max_messages_count - $this->user_messages_count; 82 | } 83 | 84 | public function kicked() 85 | { 86 | return $this->kicked; 87 | } 88 | 89 | public function ended() 90 | { 91 | return $this->kicked || $this->getRemainingMessages() <= 0; 92 | } 93 | 94 | public function createIdentifiers() 95 | { 96 | $data = Tools::request("https://www.bing.com/turing/conversation/create", [ 97 | 'cookie: _U=' . $this->cookie, 98 | 'method: GET', 99 | 'accept: application/json', 100 | "accept-language: {$this->region},{$this->locale};q=0.9", 101 | 'content-type: application/json', 102 | 'x-ms-client-request-id' => Tools::generateUUID(), 103 | ]); 104 | 105 | $data = json_decode($data, true); 106 | 107 | if (! is_array($data) || ! isset($data['result']) || ! isset($data['result']['value']) || $data['result']['value'] != 'Success') 108 | throw new \Exception("Failed to init conversation"); 109 | 110 | return $data; 111 | } 112 | 113 | public function uploadImage($image_data) 114 | { 115 | $image_encoded = base64_encode($image_data); 116 | 117 | $form = [ 118 | 'knowledgeRequest' => json_encode([ 119 | "imageInfo" => (object) [], 120 | "knowledgeRequest" => [ 121 | "invokedSkills" => ["ImageById"], 122 | "subscriptionId" => "Bing.Chat.Multimodal", 123 | "invokedSkillsRequestData" => [ 124 | "enableFaceBlur" => true 125 | ], 126 | "convoData" => [ 127 | "convoid" => '', // $this->id ? 128 | "convotone" => 'Precise' // $this->tone ? 129 | ] 130 | ] 131 | ]), 132 | 'imageBase64' => $image_encoded 133 | ]; 134 | 135 | Tools::debug("Image upload request: " . print_r($form, true)); 136 | 137 | $data = Tools::request("https://www.bing.com/images/kblob", [ 138 | 'cookie: _U=' . $this->cookie, 139 | 'method: POST', 140 | 'Content-Type: multipart/form-data', 141 | 'referer: https://www.bing.com', 142 | ], $form); 143 | 144 | Tools::debug("Image upload response: $data"); 145 | 146 | $data = json_decode($data, true); 147 | 148 | if (! is_array($data) || empty($data['blobId']) || empty($data['processedBlobId'])) 149 | throw new \Exception("Failed to upload image"); 150 | 151 | return [ 152 | 'originalImageUrl' => 'https://www.bing.com/images/blob?bcid=' . $data['blobId'], 153 | 'imageUrl' => 'https://www.bing.com/images/blob?bcid=' . $data['processedBlobId'], 154 | ]; 155 | } 156 | 157 | public function ask(Prompt $message, $callback = null) 158 | { 159 | $this->invocations++; 160 | $this->current_started = false; 161 | $this->current_text = ''; 162 | 163 | $this->current_messages = [ 164 | Message::fromPrompt($message) 165 | ]; 166 | 167 | $headers = [ 168 | 'accept-language' => $this->locale . ',' . $this->region . ';q=0.9', 169 | 'cache-control' => 'no-cache', 170 | 'pragma' => 'no-cache' 171 | ]; 172 | 173 | Tools::debug("Creating loop"); 174 | 175 | \React\EventLoop\Loop::set(\React\EventLoop\Factory::create()); 176 | 177 | $loop = \React\EventLoop\Loop::get(); 178 | 179 | \Ratchet\Client\connect('wss://sydney.bing.com/sydney/ChatHub', [], $headers, $loop)->then(function ($connection) use ($message, $callback) { 180 | Tools::debug("Connection open"); 181 | 182 | $connection->on('message', function ($raw) use ($connection, $message, $callback) { 183 | Tools::debug("Packet received"); 184 | Tools::debug($raw); 185 | $this->handlePacket($raw, $connection, $message, $callback); 186 | }); 187 | 188 | $connection->on('close', function () { 189 | Tools::debug("Connection closed"); 190 | }); 191 | 192 | Tools::debug("Sending first packet"); 193 | $connection->send(json_encode(['protocol' => 'json', 'version' => 1]) . self::END_CHAR); 194 | }, function ($error) { 195 | throw new \Exception($error->getMessage()); 196 | }); 197 | 198 | $loop->run(); 199 | 200 | return [$this->current_text, $this->current_messages]; 201 | } 202 | 203 | public function handlePacket($raw, $connection, $message, $callback) 204 | { 205 | $objects = explode(self::END_CHAR, $raw); 206 | 207 | $objects = array_map(function ($object) { 208 | return json_decode($object, true); 209 | }, $objects); 210 | 211 | $objects = array_filter($objects, function ($value) { 212 | return is_array($value); 213 | }); 214 | 215 | if (count($objects) === 0) { 216 | return; 217 | } 218 | 219 | if (! $this->current_started) { 220 | Tools::debug("Sending start ping"); 221 | $connection->send(json_encode(['type' => 6]) . self::END_CHAR); 222 | 223 | $trace_id = bin2hex(random_bytes(16)); 224 | $location = null; 225 | 226 | if (is_array($this->geolocation)) { 227 | $location = "lat:{$this->geolocation[0]};long={$this->geolocation[1]};re={$this->geolocation[2]}m;"; 228 | // Example format: "lat:47.639557;long:-122.128159;re=1000m;"; 229 | } 230 | 231 | $locationHints = [ 232 | // Example hint 233 | /*[ 234 | "country" => "France", 235 | "timezoneoffset" => 1, 236 | "countryConfidence" => 9, 237 | "Center" => [ 238 | "Latitude" => 48, 239 | "Longitude" => 1 240 | ], 241 | "RegionType" => 2, 242 | "SourceType" => 1 243 | ]*/ 244 | ]; 245 | 246 | $options = [ 247 | 'nlu_direct_response_filter', 248 | 'deepleo', 249 | 'enable_debug_commands', 250 | 'disable_emoji_spoken_text', 251 | 'responsible_ai_policy_235', 252 | 'enablemm', 253 | 254 | // V2 options 255 | 'enbfpr', 256 | 'jb095', 257 | 'jbfv1', 258 | 'nojbfedge', 259 | 'weasgv2', 260 | 'dv3sugg', 261 | 'inputlanguage', 262 | 'rediscluster', 263 | 264 | // V3 options 265 | "iyxapbing", 266 | "iycapbing", 267 | "enpcktrk", 268 | "logosv1", 269 | "iyolojb", 270 | ]; 271 | 272 | 273 | if ($this->tone === Tone::Creative) { 274 | $options = array_merge($options, [ 275 | 'h3imaginative', 276 | 'clgalileo', 277 | 'gencontentv3', 278 | 'gencontentv5' 279 | ]); 280 | } 281 | elseif ($this->tone === Tone::Precise) { 282 | $options = array_merge($options, [ 283 | 'h3precise', 284 | 'clgalileo', 285 | 'gencontentv5', 286 | 287 | // V3 options 288 | 'gencontentv3', 289 | ]); 290 | } 291 | else { 292 | $options = array_merge($options, [ 293 | 'galileo', 294 | 'visualcreative', 295 | 296 | // V3 options 297 | 'harmonyv3', 298 | 'saharagenconv5', 299 | ]); 300 | } 301 | 302 | if (! $message->cache) { 303 | $options[] = "nocache"; 304 | } 305 | 306 | $message_data = [ 307 | 'locale' => $message->locale ?? $this->locale, 308 | 'market' => $message->market ?? $this->market, 309 | 'region' => $message->region ?? $this->region, 310 | 'location' => $location, 311 | 'locationHints' => $locationHints, 312 | 'timestamp' => date('Y-m-d') . 'T' . date('H:i:sP'), 313 | 'author' => 'user', 314 | 'inputMethod' => 'Keyboard', 315 | 'text' => $message->text, 316 | 'messageType' => 'Chat', 317 | ]; 318 | 319 | if (! empty($message->image)) { 320 | $message_data = array_merge($message_data, $this->uploadImage($message->image)); 321 | $message->image = null; 322 | } 323 | 324 | $params = [ 325 | 'arguments' => [ 326 | [ 327 | 'source' => 'cib', 328 | 'verbosity' => 'verbose', 329 | 'scenario' => 'SERP', 330 | 'optionsSets' => $options, 331 | 'allowedMessageTypes' => [ 332 | 'Chat', 333 | 'InternalSearchQuery', 334 | 'InternalSearchResult', 335 | 'InternalLoaderMessage', 336 | 'RenderCardRequest', 337 | 'AdsQuery', 338 | 'SemanticSerp', 339 | 'Disengaged', 340 | 'ActionRequest', 341 | 'GenerateContentQuery', 342 | 'SearchQuery', 343 | 344 | // V3 345 | 'Context', 346 | 'Progress', 347 | ], 348 | 'sliceIds' => [], 349 | 'traceId' => $trace_id, 350 | 'isStartOfSession' => $this->invocations == 0, 351 | 'message' => $message_data, 352 | 'conversationSignature' => $this->signature, 353 | 'participant' => ['id' => $this->client_id], 354 | 'conversationId' => $this->id, 355 | 'spokenTextMode' => 'None', 356 | ] 357 | ], 358 | 'invocationId' => "{$this->invocations}", 359 | 'target' => 'chat', 360 | 'type' => 4 361 | ]; 362 | 363 | $connection->send(json_encode($params) . self::END_CHAR); 364 | 365 | $this->current_started = true; 366 | 367 | return; 368 | } 369 | 370 | foreach ($objects as $object) { 371 | if ($this->handleObject($object, $callback)) 372 | $connection->close(); 373 | } 374 | } 375 | 376 | public function handleObject($object, $callback = null) 377 | { 378 | $terminate = false; 379 | 380 | switch ($object['type']) 381 | { 382 | case 1: // Partial result 383 | $messages = []; 384 | 385 | foreach ($object['arguments'] as $argument) { 386 | if (isset($argument['messages']) && is_array($argument['messages'])) { 387 | foreach ($argument['messages'] as $messageData) { 388 | $messages[] = Message::fromData($messageData); 389 | } 390 | } 391 | 392 | if (isset($argument['throttling']) && is_array($argument['throttling'])) { 393 | if (isset($argument['throttling']['maxNumUserMessagesInConversation'])) { 394 | $this->max_messages_count = $argument['throttling']['maxNumUserMessagesInConversation']; 395 | } 396 | 397 | if (isset($argument['throttling']['numUserMessagesInConversation'])) { 398 | $this->user_messages_count = $argument['throttling']['numUserMessagesInConversation']; 399 | } 400 | } 401 | } 402 | 403 | foreach ($this->current_messages as $previous_message) { 404 | $older = true; 405 | 406 | foreach ($messages as $i => $message) { 407 | if ($message->id == $previous_message->id) { 408 | $older = false; 409 | break; 410 | } 411 | } 412 | 413 | if ($older) { 414 | array_unshift($messages, $previous_message); 415 | } 416 | } 417 | 418 | $this->current_messages = $messages; 419 | break; 420 | case 2: // Global result 421 | $this->current_messages = []; 422 | 423 | if (isset($object['item']['messages']) && is_array($object['item']['messages'])) { 424 | foreach ($object['item']['messages'] as $messageData) { 425 | $this->current_messages[] = Message::fromData($messageData); 426 | } 427 | } 428 | 429 | if (isset($object['item']['throttling']) && is_array($object['item']['throttling'])) { 430 | if (isset($object['item']['throttling']['maxNumUserMessagesInConversation'])) { 431 | $this->max_messages_count = $object['item']['throttling']['maxNumUserMessagesInConversation']; 432 | } 433 | 434 | if (isset($object['item']['throttling']['numUserMessagesInConversation'])) { 435 | $this->user_messages_count = $object['item']['throttling']['numUserMessagesInConversation']; 436 | } 437 | } 438 | break; 439 | case 3: // Answer ended 440 | // Available: $object['invocationId']; 441 | $terminate = true; 442 | break; 443 | case 6: 444 | // Ping 445 | return $terminate; 446 | } 447 | 448 | $text_parts = []; 449 | 450 | foreach ($this->current_messages as $message) { 451 | if ($message->type == MessageType::Answer) 452 | $text_parts[] = $message->toText(); 453 | elseif ($message->type == MessageType::Disengaged) 454 | $this->kicked = true; 455 | } 456 | 457 | $this->current_text = trim(implode('. ', array_filter($text_parts))); 458 | 459 | // Callback 460 | 461 | if (! is_null($callback)) { 462 | call_user_func_array($callback, [$this->current_text, $this->current_messages]); 463 | } 464 | 465 | return $terminate; 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "01757a5ae48b5afd1a53a2d727e20bfe", 8 | "packages": [ 9 | { 10 | "name": "evenement/evenement", 11 | "version": "v3.0.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/igorw/evenement.git", 15 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 20 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^6.0" 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "psr-0": { 32 | "Evenement": "src" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "Igor Wiedler", 42 | "email": "igor@wiedler.ch" 43 | } 44 | ], 45 | "description": "Événement is a very simple event dispatching library for PHP", 46 | "keywords": [ 47 | "event-dispatcher", 48 | "event-emitter" 49 | ], 50 | "support": { 51 | "issues": "https://github.com/igorw/evenement/issues", 52 | "source": "https://github.com/igorw/evenement/tree/master" 53 | }, 54 | "time": "2017-07-23T21:35:13+00:00" 55 | }, 56 | { 57 | "name": "guzzlehttp/psr7", 58 | "version": "1.9.1", 59 | "source": { 60 | "type": "git", 61 | "url": "https://github.com/guzzle/psr7.git", 62 | "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" 63 | }, 64 | "dist": { 65 | "type": "zip", 66 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", 67 | "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", 68 | "shasum": "" 69 | }, 70 | "require": { 71 | "php": ">=5.4.0", 72 | "psr/http-message": "~1.0", 73 | "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" 74 | }, 75 | "provide": { 76 | "psr/http-message-implementation": "1.0" 77 | }, 78 | "require-dev": { 79 | "ext-zlib": "*", 80 | "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" 81 | }, 82 | "suggest": { 83 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 84 | }, 85 | "type": "library", 86 | "autoload": { 87 | "files": [ 88 | "src/functions_include.php" 89 | ], 90 | "psr-4": { 91 | "GuzzleHttp\\Psr7\\": "src/" 92 | } 93 | }, 94 | "notification-url": "https://packagist.org/downloads/", 95 | "license": [ 96 | "MIT" 97 | ], 98 | "authors": [ 99 | { 100 | "name": "Graham Campbell", 101 | "email": "hello@gjcampbell.co.uk", 102 | "homepage": "https://github.com/GrahamCampbell" 103 | }, 104 | { 105 | "name": "Michael Dowling", 106 | "email": "mtdowling@gmail.com", 107 | "homepage": "https://github.com/mtdowling" 108 | }, 109 | { 110 | "name": "George Mponos", 111 | "email": "gmponos@gmail.com", 112 | "homepage": "https://github.com/gmponos" 113 | }, 114 | { 115 | "name": "Tobias Nyholm", 116 | "email": "tobias.nyholm@gmail.com", 117 | "homepage": "https://github.com/Nyholm" 118 | }, 119 | { 120 | "name": "Márk Sági-Kazár", 121 | "email": "mark.sagikazar@gmail.com", 122 | "homepage": "https://github.com/sagikazarmark" 123 | }, 124 | { 125 | "name": "Tobias Schultze", 126 | "email": "webmaster@tubo-world.de", 127 | "homepage": "https://github.com/Tobion" 128 | } 129 | ], 130 | "description": "PSR-7 message implementation that also provides common utility methods", 131 | "keywords": [ 132 | "http", 133 | "message", 134 | "psr-7", 135 | "request", 136 | "response", 137 | "stream", 138 | "uri", 139 | "url" 140 | ], 141 | "support": { 142 | "issues": "https://github.com/guzzle/psr7/issues", 143 | "source": "https://github.com/guzzle/psr7/tree/1.9.1" 144 | }, 145 | "funding": [ 146 | { 147 | "url": "https://github.com/GrahamCampbell", 148 | "type": "github" 149 | }, 150 | { 151 | "url": "https://github.com/Nyholm", 152 | "type": "github" 153 | }, 154 | { 155 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 156 | "type": "tidelift" 157 | } 158 | ], 159 | "time": "2023-04-17T16:00:37+00:00" 160 | }, 161 | { 162 | "name": "psr/http-message", 163 | "version": "1.0.1", 164 | "source": { 165 | "type": "git", 166 | "url": "https://github.com/php-fig/http-message.git", 167 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 168 | }, 169 | "dist": { 170 | "type": "zip", 171 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 172 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 173 | "shasum": "" 174 | }, 175 | "require": { 176 | "php": ">=5.3.0" 177 | }, 178 | "type": "library", 179 | "extra": { 180 | "branch-alias": { 181 | "dev-master": "1.0.x-dev" 182 | } 183 | }, 184 | "autoload": { 185 | "psr-4": { 186 | "Psr\\Http\\Message\\": "src/" 187 | } 188 | }, 189 | "notification-url": "https://packagist.org/downloads/", 190 | "license": [ 191 | "MIT" 192 | ], 193 | "authors": [ 194 | { 195 | "name": "PHP-FIG", 196 | "homepage": "http://www.php-fig.org/" 197 | } 198 | ], 199 | "description": "Common interface for HTTP messages", 200 | "homepage": "https://github.com/php-fig/http-message", 201 | "keywords": [ 202 | "http", 203 | "http-message", 204 | "psr", 205 | "psr-7", 206 | "request", 207 | "response" 208 | ], 209 | "support": { 210 | "source": "https://github.com/php-fig/http-message/tree/master" 211 | }, 212 | "time": "2016-08-06T14:39:51+00:00" 213 | }, 214 | { 215 | "name": "ralouphie/getallheaders", 216 | "version": "3.0.3", 217 | "source": { 218 | "type": "git", 219 | "url": "https://github.com/ralouphie/getallheaders.git", 220 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 221 | }, 222 | "dist": { 223 | "type": "zip", 224 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 225 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 226 | "shasum": "" 227 | }, 228 | "require": { 229 | "php": ">=5.6" 230 | }, 231 | "require-dev": { 232 | "php-coveralls/php-coveralls": "^2.1", 233 | "phpunit/phpunit": "^5 || ^6.5" 234 | }, 235 | "type": "library", 236 | "autoload": { 237 | "files": [ 238 | "src/getallheaders.php" 239 | ] 240 | }, 241 | "notification-url": "https://packagist.org/downloads/", 242 | "license": [ 243 | "MIT" 244 | ], 245 | "authors": [ 246 | { 247 | "name": "Ralph Khattar", 248 | "email": "ralph.khattar@gmail.com" 249 | } 250 | ], 251 | "description": "A polyfill for getallheaders.", 252 | "support": { 253 | "issues": "https://github.com/ralouphie/getallheaders/issues", 254 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 255 | }, 256 | "time": "2019-03-08T08:55:37+00:00" 257 | }, 258 | { 259 | "name": "ratchet/pawl", 260 | "version": "v0.4.1", 261 | "source": { 262 | "type": "git", 263 | "url": "https://github.com/ratchetphp/Pawl.git", 264 | "reference": "af70198bab77a582b31169d3cc3982bed25c161f" 265 | }, 266 | "dist": { 267 | "type": "zip", 268 | "url": "https://api.github.com/repos/ratchetphp/Pawl/zipball/af70198bab77a582b31169d3cc3982bed25c161f", 269 | "reference": "af70198bab77a582b31169d3cc3982bed25c161f", 270 | "shasum": "" 271 | }, 272 | "require": { 273 | "evenement/evenement": "^3.0 || ^2.0", 274 | "guzzlehttp/psr7": "^2.0 || ^1.7", 275 | "php": ">=5.4", 276 | "ratchet/rfc6455": "^0.3.1", 277 | "react/socket": "^1.9" 278 | }, 279 | "require-dev": { 280 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8" 281 | }, 282 | "suggest": { 283 | "reactivex/rxphp": "~2.0" 284 | }, 285 | "type": "library", 286 | "autoload": { 287 | "files": [ 288 | "src/functions_include.php" 289 | ], 290 | "psr-4": { 291 | "Ratchet\\Client\\": "src" 292 | } 293 | }, 294 | "notification-url": "https://packagist.org/downloads/", 295 | "license": [ 296 | "MIT" 297 | ], 298 | "description": "Asynchronous WebSocket client", 299 | "keywords": [ 300 | "Ratchet", 301 | "async", 302 | "client", 303 | "websocket", 304 | "websocket client" 305 | ], 306 | "support": { 307 | "issues": "https://github.com/ratchetphp/Pawl/issues", 308 | "source": "https://github.com/ratchetphp/Pawl/tree/v0.4.1" 309 | }, 310 | "time": "2021-12-10T14:32:34+00:00" 311 | }, 312 | { 313 | "name": "ratchet/rfc6455", 314 | "version": "v0.3.1", 315 | "source": { 316 | "type": "git", 317 | "url": "https://github.com/ratchetphp/RFC6455.git", 318 | "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb" 319 | }, 320 | "dist": { 321 | "type": "zip", 322 | "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb", 323 | "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb", 324 | "shasum": "" 325 | }, 326 | "require": { 327 | "guzzlehttp/psr7": "^2 || ^1.7", 328 | "php": ">=5.4.2" 329 | }, 330 | "require-dev": { 331 | "phpunit/phpunit": "^5.7", 332 | "react/socket": "^1.3" 333 | }, 334 | "type": "library", 335 | "autoload": { 336 | "psr-4": { 337 | "Ratchet\\RFC6455\\": "src" 338 | } 339 | }, 340 | "notification-url": "https://packagist.org/downloads/", 341 | "license": [ 342 | "MIT" 343 | ], 344 | "authors": [ 345 | { 346 | "name": "Chris Boden", 347 | "email": "cboden@gmail.com", 348 | "role": "Developer" 349 | }, 350 | { 351 | "name": "Matt Bonneau", 352 | "role": "Developer" 353 | } 354 | ], 355 | "description": "RFC6455 WebSocket protocol handler", 356 | "homepage": "http://socketo.me", 357 | "keywords": [ 358 | "WebSockets", 359 | "rfc6455", 360 | "websocket" 361 | ], 362 | "support": { 363 | "chat": "https://gitter.im/reactphp/reactphp", 364 | "issues": "https://github.com/ratchetphp/RFC6455/issues", 365 | "source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1" 366 | }, 367 | "time": "2021-12-09T23:20:49+00:00" 368 | }, 369 | { 370 | "name": "react/cache", 371 | "version": "v1.2.0", 372 | "source": { 373 | "type": "git", 374 | "url": "https://github.com/reactphp/cache.git", 375 | "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" 376 | }, 377 | "dist": { 378 | "type": "zip", 379 | "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", 380 | "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", 381 | "shasum": "" 382 | }, 383 | "require": { 384 | "php": ">=5.3.0", 385 | "react/promise": "^3.0 || ^2.0 || ^1.1" 386 | }, 387 | "require-dev": { 388 | "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" 389 | }, 390 | "type": "library", 391 | "autoload": { 392 | "psr-4": { 393 | "React\\Cache\\": "src/" 394 | } 395 | }, 396 | "notification-url": "https://packagist.org/downloads/", 397 | "license": [ 398 | "MIT" 399 | ], 400 | "authors": [ 401 | { 402 | "name": "Christian Lück", 403 | "email": "christian@clue.engineering", 404 | "homepage": "https://clue.engineering/" 405 | }, 406 | { 407 | "name": "Cees-Jan Kiewiet", 408 | "email": "reactphp@ceesjankiewiet.nl", 409 | "homepage": "https://wyrihaximus.net/" 410 | }, 411 | { 412 | "name": "Jan Sorgalla", 413 | "email": "jsorgalla@gmail.com", 414 | "homepage": "https://sorgalla.com/" 415 | }, 416 | { 417 | "name": "Chris Boden", 418 | "email": "cboden@gmail.com", 419 | "homepage": "https://cboden.dev/" 420 | } 421 | ], 422 | "description": "Async, Promise-based cache interface for ReactPHP", 423 | "keywords": [ 424 | "cache", 425 | "caching", 426 | "promise", 427 | "reactphp" 428 | ], 429 | "support": { 430 | "issues": "https://github.com/reactphp/cache/issues", 431 | "source": "https://github.com/reactphp/cache/tree/v1.2.0" 432 | }, 433 | "funding": [ 434 | { 435 | "url": "https://opencollective.com/reactphp", 436 | "type": "open_collective" 437 | } 438 | ], 439 | "time": "2022-11-30T15:59:55+00:00" 440 | }, 441 | { 442 | "name": "react/dns", 443 | "version": "v1.10.0", 444 | "source": { 445 | "type": "git", 446 | "url": "https://github.com/reactphp/dns.git", 447 | "reference": "a5427e7dfa47713e438016905605819d101f238c" 448 | }, 449 | "dist": { 450 | "type": "zip", 451 | "url": "https://api.github.com/repos/reactphp/dns/zipball/a5427e7dfa47713e438016905605819d101f238c", 452 | "reference": "a5427e7dfa47713e438016905605819d101f238c", 453 | "shasum": "" 454 | }, 455 | "require": { 456 | "php": ">=5.3.0", 457 | "react/cache": "^1.0 || ^0.6 || ^0.5", 458 | "react/event-loop": "^1.2", 459 | "react/promise": "^3.0 || ^2.7 || ^1.2.1", 460 | "react/promise-timer": "^1.9" 461 | }, 462 | "require-dev": { 463 | "phpunit/phpunit": "^9.3 || ^4.8.35", 464 | "react/async": "^4 || ^3 || ^2" 465 | }, 466 | "type": "library", 467 | "autoload": { 468 | "psr-4": { 469 | "React\\Dns\\": "src" 470 | } 471 | }, 472 | "notification-url": "https://packagist.org/downloads/", 473 | "license": [ 474 | "MIT" 475 | ], 476 | "authors": [ 477 | { 478 | "name": "Christian Lück", 479 | "email": "christian@clue.engineering", 480 | "homepage": "https://clue.engineering/" 481 | }, 482 | { 483 | "name": "Cees-Jan Kiewiet", 484 | "email": "reactphp@ceesjankiewiet.nl", 485 | "homepage": "https://wyrihaximus.net/" 486 | }, 487 | { 488 | "name": "Jan Sorgalla", 489 | "email": "jsorgalla@gmail.com", 490 | "homepage": "https://sorgalla.com/" 491 | }, 492 | { 493 | "name": "Chris Boden", 494 | "email": "cboden@gmail.com", 495 | "homepage": "https://cboden.dev/" 496 | } 497 | ], 498 | "description": "Async DNS resolver for ReactPHP", 499 | "keywords": [ 500 | "async", 501 | "dns", 502 | "dns-resolver", 503 | "reactphp" 504 | ], 505 | "support": { 506 | "issues": "https://github.com/reactphp/dns/issues", 507 | "source": "https://github.com/reactphp/dns/tree/v1.10.0" 508 | }, 509 | "funding": [ 510 | { 511 | "url": "https://github.com/WyriHaximus", 512 | "type": "github" 513 | }, 514 | { 515 | "url": "https://github.com/clue", 516 | "type": "github" 517 | } 518 | ], 519 | "time": "2022-09-08T12:22:46+00:00" 520 | }, 521 | { 522 | "name": "react/event-loop", 523 | "version": "v1.3.0", 524 | "source": { 525 | "type": "git", 526 | "url": "https://github.com/reactphp/event-loop.git", 527 | "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137" 528 | }, 529 | "dist": { 530 | "type": "zip", 531 | "url": "https://api.github.com/repos/reactphp/event-loop/zipball/187fb56f46d424afb6ec4ad089269c72eec2e137", 532 | "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137", 533 | "shasum": "" 534 | }, 535 | "require": { 536 | "php": ">=5.3.0" 537 | }, 538 | "require-dev": { 539 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" 540 | }, 541 | "suggest": { 542 | "ext-event": "~1.0 for ExtEventLoop", 543 | "ext-pcntl": "For signal handling support when using the StreamSelectLoop", 544 | "ext-uv": "* for ExtUvLoop" 545 | }, 546 | "type": "library", 547 | "autoload": { 548 | "psr-4": { 549 | "React\\EventLoop\\": "src" 550 | } 551 | }, 552 | "notification-url": "https://packagist.org/downloads/", 553 | "license": [ 554 | "MIT" 555 | ], 556 | "authors": [ 557 | { 558 | "name": "Christian Lück", 559 | "email": "christian@clue.engineering", 560 | "homepage": "https://clue.engineering/" 561 | }, 562 | { 563 | "name": "Cees-Jan Kiewiet", 564 | "email": "reactphp@ceesjankiewiet.nl", 565 | "homepage": "https://wyrihaximus.net/" 566 | }, 567 | { 568 | "name": "Jan Sorgalla", 569 | "email": "jsorgalla@gmail.com", 570 | "homepage": "https://sorgalla.com/" 571 | }, 572 | { 573 | "name": "Chris Boden", 574 | "email": "cboden@gmail.com", 575 | "homepage": "https://cboden.dev/" 576 | } 577 | ], 578 | "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", 579 | "keywords": [ 580 | "asynchronous", 581 | "event-loop" 582 | ], 583 | "support": { 584 | "issues": "https://github.com/reactphp/event-loop/issues", 585 | "source": "https://github.com/reactphp/event-loop/tree/v1.3.0" 586 | }, 587 | "funding": [ 588 | { 589 | "url": "https://github.com/WyriHaximus", 590 | "type": "github" 591 | }, 592 | { 593 | "url": "https://github.com/clue", 594 | "type": "github" 595 | } 596 | ], 597 | "time": "2022-03-17T11:10:22+00:00" 598 | }, 599 | { 600 | "name": "react/promise", 601 | "version": "v2.9.0", 602 | "source": { 603 | "type": "git", 604 | "url": "https://github.com/reactphp/promise.git", 605 | "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910" 606 | }, 607 | "dist": { 608 | "type": "zip", 609 | "url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910", 610 | "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910", 611 | "shasum": "" 612 | }, 613 | "require": { 614 | "php": ">=5.4.0" 615 | }, 616 | "require-dev": { 617 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" 618 | }, 619 | "type": "library", 620 | "autoload": { 621 | "files": [ 622 | "src/functions_include.php" 623 | ], 624 | "psr-4": { 625 | "React\\Promise\\": "src/" 626 | } 627 | }, 628 | "notification-url": "https://packagist.org/downloads/", 629 | "license": [ 630 | "MIT" 631 | ], 632 | "authors": [ 633 | { 634 | "name": "Jan Sorgalla", 635 | "email": "jsorgalla@gmail.com", 636 | "homepage": "https://sorgalla.com/" 637 | }, 638 | { 639 | "name": "Christian Lück", 640 | "email": "christian@clue.engineering", 641 | "homepage": "https://clue.engineering/" 642 | }, 643 | { 644 | "name": "Cees-Jan Kiewiet", 645 | "email": "reactphp@ceesjankiewiet.nl", 646 | "homepage": "https://wyrihaximus.net/" 647 | }, 648 | { 649 | "name": "Chris Boden", 650 | "email": "cboden@gmail.com", 651 | "homepage": "https://cboden.dev/" 652 | } 653 | ], 654 | "description": "A lightweight implementation of CommonJS Promises/A for PHP", 655 | "keywords": [ 656 | "promise", 657 | "promises" 658 | ], 659 | "support": { 660 | "issues": "https://github.com/reactphp/promise/issues", 661 | "source": "https://github.com/reactphp/promise/tree/v2.9.0" 662 | }, 663 | "funding": [ 664 | { 665 | "url": "https://github.com/WyriHaximus", 666 | "type": "github" 667 | }, 668 | { 669 | "url": "https://github.com/clue", 670 | "type": "github" 671 | } 672 | ], 673 | "time": "2022-02-11T10:27:51+00:00" 674 | }, 675 | { 676 | "name": "react/promise-timer", 677 | "version": "v1.9.0", 678 | "source": { 679 | "type": "git", 680 | "url": "https://github.com/reactphp/promise-timer.git", 681 | "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495" 682 | }, 683 | "dist": { 684 | "type": "zip", 685 | "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", 686 | "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", 687 | "shasum": "" 688 | }, 689 | "require": { 690 | "php": ">=5.3", 691 | "react/event-loop": "^1.2", 692 | "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" 693 | }, 694 | "require-dev": { 695 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" 696 | }, 697 | "type": "library", 698 | "autoload": { 699 | "files": [ 700 | "src/functions_include.php" 701 | ], 702 | "psr-4": { 703 | "React\\Promise\\Timer\\": "src/" 704 | } 705 | }, 706 | "notification-url": "https://packagist.org/downloads/", 707 | "license": [ 708 | "MIT" 709 | ], 710 | "authors": [ 711 | { 712 | "name": "Christian Lück", 713 | "email": "christian@clue.engineering", 714 | "homepage": "https://clue.engineering/" 715 | }, 716 | { 717 | "name": "Cees-Jan Kiewiet", 718 | "email": "reactphp@ceesjankiewiet.nl", 719 | "homepage": "https://wyrihaximus.net/" 720 | }, 721 | { 722 | "name": "Jan Sorgalla", 723 | "email": "jsorgalla@gmail.com", 724 | "homepage": "https://sorgalla.com/" 725 | }, 726 | { 727 | "name": "Chris Boden", 728 | "email": "cboden@gmail.com", 729 | "homepage": "https://cboden.dev/" 730 | } 731 | ], 732 | "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", 733 | "homepage": "https://github.com/reactphp/promise-timer", 734 | "keywords": [ 735 | "async", 736 | "event-loop", 737 | "promise", 738 | "reactphp", 739 | "timeout", 740 | "timer" 741 | ], 742 | "support": { 743 | "issues": "https://github.com/reactphp/promise-timer/issues", 744 | "source": "https://github.com/reactphp/promise-timer/tree/v1.9.0" 745 | }, 746 | "funding": [ 747 | { 748 | "url": "https://github.com/WyriHaximus", 749 | "type": "github" 750 | }, 751 | { 752 | "url": "https://github.com/clue", 753 | "type": "github" 754 | } 755 | ], 756 | "time": "2022-06-13T13:41:03+00:00" 757 | }, 758 | { 759 | "name": "react/socket", 760 | "version": "v1.12.0", 761 | "source": { 762 | "type": "git", 763 | "url": "https://github.com/reactphp/socket.git", 764 | "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b" 765 | }, 766 | "dist": { 767 | "type": "zip", 768 | "url": "https://api.github.com/repos/reactphp/socket/zipball/81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", 769 | "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", 770 | "shasum": "" 771 | }, 772 | "require": { 773 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 774 | "php": ">=5.3.0", 775 | "react/dns": "^1.8", 776 | "react/event-loop": "^1.2", 777 | "react/promise": "^3 || ^2.6 || ^1.2.1", 778 | "react/promise-timer": "^1.9", 779 | "react/stream": "^1.2" 780 | }, 781 | "require-dev": { 782 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", 783 | "react/async": "^4 || ^3 || ^2", 784 | "react/promise-stream": "^1.4" 785 | }, 786 | "type": "library", 787 | "autoload": { 788 | "psr-4": { 789 | "React\\Socket\\": "src" 790 | } 791 | }, 792 | "notification-url": "https://packagist.org/downloads/", 793 | "license": [ 794 | "MIT" 795 | ], 796 | "authors": [ 797 | { 798 | "name": "Christian Lück", 799 | "email": "christian@clue.engineering", 800 | "homepage": "https://clue.engineering/" 801 | }, 802 | { 803 | "name": "Cees-Jan Kiewiet", 804 | "email": "reactphp@ceesjankiewiet.nl", 805 | "homepage": "https://wyrihaximus.net/" 806 | }, 807 | { 808 | "name": "Jan Sorgalla", 809 | "email": "jsorgalla@gmail.com", 810 | "homepage": "https://sorgalla.com/" 811 | }, 812 | { 813 | "name": "Chris Boden", 814 | "email": "cboden@gmail.com", 815 | "homepage": "https://cboden.dev/" 816 | } 817 | ], 818 | "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", 819 | "keywords": [ 820 | "Connection", 821 | "Socket", 822 | "async", 823 | "reactphp", 824 | "stream" 825 | ], 826 | "support": { 827 | "issues": "https://github.com/reactphp/socket/issues", 828 | "source": "https://github.com/reactphp/socket/tree/v1.12.0" 829 | }, 830 | "funding": [ 831 | { 832 | "url": "https://github.com/WyriHaximus", 833 | "type": "github" 834 | }, 835 | { 836 | "url": "https://github.com/clue", 837 | "type": "github" 838 | } 839 | ], 840 | "time": "2022-08-25T12:32:25+00:00" 841 | }, 842 | { 843 | "name": "react/stream", 844 | "version": "v1.2.0", 845 | "source": { 846 | "type": "git", 847 | "url": "https://github.com/reactphp/stream.git", 848 | "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9" 849 | }, 850 | "dist": { 851 | "type": "zip", 852 | "url": "https://api.github.com/repos/reactphp/stream/zipball/7a423506ee1903e89f1e08ec5f0ed430ff784ae9", 853 | "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9", 854 | "shasum": "" 855 | }, 856 | "require": { 857 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 858 | "php": ">=5.3.8", 859 | "react/event-loop": "^1.2" 860 | }, 861 | "require-dev": { 862 | "clue/stream-filter": "~1.2", 863 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" 864 | }, 865 | "type": "library", 866 | "autoload": { 867 | "psr-4": { 868 | "React\\Stream\\": "src" 869 | } 870 | }, 871 | "notification-url": "https://packagist.org/downloads/", 872 | "license": [ 873 | "MIT" 874 | ], 875 | "authors": [ 876 | { 877 | "name": "Christian Lück", 878 | "email": "christian@clue.engineering", 879 | "homepage": "https://clue.engineering/" 880 | }, 881 | { 882 | "name": "Cees-Jan Kiewiet", 883 | "email": "reactphp@ceesjankiewiet.nl", 884 | "homepage": "https://wyrihaximus.net/" 885 | }, 886 | { 887 | "name": "Jan Sorgalla", 888 | "email": "jsorgalla@gmail.com", 889 | "homepage": "https://sorgalla.com/" 890 | }, 891 | { 892 | "name": "Chris Boden", 893 | "email": "cboden@gmail.com", 894 | "homepage": "https://cboden.dev/" 895 | } 896 | ], 897 | "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", 898 | "keywords": [ 899 | "event-driven", 900 | "io", 901 | "non-blocking", 902 | "pipe", 903 | "reactphp", 904 | "readable", 905 | "stream", 906 | "writable" 907 | ], 908 | "support": { 909 | "issues": "https://github.com/reactphp/stream/issues", 910 | "source": "https://github.com/reactphp/stream/tree/v1.2.0" 911 | }, 912 | "funding": [ 913 | { 914 | "url": "https://github.com/WyriHaximus", 915 | "type": "github" 916 | }, 917 | { 918 | "url": "https://github.com/clue", 919 | "type": "github" 920 | } 921 | ], 922 | "time": "2021-07-11T12:37:55+00:00" 923 | } 924 | ], 925 | "packages-dev": [], 926 | "aliases": [], 927 | "minimum-stability": "stable", 928 | "stability-flags": [], 929 | "prefer-stable": false, 930 | "prefer-lowest": false, 931 | "platform": { 932 | "php": ">=7.1", 933 | "ext-curl": "*" 934 | }, 935 | "platform-dev": [], 936 | "plugin-api-version": "2.2.0" 937 | } 938 | --------------------------------------------------------------------------------