⚠️ Info: Auto-Registering is faulty at the moment.
What changed:
- The API now creates a burner account and persona in your account. This is required due to the new infrastructure.
- Clone this repository
git clone https://github.com/Recentaly/JLLM-Wrapper.git
- Install requirements
pip install -r requirements.txt
Side-note: You need to have Google Chrome installed alongside Selenium!
- All finished! Now create a python file at the root of the directory to get started.
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:
- A registering function to easily register
- Comes with
python-tempmail
so you can create temporary mails for registration. - On top of that, this project includes functions to verify your
python-tempmail
account on JanitorAI's site. - Allows you to log in and retrieve your JWT
- With the JWT, you can chat with the JLLM (Janitor LLM) for free.
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.
- Import all neccessary modules
from scripts.__login import login
from scripts import uc
- create an asynchronous main function in your code (We need it to be asynchronous for
nodriver
to work.)
# after the imports, create this function:
async def main():
...
- Using the
login
function in yourmain
function to retrieve your JWT.
async def main():
jwt = await login(
email="your_email_here@whatever.com",
password="your_password_here"
)
- Running your function
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="xxxxxxxxx@xxxxx.xxx",
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.
- Importing neccessary modules This time, we need some more modules:
from scripts.__login import login
from scripts import uc, logger, verify_mail, get_message, random_string, EMail
from scripts.__register import register
- Creating your
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}")
- Registering your account
To register your account now, all you need to do is call the
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.
- Getting the confirmation E-Mail by JanitorAI and verifying it.
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.")
- Logging in and fetching your JWT You can now simply log in again like we did above.
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="whatever@idontreally.gaf",
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.
- Importing the API module
from scripts.API import API
- Initializing the API class
"""
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.
- Preparing your parameters:
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. Example:
[
{
"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 to150
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 to1.2
. Wouldn't recommend changing this value. -
stream
|bool
: Whether to stream the AI's response or not. IfTrue
, then thegenerate
function will use aGenerator[str, Any, Any]
to gradually yield tokens as the JLLM generates them. IfFalse
, thegenerate
function will simply return the full text. Defaults to False. -
temperature
|float
: Value of 'randomness' for the AI. Defaults to0.7
. Wouldn't recommend you to have this value over1.2
but results vary, I suppose. -
system_message
|str
: A custom message that heavily influences how the AI acts.
- Calling the
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.
- Calling the
generate
function but this time with streaming
api = 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.
- (Optional) Reusing valid JWTs
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="xxxxxxx@gmail.com",
password="xxxxxxxxxx"
)
Do note that a JWT is only valid for one hour and this code is not guaranteed to work.
- Error
403
Sometimes, I get error403
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.