Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed BingChat to recent API changes. #41

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ riderModule.iml
.idea/
.DS_Store
build/
publish/
publish/
.vs/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file does not belong to the PR

Binary file not shown.
6 changes: 5 additions & 1 deletion examples/BingChat.Examples/Simple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ public static async Task AskSimply()
{
// Get the Bing "_U" cookie from wherever you like
var cookie = Environment.GetEnvironmentVariable("BING_COOKIE");


// Get the Bing cookie file path
var cookieFile = Environment.GetEnvironmentVariable("BING_COOKIES_FILE");

// Construct the chat client
var client = new BingChatClient(new BingChatClientOptions
{
CookieU = cookie,
CookieFilePath = cookieFile,
Tone = BingChatTone.Balanced,
});

Expand Down
4 changes: 4 additions & 0 deletions examples/BingChat.Examples/Streaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ public static async Task AskAndStreamResponseText()
// Get the Bing "_U" cookie from wherever you like
var cookie = Environment.GetEnvironmentVariable("BING_COOKIE");

// Get the Bing cookie file path
var cookieFile = Environment.GetEnvironmentVariable("BING_COOKIES_FILE");

// Construct the chat client
var client = new BingChatClient(new BingChatClientOptions
{
CookieU = cookie,
CookieFilePath = cookieFile,
Tone = BingChatTone.Creative,
});

Expand Down
4 changes: 4 additions & 0 deletions examples/BingChat.Examples/WithConversation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ public static async Task AskWithConversation()
// Get the Bing "_U" cookie from wherever you like
var cookie = Environment.GetEnvironmentVariable("BING_COOKIE");

// Get the Bing cookie file path
var cookieFile = Environment.GetEnvironmentVariable("BING_COOKIES_FILE");

// Construct the chat client
var client = new BingChatClient(new BingChatClientOptions
{
CookieU = cookie,
CookieFilePath = cookieFile,
Tone = BingChatTone.Balanced,
});

Expand Down
26 changes: 19 additions & 7 deletions src/BingChat/BingChatClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Net;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Web;

Expand Down Expand Up @@ -41,6 +42,7 @@ public async Task<BingChatConversation> CreateConversation()
{
JsonNode? cookieJson = JsonNode.Parse(File.ReadAllText(_options.CookieFilePath));
cookies = new CookieContainer();
cookies.PerDomainCapacity = 100;
foreach (var cookieItemJson in (JsonArray)cookieJson!)
{
if (cookieItemJson is null)
Expand All @@ -54,8 +56,14 @@ public async Task<BingChatConversation> CreateConversation()
string.IsNullOrEmpty(name) ||
string.IsNullOrEmpty(value))
continue;
cookies.Add(new Uri("https://www.bing.com"),
new Cookie(name, HttpUtility.UrlEncode(value), path, domain));
try
{
cookies.Add(new Cookie(name, value, path, domain));
}
catch
{
cookies.Add(new Cookie(name, HttpUtility.UrlEncode(value), path, domain));
}
}
}
catch
Expand Down Expand Up @@ -88,14 +96,14 @@ public async Task<BingChatConversation> CreateConversation()
headers.Add("sec-fetch-site", "same-origin");
headers.Add("x-edge-shopping-flag", "1");
headers.Add("x-ms-client-request-id", requestId.ToString().ToLower());
headers.Add("x-ms-useragent", "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/MacIntel");
headers.Add("x-ms-useragent", "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.12.0 OS/Windows");
headers.Add("referer", "https://www.bing.com/search");
headers.Add("referer-policy", "origin-when-cross-origin");

var response = await client.GetFromJsonAsync(
"https://www.bing.com/turing/conversation/create",
SerializerContext.Default.BingCreateConversationResponse);
var rawResponse = await client.GetAsync("https://www.bing.com/turing/conversation/create?bundleVersion=1.1135.1");

var response = (BingCreateConversationResponse?)await rawResponse.Content.ReadFromJsonAsync(typeof(BingCreateConversationResponse),SerializerContext.Default);

if (response!.Result is { } errResult &&
!errResult.Value.Equals("Success", StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -107,11 +115,15 @@ public async Task<BingChatConversation> CreateConversation()
throw new BingChatException(message);
}

var encryptedConversationSignature = rawResponse.Headers.GetValues("X-Sydney-EncryptedConversationSignature").FirstOrDefault();
Copy link
Collaborator

@neon-sunset neon-sunset Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this doesn't work without encryptedConversationSignature, shouldn't it be First() instead of FirstOrDefault()?


return new(
response.ClientId,
response.ConversationId,
response.ConversationSignature,
_options.Tone);
_options.Tone,
encryptedConversationSignature,
cookies);
}

