diff --git a/.env.example b/.env.example
index ec0415c..4d21d63 100644
--- a/.env.example
+++ b/.env.example
@@ -11,5 +11,6 @@ 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
+OUTPUT_FOUR_IMAGES="true" # Optional
IMPORT_KEYS_PATH="element-keys.txt" # Optional
IMPORT_KEYS_PASSWORD="xxxxxxx" # Optional
\ No newline at end of file
diff --git a/BingImageGen.py b/BingImageGen.py
index b3c3a4f..7fb32d6 100644
--- a/BingImageGen.py
+++ b/BingImageGen.py
@@ -2,6 +2,7 @@
Code derived from:
https://github.com/acheong08/EdgeGPT/blob/f940cecd24a4818015a8b42a2443dd97c3c2a8f4/src/ImageGen.py
"""
+
from log import getlogger
from uuid import uuid4
import os
@@ -20,16 +21,17 @@ FORWARDED_IP = (
f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
)
HEADERS = {
- "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",
+ "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", # noqa: E501
"accept-language": "en-US,en;q=0.9",
"cache-control": "max-age=0",
"content-type": "application/x-www-form-urlencoded",
"referrer": "https://www.bing.com/images/create/",
"origin": "https://www.bing.com",
- "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63",
+ "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63", # noqa: E501
"x-forwarded-for": FORWARDED_IP,
}
+
class ImageGenAsync:
"""
Image generation by Microsoft Bing
@@ -53,7 +55,7 @@ class ImageGenAsync:
def __del__(self):
try:
loop = asyncio.get_running_loop()
- except RuntimeError as e:
+ except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self._close())
@@ -76,7 +78,7 @@ class ImageGenAsync:
content = await response.text()
if "this prompt has been blocked" in content.lower():
raise Exception(
- "Your prompt has been blocked by Bing. Try to change any bad words and try again.",
+ "Your prompt has been blocked by Bing. Try to change any bad words and try again.", # noqa: E501
)
if response.status != 302:
# if rt4 fails, try rt3
@@ -97,7 +99,7 @@ class ImageGenAsync:
request_id = redirect_url.split("id=")[-1]
await 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}"
+ polling_url = f"{BING_URL}/images/create/async/results/{request_id}?q={url_encoded_prompt}" # noqa: E501
# Poll for results
if not self.quiet:
print("Waiting for results...")
@@ -134,31 +136,40 @@ class ImageGenAsync:
raise Exception("No images")
return normal_image_links
- async def save_images(self, links: list, output_dir: str) -> str:
+ async def save_images(self, links: list, output_dir: str,
+ output_four_images: bool) -> list:
"""
Saves images to output directory
"""
- if not self.quiet:
- print("\nDownloading images...")
with contextlib.suppress(FileExistsError):
os.mkdir(output_dir)
- # 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_list = []
- image_path = os.path.join(output_dir, f"{image_name}.jpeg")
- try:
- async with self.session.get(link, raise_for_status=True) as response:
- # save response to file
- with open(image_path, "wb") as output_file:
- async for chunk in response.content.iter_chunked(8192):
- output_file.write(chunk)
- return f"{output_dir}/{image_name}.jpeg"
+ if output_four_images:
+ for link in links:
+ image_name = str(uuid4())
+ image_path = os.path.join(output_dir, f"{image_name}.jpeg")
+ try:
+ async with self.session.get(link, raise_for_status=True) as response:
+ with open(image_path, "wb") as output_file:
+ async for chunk in response.content.iter_chunked(8192):
+ output_file.write(chunk)
+ image_path_list.append(image_path)
+ except aiohttp.client_exceptions.InvalidURL as url_exception:
+ raise Exception("Inappropriate contents found in the generated images. Please try again or try another prompt.") from url_exception # noqa: E501
+ else:
+ image_name = str(uuid4())
+ if links:
+ link = links.pop()
+ try:
+ async with self.session.get(link, raise_for_status=True) as response:
+ image_path = os.path.join(output_dir, f"{image_name}.jpeg")
+ with open(image_path, "wb") as output_file:
+ async for chunk in response.content.iter_chunked(8192):
+ output_file.write(chunk)
+ image_path_list.append(image_path)
+ except aiohttp.client_exceptions.InvalidURL as url_exception:
+ raise Exception("Inappropriate contents found in the generated images. Please try again or try another prompt.") from url_exception # noqa: E501
- except aiohttp.client_exceptions.InvalidURL as url_exception:
- raise Exception(
- "Inappropriate contents found in the generated images. Please try again or try another prompt.",
- ) from url_exception
+ return image_path_list
diff --git a/README.md b/README.md
index 8305d5e..561ac43 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ sudo docker compose up -d
Normal Method:
+system dependece: `libolm-dev`
1. Clone the repository and create virtual environment:
@@ -74,6 +75,7 @@ python main.py
## Usage
To interact with the bot, simply send a message to the bot in the Matrix room with one of the two prompts:
+- `!help` help message
- `!gpt` To generate a one time response:
@@ -115,6 +117,8 @@ https://github.com/hibobmaster/matrix_chatgpt_bot/wiki/
1. [matrix-nio](https://github.com/poljar/matrix-nio)
2. [acheong08](https://github.com/acheong08)
3. [node-chatgpt-api](https://github.com/waylaidwanderer/node-chatgpt-api)
+4. [8go](https://github.com/8go/)
+
diff --git a/bot.py b/bot.py
index 176097b..a8ce610 100644
--- a/bot.py
+++ b/bot.py
@@ -42,6 +42,7 @@ class Bot:
jailbreakEnabled: Union[bool, None] = True,
bing_auth_cookie: Union[str, None] = '',
markdown_formatted: Union[bool, None] = False,
+ output_four_images: Union[bool, None] = False,
import_keys_path: Optional[str] = None,
import_keys_password: Optional[str] = None,
):
@@ -88,6 +89,11 @@ class Bot:
else:
self.markdown_formatted = markdown_formatted
+ if output_four_images is None:
+ self.output_four_images = False
+ else:
+ self.output_four_images = output_four_images
+
# initialize AsyncClient object
self.store_path = os.getcwd()
self.config = AsyncClientConfig(store=SqliteStore,
@@ -95,7 +101,8 @@ class Bot:
store_sync_tokens=True,
encryption_enabled=True,
)
- self.client = AsyncClient(homeserver=self.homeserver, user=self.user_id, device_id=self.device_id,
+ self.client = AsyncClient(homeserver=self.homeserver, user=self.user_id,
+ device_id=self.device_id,
config=self.config, store_path=self.store_path,)
if self.access_token is not None:
@@ -111,7 +118,7 @@ class Bot:
self.client.add_to_device_callback(
self.to_device_callback, (KeyVerificationEvent, ))
- # regular expression to match keyword [!gpt {prompt}] [!chat {prompt}] [!bing {prompt}] [!pic {prompt}] [!bard {prompt}]
+ # regular expression to match keyword commands
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*(.+)$")
@@ -149,7 +156,7 @@ class Bot:
def __del__(self):
try:
loop = asyncio.get_running_loop()
- except RuntimeError as e:
+ except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self._close())
@@ -202,10 +209,12 @@ class Bot:
)
except Exception as e:
logger.error(e, exc_info=True)
- await send_room_message(self.client, room_id, reply_message=str(e))
+ await send_room_message(self.client, room_id,
+ reply_message=str(e))
else:
logger.warning("No API_KEY provided")
- await send_room_message(self.client, room_id, reply_message="API_KEY not provided")
+ await send_room_message(self.client, room_id,
+ reply_message="API_KEY not provided")
m = self.gpt_prog.match(content_body)
if m:
@@ -239,7 +248,8 @@ class Bot:
)
except Exception as e:
logger.error(e, exc_info=True)
- await send_room_message(self.client, room_id, reply_message=str(e))
+ await send_room_message(self.client, room_id,
+ reply_message=str(e))
# Image Generation by Microsoft Bing
if self.bing_auth_cookie != '':
@@ -250,7 +260,8 @@ class Bot:
asyncio.create_task(self.pic(room_id, prompt))
except Exception as e:
logger.error(e, exc_info=True)
- await send_room_message(self.client, room_id, reply_message=str(e))
+ await send_room_message(self.client, room_id,
+ reply_message=str(e))
# Google's Bard
if self.bard_token is not None:
@@ -281,7 +292,8 @@ class Bot:
return
logger.error(
- f"Failed to decrypt message: {event.event_id} from {event.sender} in {room.room_id}\n" +
+ f"Failed to decrypt message: {event.event_id} \
+ from {event.sender} in {room.room_id}\n" +
"Please make sure the bot current session is verified"
)
@@ -501,7 +513,8 @@ class Bot:
logger.info(estr)
# !chat command
- async def chat(self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message):
+ async def chat(self, room_id, reply_to_event_id, prompt, sender_id,
+ raw_user_message):
await self.client.room_typing(room_id, timeout=120000)
try:
text = await self.chatbot.ask_async(prompt)
@@ -511,17 +524,23 @@ class Bot:
try:
text = text.strip()
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)
+ 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)
# !gpt command
- async def gpt(self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message) -> None:
+ async def gpt(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=240000)
# timeout 240s
- text = await asyncio.wait_for(self.askgpt.oneTimeAsk(prompt, self.chatgpt_api_endpoint, self.headers), timeout=240)
+ text = await asyncio.wait_for(self.askgpt.oneTimeAsk(prompt,
+ self.chatgpt_api_endpoint,
+ self.headers),
+ timeout=240)
except TimeoutError:
logger.error("TimeoutException", exc_info=True)
raise Exception("Timeout error")
@@ -531,12 +550,15 @@ class Bot:
try:
text = text.strip()
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)
+ 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)
# !bing command
- async def bing(self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message) -> None:
+ 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)
@@ -551,12 +573,15 @@ class Bot:
try:
text = text.strip()
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)
+ 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)
# !bard command
- async def bard(self, room_id, reply_to_event_id, prompt, sender_id, raw_user_message) -> None:
+ 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)
@@ -567,7 +592,9 @@ class Bot:
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)
+ 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)
@@ -580,14 +607,16 @@ class Bot:
try:
links = await self.imageGen.get_images(prompt)
- image_path = await self.imageGen.save_images(links, "images")
+ image_path_list = await self.imageGen.save_images(links, "images",
+ self.output_four_images)
except Exception as e:
logger.error(f"Image Generation error: {e}", exc_info=True)
raise Exception(e)
# send image
try:
- await send_room_image(self.client, room_id, image_path)
+ for image_path in image_path_list:
+ await send_room_image(self.client, room_id, image_path)
await self.client.room_typing(room_id, typing_state=False)
except Exception as e:
logger.error(e, exc_info=True)
@@ -605,7 +634,7 @@ class Bot:
"!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"
+ "!help, help message" # noqa: E501
await send_room_message(self.client, room_id, reply_message=help_info)
except Exception as e:
@@ -635,7 +664,7 @@ class Bot:
logger.error(f"import_keys failed with {resp}")
else:
logger.info(
- f"import_keys success, please remove import_keys configuration!!!")
+ "import_keys success, please remove import_keys configuration!!!")
# sync messages in the room
async def sync_forever(self, timeout=30000, full_state=True) -> None:
diff --git a/config.json.sample b/config.json.sample
index d50dc85..e0f1568 100644
--- a/config.json.sample
+++ b/config.json.sample
@@ -11,6 +11,7 @@
"bard_token": "xxxxxxx",
"bing_auth_cookie": "xxxxxxxxxxx",
"markdown_formatted": true,
+ "output_four_images": true,
"import_keys_path": "element-keys.txt",
"import_keys_password": "xxxxxxxxx"
}
diff --git a/main.py b/main.py
index 8338bf1..b467a9e 100644
--- a/main.py
+++ b/main.py
@@ -25,11 +25,13 @@ async def main():
jailbreakEnabled=config.get('jailbreakEnabled'),
bing_auth_cookie=config.get('bing_auth_cookie'),
markdown_formatted=config.get('markdown_formatted'),
+ output_four_images=config.get('output_four_images'),
import_keys_path=config.get('import_keys_path'),
import_keys_password=config.get(
'import_keys_password'),
)
- if config.get('import_keys_path') and config.get('import_keys_password') is not None:
+ if config.get('import_keys_path') and \
+ config.get('import_keys_password') is not None:
need_import_keys = True
else:
@@ -43,21 +45,27 @@ async def main():
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'),
+ "JAILBREAKENABLED", "false").lower() \
+ in ('true', '1', 't'),
bing_auth_cookie=os.environ.get("BING_AUTH_COOKIE"),
markdown_formatted=os.environ.get(
- "MARKDOWN_FORMATTED", "false").lower() in ('true', '1', 't'),
+ "MARKDOWN_FORMATTED", "false").lower() \
+ in ('true', '1', 't'),
+ output_four_images=os.environ.get(
+ "OUTPUT_FOUR_IMAGES", "false").lower() \
+ in ('true', '1', 't'),
import_keys_path=os.environ.get("IMPORT_KEYS_PATH"),
import_keys_password=os.environ.get(
"IMPORT_KEYS_PASSWORD"),
)
- if os.environ.get("IMPORT_KEYS_PATH") and os.environ.get("IMPORT_KEYS_PASSWORD") is not None:
+ if os.environ.get("IMPORT_KEYS_PATH") \
+ and os.environ.get("IMPORT_KEYS_PASSWORD") is not None:
need_import_keys = True
await matrix_bot.login()
if need_import_keys:
- logger.info("start import_keys process, this may take a while...")
- await matrix_bot.import_keys()
+ logger.info("start import_keys process, this may take a while...")
+ await matrix_bot.import_keys()
await matrix_bot.sync_forever(timeout=30000, full_state=True)