Add Google's Bard Chat #4
7 changed files with 177 additions and 194 deletions
|
@ -7,6 +7,7 @@ ROOM_ID="!FYCmBSkCRUXXXXXXXXX:matrix.XXX.XXX" # Optional, if not set, bot will w
|
|||
OPENAI_API_KEY="xxxxxxxxxxxxxxxxx" # Optional, for !chat and !gpt command
|
||||
BING_API_ENDPOINT="xxxxxxxxxxxxxxx" # Optional, for !bing command
|
||||
ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxxxx" # Optional, use user_id and password is recommended
|
||||
BARD_TOKEN="xxxxxxxxxxxxxxxxxxxx", # Optional, for !bard command
|
||||
JAILBREAKENABLED="true" # Optional
|
||||
BING_AUTH_COOKIE="xxxxxxxxxxxxxxxxxxx" # _U cookie, Optional, for Bing Image Creator
|
||||
MARKDOWN_FORMATTED="true" # Optional
|
161
BingImageGen.py
161
BingImageGen.py
|
@ -3,15 +3,12 @@ Code derived from:
|
|||
https://github.com/acheong08/EdgeGPT/blob/f940cecd24a4818015a8b42a2443dd97c3c2a8f4/src/ImageGen.py
|
||||
"""
|
||||
from log import getlogger
|
||||
|
||||
from typing import Union
|
||||
from uuid import uuid4
|
||||
import os
|
||||
import contextlib
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
import requests
|
||||
import regex
|
||||
|
||||
|
@ -33,164 +30,6 @@ HEADERS = {
|
|||
"x-forwarded-for": FORWARDED_IP,
|
||||
}
|
||||
|
||||
# Error messages
|
||||
error_timeout = "Your request has timed out."
|
||||
error_redirect = "Redirect failed"
|
||||
error_blocked_prompt = (
|
||||
"Your prompt has been blocked by Bing. Try to change any bad words and try again."
|
||||
)
|
||||
error_noresults = "Could not get results"
|
||||
error_unsupported_lang = "\nthis language is currently not supported by bing"
|
||||
error_bad_images = "Bad images"
|
||||
error_no_images = "No images"
|
||||
#
|
||||
sending_message = "Sending request..."
|
||||
wait_message = "Waiting for results..."
|
||||
download_message = "\nDownloading images..."
|
||||
|
||||
|
||||
def debug(debug_file, text_var):
|
||||
"""helper function for debug"""
|
||||
with open(f"{debug_file}", "a") as f:
|
||||
f.write(str(text_var))
|
||||
|
||||
|
||||
class ImageGen:
|
||||
"""
|
||||
Image generation by Microsoft Bing
|
||||
Parameters:3
|
||||
auth_cookie: str
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, auth_cookie: str, debug_file: Union[str, None] = None, quiet: bool = False
|
||||
) -> None:
|
||||
self.session: requests.Session = requests.Session()
|
||||
self.session.headers = HEADERS
|
||||
self.session.cookies.set("_U", auth_cookie)
|
||||
self.quiet = quiet
|
||||
self.debug_file = debug_file
|
||||
if self.debug_file:
|
||||
self.debug = partial(debug, self.debug_file)
|
||||
|
||||
def get_images(self, prompt: str) -> list:
|
||||
"""
|
||||
Fetches image links from Bing
|
||||
Parameters:
|
||||
prompt: str
|
||||
"""
|
||||
if not self.quiet:
|
||||
print(sending_message)
|
||||
if self.debug_file:
|
||||
self.debug(sending_message)
|
||||
url_encoded_prompt = requests.utils.quote(prompt)
|
||||
# https://www.bing.com/images/create?q=<PROMPT>&rt=3&FORM=GENCRE
|
||||
url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=4&FORM=GENCRE"
|
||||
response = self.session.post(url, allow_redirects=False)
|
||||
# check for content waring message
|
||||
if "this prompt has been blocked" in response.text.lower():
|
||||
if self.debug_file:
|
||||
self.debug(f"ERROR: {error_blocked_prompt}")
|
||||
raise Exception(
|
||||
error_blocked_prompt,
|
||||
)
|
||||
if (
|
||||
"we're working hard to offer image creator in more languages"
|
||||
in response.text.lower()
|
||||
):
|
||||
if self.debug_file:
|
||||
self.debug(f"ERROR: {error_unsupported_lang}")
|
||||
raise Exception(error_unsupported_lang)
|
||||
if response.status_code != 302:
|
||||
# if rt4 fails, try rt3
|
||||
url = f"{BING_URL}/images/create?q={url_encoded_prompt}&rt=3&FORM=GENCRE"
|
||||
response3 = self.session.post(
|
||||
url, allow_redirects=False, timeout=200)
|
||||
if response3.status_code != 302:
|
||||
if self.debug_file:
|
||||
self.debug(f"ERROR: {error_redirect}")
|
||||
print(f"ERROR: {response3.text}")
|
||||
raise Exception(error_redirect)
|
||||
response = response3
|
||||
# Get redirect URL
|
||||
redirect_url = response.headers["Location"].replace("&nfy=1", "")
|
||||
request_id = redirect_url.split("id=")[-1]
|
||||
self.session.get(f"{BING_URL}{redirect_url}")
|
||||
# https://www.bing.com/images/create/async/results/{ID}?q={PROMPT}
|
||||
polling_url = f"{BING_URL}/images/create/async/results/{request_id}?q={url_encoded_prompt}"
|
||||
# Poll for results
|
||||
if self.debug_file:
|
||||
self.debug("Polling and waiting for result")
|
||||
if not self.quiet:
|
||||
print("Waiting for results...")
|
||||
start_wait = time.time()
|
||||
while True:
|
||||
if int(time.time() - start_wait) > 200:
|
||||
if self.debug_file:
|
||||
self.debug(f"ERROR: {error_timeout}")
|
||||
raise Exception(error_timeout)
|
||||
if not self.quiet:
|
||||
print(".", end="", flush=True)
|
||||
response = self.session.get(polling_url)
|
||||
if response.status_code != 200:
|
||||
if self.debug_file:
|
||||
self.debug(f"ERROR: {error_noresults}")
|
||||
raise Exception(error_noresults)
|
||||
if not response.text or response.text.find("errorMessage") != -1:
|
||||
time.sleep(1)
|
||||
continue
|
||||
else:
|
||||
break
|
||||
# Use regex to search for src=""
|
||||
image_links = regex.findall(r'src="([^"]+)"', response.text)
|
||||
# Remove size limit
|
||||
normal_image_links = [link.split("?w=")[0] for link in image_links]
|
||||
# Remove duplicates
|
||||
normal_image_links = list(set(normal_image_links))
|
||||
|
||||
# bad_images = [
|
||||
# "https://r.bing.com/rp/in-2zU3AJUdkgFe7ZKv19yPBHVs.png",
|
||||
# "https://r.bing.com/rp/TX9QuO3WzcCJz1uaaSwQAz39Kb0.jpg",
|
||||
# ]
|
||||
# for img in normal_image_links:
|
||||
# if img in bad_images:
|
||||
# raise Exception("Bad images")
|
||||
# No images
|
||||
if not normal_image_links:
|
||||
raise Exception(error_no_images)
|
||||
return normal_image_links
|
||||
|
||||
def save_images(self, links: list, output_dir: str) -> str:
|
||||
"""
|
||||
Saves images to output directory
|
||||
"""
|
||||
|
||||
# image name
|
||||
image_name = str(uuid4())
|
||||
# since matrix only support one media attachment per message, we just need one link
|
||||
if links:
|
||||
link = links.pop()
|
||||
|
||||
image_path = os.path.join(output_dir, f"{image_name}.jpeg")
|
||||
|
||||
with contextlib.suppress(FileExistsError):
|
||||
os.mkdir(output_dir)
|
||||
try:
|
||||
with self.session.get(link, stream=True) as response:
|
||||
# save response to file
|
||||
response.raise_for_status()
|
||||
with open(
|
||||
os.path.join(output_dir, image_path), "wb"
|
||||
) as output_file:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
output_file.write(chunk)
|
||||
return image_path
|
||||
except requests.exceptions.MissingSchema as url_exception:
|
||||
raise Exception(
|
||||
"Inappropriate contents found in the generated images. Please try again or try another prompt.",
|
||||
) from url_exception
|
||||
|
||||
|
||||
class ImageGenAsync:
|
||||
"""
|
||||
Image generation by Microsoft Bing
|
||||
|
|
103
bard.py
Normal file
103
bard.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
"""
|
||||
Code derived from: https://github.com/acheong08/Bard/blob/main/src/Bard.py
|
||||
"""
|
||||
|
||||
import random
|
||||
import string
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
|
||||
class Bardbot:
|
||||
"""
|
||||
A class to interact with Google Bard.
|
||||
Parameters
|
||||
session_id: str
|
||||
The __Secure-1PSID cookie.
|
||||
"""
|
||||
|
||||
__slots__ = [
|
||||
"headers",
|
||||
"_reqid",
|
||||
"SNlM0e",
|
||||
"conversation_id",
|
||||
"response_id",
|
||||
"choice_id",
|
||||
"session",
|
||||
]
|
||||
|
||||
def __init__(self, session_id):
|
||||
headers = {
|
||||
"Host": "bard.google.com",
|
||||
"X-Same-Domain": "1",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
"Origin": "https://bard.google.com",
|
||||
"Referer": "https://bard.google.com/",
|
||||
}
|
||||
self._reqid = int("".join(random.choices(string.digits, k=4)))
|
||||
self.conversation_id = ""
|
||||
self.response_id = ""
|
||||
self.choice_id = ""
|
||||
self.session = requests.Session()
|
||||
self.session.headers = headers
|
||||
self.session.cookies.set("__Secure-1PSID", session_id)
|
||||
self.SNlM0e = self.__get_snlm0e()
|
||||
|
||||
def __get_snlm0e(self):
|
||||
resp = self.session.get(url="https://bard.google.com/", timeout=10)
|
||||
# Find "SNlM0e":"<ID>"
|
||||
if resp.status_code != 200:
|
||||
raise Exception("Could not get Google Bard")
|
||||
SNlM0e = re.search(r"SNlM0e\":\"(.*?)\"", resp.text).group(1)
|
||||
return SNlM0e
|
||||
|
||||
def ask(self, message: str) -> dict:
|
||||
"""
|
||||
Send a message to Google Bard and return the response.
|
||||
:param message: The message to send to Google Bard.
|
||||
:return: A dict containing the response from Google Bard.
|
||||
"""
|
||||
# url params
|
||||
params = {
|
||||
"bl": "boq_assistant-bard-web-server_20230326.21_p0",
|
||||
"_reqid": str(self._reqid),
|
||||
"rt": "c",
|
||||
}
|
||||
|
||||
# message arr -> data["f.req"]. Message is double json stringified
|
||||
message_struct = [
|
||||
[message],
|
||||
None,
|
||||
[self.conversation_id, self.response_id, self.choice_id],
|
||||
]
|
||||
data = {
|
||||
"f.req": json.dumps([None, json.dumps(message_struct)]),
|
||||
"at": self.SNlM0e,
|
||||
}
|
||||
|
||||
# do the request!
|
||||
resp = self.session.post(
|
||||
"https://bard.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
|
||||
params=params,
|
||||
data=data,
|
||||
timeout=120,
|
||||
)
|
||||
|
||||
chat_data = json.loads(resp.content.splitlines()[3])[0][2]
|
||||
if not chat_data:
|
||||
return {"content": f"Google Bard encountered an error: {resp.content}."}
|
||||
json_chat_data = json.loads(chat_data)
|
||||
results = {
|
||||
"content": json_chat_data[0][0],
|
||||
"conversation_id": json_chat_data[1][0],
|
||||
"response_id": json_chat_data[1][1],
|
||||
"factualityQueries": json_chat_data[3],
|
||||
"textQuery": json_chat_data[2][0] if json_chat_data[2] is not None else "",
|
||||
"choices": [{"id": i[0], "content": i[1]} for i in json_chat_data[4]],
|
||||
}
|
||||
self.conversation_id = results["conversation_id"]
|
||||
self.response_id = results["response_id"]
|
||||
self.choice_id = results["choices"][0]["id"]
|
||||
self._reqid += 100000
|
||||
return results
|
94
bot.py
94
bot.py
|
@ -1,36 +1,26 @@
|
|||
import sys
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import re
|
||||
import os
|
||||
from functools import partial
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Optional, Union
|
||||
from nio import (
|
||||
AsyncClient,
|
||||
MatrixRoom,
|
||||
RoomMessageText,
|
||||
InviteMemberEvent,
|
||||
MegolmEvent,
|
||||
LoginResponse,
|
||||
JoinError,
|
||||
ToDeviceError,
|
||||
LocalProtocolError,
|
||||
KeyVerificationEvent,
|
||||
KeyVerificationStart,
|
||||
KeyVerificationCancel,
|
||||
KeyVerificationKey,
|
||||
KeyVerificationMac,
|
||||
AsyncClientConfig
|
||||
)
|
||||
|
||||
import aiohttp
|
||||
from nio import (AsyncClient, AsyncClientConfig, InviteMemberEvent, JoinError,
|
||||
KeyVerificationCancel, KeyVerificationEvent,
|
||||
KeyVerificationKey, KeyVerificationMac, KeyVerificationStart,
|
||||
LocalProtocolError, LoginResponse, MatrixRoom, MegolmEvent,
|
||||
RoomMessageText, ToDeviceError)
|
||||
from nio.store.database import SqliteStore
|
||||
|
||||
from askgpt import askGPT
|
||||
from send_message import send_room_message
|
||||
from v3 import Chatbot
|
||||
from log import getlogger
|
||||
from bing import BingBot
|
||||
from BingImageGen import ImageGenAsync
|
||||
from log import getlogger
|
||||
from send_image import send_room_image
|
||||
from send_message import send_room_message
|
||||
from v3 import Chatbot
|
||||
from bard import Bardbot
|
||||
|
||||
logger = getlogger()
|
||||
|
||||
|
@ -48,6 +38,7 @@ class Bot:
|
|||
bing_api_endpoint: Union[str, None] = None,
|
||||
password: Union[str, None] = None,
|
||||
access_token: Union[str, None] = None,
|
||||
bard_token: Union[str, None] = None,
|
||||
jailbreakEnabled: Union[bool, None] = True,
|
||||
bing_auth_cookie: Union[str, None] = '',
|
||||
markdown_formatted: Union[bool, None] = False,
|
||||
|
@ -65,6 +56,7 @@ class Bot:
|
|||
self.user_id = user_id
|
||||
self.password = password
|
||||
self.access_token = access_token
|
||||
self.bard_token = bard_token
|
||||
self.device_id = device_id
|
||||
self.room_id = room_id
|
||||
self.api_key = api_key
|
||||
|
@ -115,10 +107,11 @@ class Bot:
|
|||
self.client.add_to_device_callback(
|
||||
self.to_device_callback, (KeyVerificationEvent, ))
|
||||
|
||||
# regular expression to match keyword [!gpt {prompt}] [!chat {prompt}]
|
||||
# regular expression to match keyword [!gpt {prompt}] [!chat {prompt}] [!bing {prompt}] [!pic {prompt}] [!bard {prompt}]
|
||||
self.gpt_prog = re.compile(r"^\s*!gpt\s*(.+)$")
|
||||
self.chat_prog = re.compile(r"^\s*!chat\s*(.+)$")
|
||||
self.bing_prog = re.compile(r"^\s*!bing\s*(.+)$")
|
||||
self.bard_prog = re.compile(r"^\s*!bard\s*(.+)$")
|
||||
self.pic_prog = re.compile(r"^\s*!pic\s*(.+)$")
|
||||
self.help_prog = re.compile(r"^\s*!help\s*.*$")
|
||||
|
||||
|
@ -150,6 +143,10 @@ class Bot:
|
|||
if self.bing_auth_cookie != '':
|
||||
self.imageGen = ImageGenAsync(self.bing_auth_cookie, quiet=True)
|
||||
|
||||
# initialize Bardbot
|
||||
if bard_token is not None:
|
||||
self.bardbot = Bardbot(self.bard_token)
|
||||
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
|
@ -257,6 +254,25 @@ class Bot:
|
|||
logger.error(e, exc_info=True)
|
||||
await send_room_message(self.client, room_id, reply_message=str(e))
|
||||
|
||||
# Google's Bard
|
||||
if self.bard_token is not None:
|
||||
b = self.bard_prog.match(content_body)
|
||||
if b:
|
||||
prompt = b.group(1)
|
||||
try:
|
||||
await self.bard(
|
||||
room_id,
|
||||
reply_to_event_id,
|
||||
prompt,
|
||||
sender_id,
|
||||
raw_user_message
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
await send_room_message(self.client, room_id, reply_message={e})
|
||||
|
||||
|
||||
|
||||
# help command
|
||||
h = self.help_prog.match(content_body)
|
||||
if h:
|
||||
|
@ -542,11 +558,11 @@ class Bot:
|
|||
logger.error(f"Error: {e}", exc_info=True)
|
||||
|
||||
# !bing command
|
||||
async def bing(self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message):
|
||||
async def bing(self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message) -> None:
|
||||
try:
|
||||
# sending typing state
|
||||
await self.client.room_typing(room_id, timeout=180000)
|
||||
# timeout 120s
|
||||
# timeout 240s
|
||||
text = await asyncio.wait_for(self.bingbot.ask_bing(prompt), timeout=240)
|
||||
except TimeoutError:
|
||||
logger.error("timeoutException", exc_info=True)
|
||||
|
@ -560,7 +576,24 @@ class Bot:
|
|||
await send_room_message(self.client, room_id, reply_message=text,
|
||||
reply_to_event_id="", sender_id=sender_id, user_message=raw_user_message, markdown_formatted=self.markdown_formatted)
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}", exc_info=True)
|
||||
logger.error(e, exc_info=True)
|
||||
|
||||
# !bard command
|
||||
async def bard(self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message) -> None:
|
||||
try:
|
||||
# sending typing state
|
||||
await self.client.room_typing(room_id)
|
||||
response = await asyncio.to_thread(self.bardbot.ask, prompt)
|
||||
except Exception as e:
|
||||
raise Exception(e)
|
||||
|
||||
try:
|
||||
content = str(response['content']).strip()
|
||||
await send_room_message(self.client, room_id, reply_message=content,
|
||||
reply_to_event_id="", sender_id=sender_id, user_message=raw_user_message, markdown_formatted=self.markdown_formatted)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
|
||||
|
||||
# !pic command
|
||||
async def pic(self, room_id, prompt):
|
||||
|
@ -583,7 +616,7 @@ class Bot:
|
|||
logger.error(e, exc_info=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}", exc_info=True)
|
||||
logger.error(e, exc_info=True)
|
||||
|
||||
# !help command
|
||||
async def help(self, room_id):
|
||||
|
@ -593,12 +626,13 @@ class Bot:
|
|||
help_info = "!gpt [content], generate response without context conversation\n" + \
|
||||
"!chat [content], chat with context conversation\n" + \
|
||||
"!bing [content], chat with context conversation powered by Bing AI\n" + \
|
||||
"!bard [content], chat with Google's Bard\n" + \
|
||||
"!pic [prompt], Image generation by Microsoft Bing\n" + \
|
||||
"!help, help message"
|
||||
|
||||
await send_room_message(self.client, room_id, reply_message=help_info)
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}", exc_info=True)
|
||||
logger.error(e, exc_info=True)
|
||||
|
||||
# bot login
|
||||
async def login(self) -> None:
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"bing_api_endpoint": "http://api:3000/conversation",
|
||||
"jailbreakEnabled": true,
|
||||
"access_token": "xxxxxxx",
|
||||
"bard_token": "xxxxxxx",
|
||||
"bing_auth_cookie": "xxxxxxxxxxx",
|
||||
"markdown_formatted": true
|
||||
}
|
||||
|
|
6
main.py
6
main.py
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
|
||||
from bot import Bot
|
||||
from log import getlogger
|
||||
|
||||
|
@ -22,6 +22,7 @@ async def main():
|
|||
api_key=config.get('api_key'),
|
||||
bing_api_endpoint=config.get('bing_api_endpoint'),
|
||||
access_token=config.get('access_token'),
|
||||
bard_token=config.get('bard_token'),
|
||||
jailbreakEnabled=config.get('jailbreakEnabled'),
|
||||
bing_auth_cookie=config.get('bing_auth_cookie'),
|
||||
markdown_formatted=config.get('markdown_formatted'),
|
||||
|
@ -36,6 +37,7 @@ async def main():
|
|||
api_key=os.environ.get("OPENAI_API_KEY"),
|
||||
bing_api_endpoint=os.environ.get("BING_API_ENDPOINT"),
|
||||
access_token=os.environ.get("ACCESS_TOKEN"),
|
||||
bard_token=os.environ.get("BARD_TOKEN"),
|
||||
jailbreakEnabled=os.environ.get(
|
||||
"JAILBREAKENABLED", "false").lower() in ('true', '1', 't'),
|
||||
bing_auth_cookie=os.environ.get("BING_AUTH_COOKIE"),
|
||||
|
|
5
v3.py
5
v3.py
|
@ -1,7 +1,10 @@
|
|||
"""
|
||||
Code derived from: https://github.com/acheong08/ChatGPT/blob/main/src/revChatGPT/V3.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import AsyncGenerator
|
||||
import asyncio
|
||||
import httpx
|
||||
import requests
|
||||
import tiktoken
|
||||
|
|
Loading…
Reference in a new issue