/// <summary>
Expand Down
94 changes: 65 additions & 29 deletions src/BingChat/BingChatConversation.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Runtime.CompilerServices;
using System.Net;
using System.Net.WebSockets;
using System.Runtime.CompilerServices;
using System.Threading.Channels;
using System.Web;
using BingChat.Model;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections;
Expand All @@ -18,35 +21,23 @@ namespace BingChat;
/// </summary>
public sealed class BingChatConversation : IBingChattable
{
private static readonly HttpConnectionFactory ConnectionFactory = new(Options.Create(
new HttpConnectionOptions
{
DefaultTransferFormat = TransferFormat.Text,
SkipNegotiation = true,
Transports = HttpTransportType.WebSockets,
Headers =
{
["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.57"
}
}),
NullLoggerFactory.Instance);

private static readonly JsonHubProtocol HubProtocol = new(
Options.Create(new JsonHubProtocolOptions()
{
PayloadSerializerOptions = SerializerContext.Default.Options
}));

private static readonly UriEndPoint HubEndpoint = new(new Uri("wss://sydney.bing.com/sydney/ChatHub"));
private static readonly DefaultRetryPolicy DefaultRetryPolicy = new();

private readonly BingChatRequest _request;

private readonly string? _encryptedConversationSignature;
private readonly CookieContainer _cookies;
internal BingChatConversation(
string clientId, string conversationId, string conversationSignature, BingChatTone tone)
string clientId, string conversationId, string conversationSignature, BingChatTone tone, string? encryptedConversationSignature, CookieContainer cookies)
{
_request = new BingChatRequest(clientId, conversationId, conversationSignature, tone);
_encryptedConversationSignature = encryptedConversationSignature;
this._cookies = cookies;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this._cookies = cookies;
_cookies = cookies;

}

/// <inheritdoc/>
Expand Down Expand Up @@ -99,11 +90,18 @@ public async IAsyncEnumerable<string> StreamAsync(
// This is best done by extracting formatting logic from one-shot BuildAnswer used by AskAsync.
if (messageId != null)
{
var completedMessage = response.Result.Messages
.First(msg => msg.MessageId == messageId)
.Text;
if (completedMessage!.Length > completedLength)
tx.TryWrite(completedMessage[completedLength..]);
if (response.Result.Messages.Any(s => s.MessageType == "Disengaged"))
{
tx.TryWrite("Chat Bot Disengaged");
}
else
{
var completedMessage = response.Result.Messages
.First(msg => msg.MessageId == messageId)
.Text;
if (completedMessage!.Length > completedLength)
tx.TryWrite(completedMessage[completedLength..]);
}
}
tx.Complete();
}, ct);
Expand Down Expand Up @@ -169,16 +167,54 @@ public async IAsyncEnumerable<string> StreamAsync(
return messages.Count > 0 ? string.Join("\n\n", messages) : null;
}

private static async Task<HubConnection> Connect(CancellationToken ct)
private HttpConnectionFactory CreateConnectionFactory() => new(Options.Create(
new HttpConnectionOptions
{
DefaultTransferFormat = TransferFormat.Text,
SkipNegotiation = true,
Transports = HttpTransportType.WebSockets,
Cookies = _cookies,
Headers =
{
["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47",
["Origin"] = "https://www.bing.com"
}
}),
NullLoggerFactory.Instance);

private async Task<HubConnection> Connect(CancellationToken ct = default)
{
var uri = this._encryptedConversationSignature == null
? "wss://sydney.bing.com/sydney/ChatHub"
: $"wss://sydney.bing.com/sydney/ChatHub?sec_access_token={HttpUtility.UrlEncode(_encryptedConversationSignature)}";

var conn = new HubConnection(
ConnectionFactory,
CreateConnectionFactory(),
HubProtocol,
HubEndpoint,
new UriEndPoint(new Uri(uri)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to cache this property instead of creating it on each request.

new ServiceCollection().BuildServiceProvider(),
NullLoggerFactory.Instance);
NullLoggerFactory.Instance,
DefaultRetryPolicy);

await conn.StartAsync(ct);
//Retry on SSL connection error.
int retry = 0;
while (true)
{
try
{
await conn.StartAsync(ct);
break;
}
catch (WebSocketException)
{
Thread.Sleep(100);
Copy link
Collaborator

@neon-sunset neon-sunset Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You must never use Thread.Sleep(...) in async methods or methods that can be called from async. The correct way to do it is to use Task.Delay(...) instead.

if (retry < 5)
{
retry++;
}
else throw;
}
}

return conn;
}
Expand Down
17 changes: 17 additions & 0 deletions src/BingChat/DefaultRetryPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;

namespace BingChat
{
public class DefaultRetryPolicy:IRetryPolicy
{
public TimeSpan? NextRetryDelay(RetryContext retryContext)
{
return TimeSpan.FromSeconds(1);
}
}
}
3 changes: 3 additions & 0 deletions src/dotnet-bingchat/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ internal static class Utils
public static BingChatClient GetClient(BingChatTone tone)
{
var cookie = Environment.GetEnvironmentVariable("BING_COOKIE");
var cookieFile = Environment.GetEnvironmentVariable("BING_COOKIES_FILE");

return new BingChatClient(new BingChatClientOptions
{
CookieU = string.IsNullOrWhiteSpace(cookie) ? null : cookie,
CookieFilePath = string.IsNullOrEmpty(cookieFile)? null: cookieFile,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CookieFilePath = string.IsNullOrEmpty(cookieFile)? null: cookieFile,
CookieFilePath = string.IsNullOrEmpty(cookieFile) ? null : cookieFile,

Tone = tone,
});
}
Expand Down
8 changes: 6 additions & 2 deletions src/dotnet-bingchat/dotnet-bingchat.csproj
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert all changes in this file.

Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BingChat\BingChat.csproj"/>
<ProjectReference Include="..\BingChat\BingChat.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Spectre.Console.Cli" Version="0.46.1-preview.0.7"/>
<PackageReference Include="Spectre.Console.Cli" Version="0.46.1-preview.0.7" />
</ItemGroup>

<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>

</Project>