|
import aiohttp |
|
import asyncio |
|
import enum |
|
import requests |
|
import os |
|
from functools import cache |
|
import tempfile |
|
import uuid |
|
|
|
|
|
class VoiceType(enum.Enum): |
|
voice1 = "voice1" |
|
voice2 = "voice2" |
|
voice3 = "voice3" |
|
voice4 = "voice4" |
|
voice5 = "voice5" |
|
voice5_update = "voice5-update" |
|
voice6 = "voice6" |
|
voice7 = "voice7" |
|
voice8 = "voice8" |
|
voice9 = "voice9" |
|
voice10 = "voice10" |
|
voice11 = "voice11" |
|
voice12 = "voice12" |
|
qdpi = "qdpi" |
|
|
|
|
|
class PiAIClient: |
|
def __init__(self): |
|
self.dir = "/tmp/Audio" |
|
self.base_url = "https://pi.ai/api/chat" |
|
self.referer = "https://pi.ai/talk" |
|
self.origin = "https://pi.ai" |
|
self.user_agent = ( |
|
"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0" |
|
) |
|
self.cookie = None |
|
self.headers = { |
|
"User-Agent": self.user_agent, |
|
"Accept": "text/event-stream", |
|
"Referer": self.referer, |
|
"X-Api-Version": "3", |
|
"Content-Type": "application/json", |
|
"Origin": self.origin, |
|
"Connection": "keep-alive", |
|
"Sec-Fetch-Dest": "empty", |
|
"Sec-Fetch-Mode": "no-cors", |
|
"Sec-Fetch-Site": "same-origin", |
|
"DNT": "1", |
|
"Sec-GPC": "1", |
|
"TE": "trailers", |
|
"Cookie": "__cf_bm=XagWXCS3SJekiP5O.A8K9wgtGuEieLNW7AFXj10hzqk-1717865973-1.0.1.1-4Xp_xUVYB5G.Zkpfgcm30PCVGnj3g6URzZsfCS28BQIdt8dZm76rnNbQiX9vNG_OsYdbUiDiX2pa.E3ajhcOXA; path=/; expires=Sat, 08-Jun-24 17:29:33 GMT; domain=.pi.ai; HttpOnly; Secure; SameSite=None", |
|
"Pragma": "no-cache", |
|
"Cache-Control": "no-cache", |
|
} |
|
|
|
async def get_cookie(self) -> str: |
|
headers = self.headers.copy() |
|
async with aiohttp.ClientSession() as session: |
|
async with session.post( |
|
f"{self.base_url}/start", headers=headers, json={} |
|
) as response: |
|
self.cookie = response.headers["Set-Cookie"] |
|
return self.cookie |
|
|
|
async def make_request( |
|
self, endpoint: str, headers: dict, json: dict = None, method: str = "POST" |
|
): |
|
async with aiohttp.ClientSession() as session: |
|
if method == "POST": |
|
async with session.post( |
|
endpoint, headers=headers, json=json |
|
) as response: |
|
return await response.text() |
|
elif method == "GET": |
|
async with session.get(endpoint, headers=headers) as response: |
|
return response |
|
|
|
async def get_response(self, input_text) -> tuple[list[str], list[str]]: |
|
if self.cookie is None: |
|
self.cookie = await self.get_cookie() |
|
|
|
headers = self.headers.copy() |
|
headers["Cookie"] = self.cookie |
|
|
|
data = {"text": input_text} |
|
response_text = await self.make_request(self.base_url, headers, json=data) |
|
|
|
response_lines = response_text.split("\n") |
|
response_texts = [] |
|
response_sids = [] |
|
print(response_lines) |
|
for line in response_lines: |
|
if line.startswith('data: {"text":"'): |
|
start = len('data: {"text":') |
|
end = line.rindex("}") |
|
text_dict = line[start + 1 : end - 1].strip() |
|
response_texts.append(text_dict) |
|
elif line.startswith('data: {"sid":'): |
|
start = len('data: {"sid":') |
|
end = line.rindex("}") |
|
sid_dict = line[start : end - 1].strip() |
|
sid_dict = sid_dict.split(",")[0][1:-1] |
|
response_sids.append(sid_dict) |
|
|
|
return response_texts, response_sids |
|
|
|
async def speak_response( |
|
self, message_sid: str, voice: VoiceType = VoiceType.voice4.value |
|
) -> None: |
|
if self.cookie is None: |
|
self.cookie = await self.get_cookie() |
|
|
|
headers = self.headers.copy() |
|
headers.update( |
|
{ |
|
"Host": "pi.ai", |
|
"Accept": "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5", |
|
"Accept-Language": "en-US,en;q=0.9", |
|
"Range": "bytes=0-", |
|
"Sec-Fetch-Dest": "audio", |
|
"Sec-Fetch-Mode": "no-cors", |
|
"Sec-Fetch-Site": "same-origin", |
|
"Sec-CH-UA": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', |
|
"Sec-CH-UA-Mobile": "?0", |
|
"Sec-CH-UA-Platform": '"Windows"', |
|
} |
|
) |
|
|
|
headers = { |
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0", |
|
"Accept": "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5", |
|
"Accept-Language": "en-US,en;q=0.5", |
|
"Range": "bytes=0-", |
|
"Connection": "keep-alive", |
|
"Referer": "https://pi.ai/talk", |
|
|
|
"Sec-Fetch-Dest": "audio", |
|
"Sec-Fetch-Mode": "no-cors", |
|
"Sec-Fetch-Site": "same-origin", |
|
"DNT": "1", |
|
"Sec-GPC": "1", |
|
"Accept-Encoding": "identity", |
|
"TE": "trailers", |
|
} |
|
headers["Cookie"] = self.cookie |
|
print(headers) |
|
endpoint = ( |
|
f"{self.base_url}/voice?mode=eager&voice={voice}&messageSid={message_sid}" |
|
) |
|
async with aiohttp.ClientSession() as session: |
|
async with session.get(endpoint, headers=headers) as response: |
|
print(response.status) |
|
file_name = str(uuid.uuid4()) + ".mp3" |
|
file_path = os.path.join(self.dir, file_name) |
|
os.makedirs(self.dir, exist_ok=True) |
|
if response.status == 200: |
|
with open(file_path, "wb") as file: |
|
async for chunk in response.content.iter_chunked(128): |
|
file.write(chunk) |
|
return { |
|
"url": f"https://yakova-embedding.hf.space/audio/{file_name}" |
|
} |
|
|
|
|
|
else: |
|
temp = await response.text() |
|
print(temp) |
|
self.cookie = None |
|
return "Error: Unable to retrieve audio." |
|
|
|
async def say(self, text, voice=VoiceType.qdpi.value): |
|
_, response_sids = await self.get_response(text) |
|
|
|
if response_sids: |
|
return await self.speak_response(response_sids[0], voice=voice) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|