⚠️ Info: Auto-Registering is faulty at the moment.
What changed:
git clone https://github.com/Recentaly/JLLM-Wrapper.git
pip install -r requirements.txt
Side-note: You need to have Google Chrome installed alongside Selenium!
This is an unofficial API repository that allows you to interact with the JLLM through your own code. It includes many helper functions, including functions for registering accounts, logging in and chatting with the JLLM.
This was a rather challenging project for me and I had fun developing this. I had reversed JanitorAI before but now they added CloudFlare protection. I was about to give up but didn't quit and developed this as a follow-up.
This repository includes:
python-tempmail
so you can create temporary mails for registration.python-tempmail
account on JanitorAI's site.You require the JWT
to use the JLLM. Luckily, it's pretty simple. In the following, I will describe step-by-step instructions on creating a Python script to retrieve your JWT from JanitorAI. Here's a curt explanation on how I pulled this off:
First of all, I couldn't use Selenium anymore. This is because JAI added Cloudflare. And this made stuff so much more complicated. undetected-chromedriver
also kept getting caught when trying to log in or register. So I resorted to nodriver
-A hidden gem I hadn't found yet.
... However... Logging in worked perfectly fine now. Expect for the fact that I couldn't retrieve the JWT from localstorage like I'd normally do because nodriver
doesn't support that feature. Instead, I had to use a brain-meltingly complicated alternative.
When you log in, my Request middleman (aka the network handler) listens carefully for any on-going requests to https://kim.janitorai.com/profiles/mine
Because when you enter the home page, where you'll automatically be redirected to after logging in, it sends a request to that exact URL (to of-course fetch your profile). The handler middleman then can snoop inside that request and extract the Authorization header.
That header is then written to a temporary file (Because using return
isn't possible) and the log-in script reads that value before deleting the temporary file again. This was a headache to figure out, I'll admit.
You require the JWT
to use the JLLM. Luckily, it's pretty simple. In the following, I will describe step-by-step instructions on creating a Python script to retrieve your JWT from JanitorAI.
from scripts.__login import login
from scripts import uc
nodriver
to work.)# after the imports, create this function:
async def main():
...
login
function in your main
function to retrieve your JWT.async def main():
jwt = await login(
email="[email protected]",
password="your_password_here"
)
Since we're using nodriver
, we need to use its pre-made loop and run it until completion like this:
# start the code
if __name__ == "__main__":
uc.loop().run_until_complete(main())
All done now! Here's the full example code:
"""
A simple example of how to use the API.
"""
from scripts.__login import login
from scripts import uc
async def main():
jwt = await login(
email="[email protected]",
password="xxxxxxxx"
)
# start the code
if __name__ == "__main__":
uc.loop().run_until_complete(main())
If you don't want to use a pre-exisiting account, you can simply register a burner account for each run. However, this process of registering takes some additional time and it's always faster to use pre-exisiting accounts.
from scripts.__login import login
from scripts import uc, logger, verify_mail, get_message, random_string, EMail
from scripts.__register import register
main
and random credentials (and logging)# imports would be here
async def main():
email = EMail() # instance of tempmail-python
password = random_string(8)
# --- additional logging --- #
logger.info(f"Email address: {email.address}")
logger.info(f"Password: {password}")
register
function in an asynchronous context....
# comment: Use email.address for a string representation of the full E-Mail
# instead of the E-Mail class object
await register(email.address, password) # no return.
tempmail-python
has in-built functions to automatically retrieve any received E-Mails which is super helpful. On top of that, we'll be using a headless Selenium webdriver to access the verification mail and verify.(Side-note: If you'd like the Selenium driver not to be headless, you can simply change that in the __init__.py
by commenting out line 51 (__options.add_argument("--headless")
))
Yes, you are required to verify your mail. Only after verification, you're allowed JLLM access for free.
message = get_message(email)
logger.info("Got a message.")
verify_mail(message)
logger.info("Mail verified.")
Full code:
from scripts.__login import login
from scripts import uc, logger, verify_mail, get_message, random_string, EMail
from scripts.__register import register
async def main():
# Use code below to register and login using fake email and password
email = EMail()
password = random_string(8)
logger.info(f"Email address: {email.address}")
logger.info(f"Password: {password}")
await register(email.address, password)
message = get_message(email)
logger.info("Got a message.")
verify_mail(message)
logger.info("Mail verified.")
jwt = await login(
email="[email protected]",
password="xxxxxxxxxxx"
)
# start the code
if __name__ == "__main__":
uc.loop().run_until_complete(main())
Warning. Following section assumes you already have your JWT via the code above.
from scripts.API import API
"""
Your JWT is needed upon initializing the API class.
You could also pass an empty string but you must update the value
with an actually valid JWT before sending your request
Due to new infrastructure:
The chatting procedure now requires you to also provide a character link. Please use: "0d97bea1-eb06-4093-a470-c7945d14a58a_character-willson-wang" as the second parameter when initializing the API.
Never use your own characters.
It's recommended to use just any popular character because JAI won't take them down.
"""
api = API(jwt, "0d97bea1-eb06-4093-a470-c7945d14a58a_character-willson-wang") # replace jwt with the actual variable for you that holds your JWT value.
The generate
function, responsible for chatting with the JLLM, takes following parameters:
messages
| list[dict[str, str]]
: A list of messages in OpenAI's format to pass to the LLM.[
{
"role": "system",
"content": "You're an AI assistant!"
},
{
"role": "assistant",
"content": "Hello! How can I assist you today?"
},
{
"role": "user",
"content": "What's 9+10?"
}
]
max_tokens
| int
: This number represents the number of tokens the AI may generate. Refer to the OpenAI official tokenizer website to experiment with tokens as they differ from words. Defaults to 150
tokens.
repetition_penalty
| depracated, float
: As this isn't passed to the JLLM on the official JAI website, I'm not sure whether this actually has a difference. However, it once was used but I will call it depracated anyways now. Defaults to 1.2
. Wouldn't recommend changing this value.
stream
| bool
: Whether to stream the AI's response or not. If True
, then the generate
function will use a Generator[str, Any, Any]
to gradually yield tokens as the JLLM generates them. If False
, the generate
function will simply return the full text. Defaults to False.
temperature
| float
: Value of 'randomness' for the AI. Defaults to 0.7
. Wouldn't recommend you to have this value over 1.2
but results vary, I suppose.
system_message
| str
: A custom message that heavily influences how the AI acts.
generate
function example without streaming:api = API(jwt, "0d97bea1-eb06-4093-a470-c7945d14a58a_character-willson-wang") # use your actual JWT here and a character that is public on JAI
resp = api.generate(
messages=[{"role": "user", "content": "Yo"}],
stream=False)
print(next(resp))
# Output: Hello! How can I help you today? If you have any questions or need assistance, feel free to ask.
generate
function but this time with streamingapi = API(jwt, "0d97bea1-eb06-4093-a470-c7945d14a58a_character-willson-wang")
for chunk in api.generate(
messages=[{"role": "user", "content": "Yo"}],
stream=True
):
print(chunk, end="", flush=True)
print("\n") # final newline so AI's output doesn't merge with the debug message of selenium when the webdriver closes
# Output: Hello! How can I assist you today? If you have any questions or need help with something, feel free to ask.
Since the process of getting your JWT all the time is tedious and time-consuming, the handler actually stores your JWT and the current timestamp in files called TIME:temp
and TOKEN.temp
To reuse them, add this code instead of your usual login:
from scripts import get_preexisting_jwt, ...
# try getting jwt from file
jwt = get_preexisting_jwt(
# This path: Using pre-existing account
)
if jwt is None:
jwt = await login(
email="[email protected]",
password="xxxxxxxxxx"
)
Do note that a JWT is only valid for one hour and this code is not guaranteed to work.
403
403
and you might too. This can happen in the generation route, chats route and personas route. It's sadly very common right now. Re-run the code a few times and it'll work. I will implement a fix in the future.