{"id":27185497,"url":"https://github.com/5eroo/jllm-wrapper","last_synced_at":"2025-04-09T17:51:36.147Z","repository":{"id":233981554,"uuid":"787583976","full_name":"5eroo/JLLM-Wrapper","owner":"5eroo","description":"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.","archived":false,"fork":false,"pushed_at":"2025-02-03T02:51:27.000Z","size":104,"stargazers_count":2,"open_issues_count":5,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-23T19:51:24.817Z","etag":null,"topics":["ai","educational-project","jai","janitorai","jllm","llm","nodriver","py","py3","python","requests","reverse-engineering","tempmail","webdriver","wrapper-api"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/5eroo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-16T19:55:50.000Z","updated_at":"2024-09-28T20:15:00.000Z","dependencies_parsed_at":"2024-04-29T03:34:21.299Z","dependency_job_id":"cffbfb42-4b6b-446a-bf06-a034f3db59f5","html_url":"https://github.com/5eroo/JLLM-Wrapper","commit_stats":{"total_commits":34,"total_committers":3,"mean_commits":"11.333333333333334","dds":"0.47058823529411764","last_synced_commit":"3bd557ae4fd8dd24a94dd9ab90c3c16c23296dc8"},"previous_names":["recentaly/jllm-wrapper","itszerrin/jllm-wrapper","5eroo/jllm-wrapper"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5eroo%2FJLLM-Wrapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5eroo%2FJLLM-Wrapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5eroo%2FJLLM-Wrapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/5eroo%2FJLLM-Wrapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/5eroo","download_url":"https://codeload.github.com/5eroo/JLLM-Wrapper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248083132,"owners_count":21045047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","educational-project","jai","janitorai","jllm","llm","nodriver","py","py3","python","requests","reverse-engineering","tempmail","webdriver","wrapper-api"],"created_at":"2025-04-09T17:51:35.310Z","updated_at":"2025-04-09T17:51:36.140Z","avatar_url":"https://github.com/5eroo.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JLLM Wrapper for Python\r\n\r\n\u003e :warning: **Info:** Auto-Registering is faulty at the moment.\r\n\r\nWhat changed:\r\n\r\n- The API now creates a burner account and persona in your account. This is required due to the new infrastructure.\r\n\r\n## Table of Contents\r\n- [Installation](#installation)\r\n- [Introduction](#introduction)\r\n    - [Why I did this](#why-i-did-this)\r\n    - [Brief description of this repository](#brief-description-of-this-repository)\r\n- [Getting your JWT](#getting-your-jwt)\r\n    - [Using an existing account](#using-an-existing-account)\r\n    - [Registering with a burner account](#registering-with-a-burner-account)\r\n- [Chatting with the JLLM](#chatting-with-the-jllm)\r\n- [Common errors](#common-issues)\r\n- [Badges](#badges)\r\n\r\n## Installation\r\n\r\n1. Clone this repository\r\n```bash\r\ngit clone https://github.com/Recentaly/JLLM-Wrapper.git\r\n```\r\n\r\n2. Install requirements\r\n```bash\r\npip install -r requirements.txt\r\n```\r\n\r\n\u003cstrong\u003eSide-note: You need to have Google Chrome installed alongside Selenium!\u003c/strong\u003e\r\n\r\n3. All finished! Now create a python file at the root of the directory to get started.\r\n\r\n## Introduction\r\n\r\nThis 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.\r\n\r\n### Why I did this\r\n\r\nThis 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.\r\n\r\n## Brief description of this repository\r\n\r\nThis repository includes:\r\n\r\n- A registering function to easily register\r\n- Comes with `python-tempmail` so you can create temporary mails for registration.\r\n- On top of that, this project includes functions to verify your `python-tempmail` account on JanitorAI's site.\r\n- Allows you to log in and retrieve your JWT\r\n- With the JWT, you can chat with the JLLM (Janitor LLM) for free.\r\n\r\n## Getting your JWT\r\n\r\nYou 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:\r\n\r\nFirst 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.\r\n\r\n... 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.\r\n\r\nWhen 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.\r\n\r\nThat 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.\r\n\r\n### Using an existing account\r\n\r\nYou 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.\r\n\r\n1. Import all neccessary modules\r\n```py\r\nfrom scripts.__login import login\r\nfrom scripts import uc \r\n```\r\n\r\n2. create an asynchronous main function in your code (We need it to be asynchronous for `nodriver` to work.)\r\n```py\r\n# after the imports, create this function:\r\nasync def main():\r\n    ...\r\n```\r\n\r\n3. Using the `login` function in your `main` function to retrieve your JWT.\r\n```py\r\nasync def main():\r\n\r\n    jwt = await login(\r\n        email=\"your_email_here@whatever.com\",\r\n        password=\"your_password_here\"\r\n    )\r\n```\r\n\r\n4. Running your function\r\n\r\nSince we're using `nodriver`, we need to use its pre-made loop and run it until completion like this:\r\n\r\n```py\r\n# start the code\r\nif __name__ == \"__main__\":\r\n\r\n    uc.loop().run_until_complete(main())\r\n```\r\n\r\nAll done now! Here's the full example code:\r\n\r\n```py\r\n\"\"\"\r\nA simple example of how to use the API.\r\n\"\"\"\r\n\r\nfrom scripts.__login import login\r\nfrom scripts import uc\r\n\r\nasync def main():\r\n\r\n    jwt = await login(\r\n        email=\"xxxxxxxxx@xxxxx.xxx\",\r\n        password=\"xxxxxxxx\"\r\n    )\r\n    \r\n# start the code\r\nif __name__ == \"__main__\":\r\n\r\n    uc.loop().run_until_complete(main())\r\n```\r\n\r\n### Registering with a burner account\r\n\r\nIf 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.\r\n\r\n1. Importing neccessary modules\r\nThis time, we need some more modules:\r\n\r\n```py\r\nfrom scripts.__login import login\r\nfrom scripts import uc, logger, verify_mail, get_message, random_string, EMail\r\nfrom scripts.__register import register\r\n```\r\n\r\n2. Creating your `main` and random credentials (and logging)\r\n```py\r\n# imports would be here\r\nasync def main():\r\n\r\n    email = EMail() # instance of tempmail-python\r\n    password = random_string(8)\r\n\r\n    # --- additional logging --- #\r\n    logger.info(f\"Email address: {email.address}\")\r\n    logger.info(f\"Password: {password}\")\r\n```\r\n\r\n3. Registering your account\r\nTo register your account now, all you need to do is call the `register` function in an asynchronous context.\r\n\r\n```py\r\n...\r\n# comment: Use email.address for a string representation of the full E-Mail\r\n# instead of the E-Mail class object\r\nawait register(email.address, password) # no return.\r\n```\r\n\r\n4. Getting the confirmation E-Mail by JanitorAI and verifying it.\r\n`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.\r\n\r\n(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\")`))\r\n\r\nYes, you are required to verify your mail. Only after verification, you're allowed JLLM access for free.\r\n\r\n```py\r\nmessage = get_message(email)\r\nlogger.info(\"Got a message.\")\r\n\r\nverify_mail(message)\r\nlogger.info(\"Mail verified.\")\r\n```\r\n\r\n5. Logging in and fetching your JWT\r\nYou can now simply log in again like we did above.\r\n\r\nFull code:\r\n\r\n```py\r\nfrom scripts.__login import login\r\nfrom scripts import uc, logger, verify_mail, get_message, random_string, EMail\r\nfrom scripts.__register import register\r\n\r\nasync def main():\r\n\r\n    # Use code below to register and login using fake email and password\r\n\r\n    email = EMail()\r\n    password = random_string(8)\r\n\r\n    logger.info(f\"Email address: {email.address}\")\r\n    logger.info(f\"Password: {password}\")\r\n\r\n    await register(email.address, password)\r\n\r\n    message = get_message(email)\r\n    logger.info(\"Got a message.\")\r\n\r\n    verify_mail(message)\r\n    logger.info(\"Mail verified.\")\r\n\r\n    jwt = await login(\r\n        email=\"whatever@idontreally.gaf\",\r\n        password=\"xxxxxxxxxxx\"\r\n    )\r\n\r\n# start the code\r\nif __name__ == \"__main__\":\r\n\r\n    uc.loop().run_until_complete(main())\r\n```\r\n\r\n## Chatting with the JLLM\r\n\r\nWarning. Following section assumes you already have your JWT via the code above.\r\n\r\n1. Importing the API module\r\n```py\r\nfrom scripts.API import API\r\n```\r\n\r\n2. Initializing the API class\r\n```py\r\n\"\"\"\r\nYour JWT is needed upon initializing the API class. \r\nYou could also pass an empty string but you must update the value\r\nwith an actually valid JWT before sending your request\r\n\r\nDue to new infrastructure:\r\n\r\nThe 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.\r\nNever use your own characters.\r\nIt's recommended to use just any popular character because JAI won't take them down.\r\n\"\"\"\r\n\r\napi = API(jwt, \"0d97bea1-eb06-4093-a470-c7945d14a58a_character-willson-wang\") # replace jwt with the actual variable for you that holds your JWT value.\r\n```\r\n\r\n3. Preparing your parameters:\r\n\r\nThe `generate` function, responsible for chatting with the JLLM, takes following parameters:\r\n\r\n- `messages` | `list[dict[str, str]]`: A list of messages in OpenAI's format to pass to the LLM.\r\nExample:\r\n```json\r\n[\r\n    {\r\n        \"role\": \"system\",\r\n        \"content\": \"You're an AI assistant!\"\r\n    },\r\n    {\r\n        \"role\": \"assistant\",\r\n        \"content\": \"Hello! How can I assist you today?\"\r\n    },\r\n    {\r\n        \"role\": \"user\",\r\n        \"content\": \"What's 9+10?\"\r\n    }\r\n]\r\n```\r\n\r\n- `max_tokens` | `int`: This number represents the number of tokens the AI may generate. Refer to the [OpenAI official tokenizer website](https://platform.openai.com/tokenizer) to experiment with tokens as they differ from words. Defaults to `150` tokens.\r\n\r\n- `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.\r\n\r\n- `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.\r\n\r\n- `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.\r\n\r\n- `system_message` | `str`: A custom message that heavily influences how the AI acts.\r\n\r\n4. Calling the `generate` function example **without** streaming:\r\n```py\r\napi = API(jwt, \"0d97bea1-eb06-4093-a470-c7945d14a58a_character-willson-wang\") # use your actual JWT here and a character that is public on JAI\r\n\r\nresp = api.generate(\r\n    messages=[{\"role\": \"user\", \"content\": \"Yo\"}],\r\n    stream=False)\r\n\r\nprint(next(resp))\r\n\r\n# Output: Hello! How can I help you today? If you have any questions or need assistance, feel free to ask.\r\n```\r\n\r\n5. Calling the `generate` function but this time with streaming\r\n```py\r\napi = API(jwt, \"0d97bea1-eb06-4093-a470-c7945d14a58a_character-willson-wang\")\r\n\r\nfor chunk in api.generate(\r\n    messages=[{\"role\": \"user\", \"content\": \"Yo\"}],\r\n    stream=True\r\n):\r\n\r\n    print(chunk, end=\"\", flush=True)\r\nprint(\"\\n\") # final newline so AI's output doesn't merge with the debug message of selenium when the webdriver closes\r\n\r\n# Output: Hello! How can I assist you today? If you have any questions or need help with something, feel free to ask.\r\n```\r\n\r\n6. (Optional) Reusing valid JWTs\r\n\r\nSince 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``\r\n\r\nTo reuse them, add this code instead of your usual login:\r\n\r\n```py\r\nfrom scripts import get_preexisting_jwt, ...\r\n\r\n# try getting jwt from file\r\njwt = get_preexisting_jwt(\r\n    # This path: Using pre-existing account\r\n)\r\n\r\nif jwt is None:\r\n\r\n    jwt = await login(\r\n        email=\"xxxxxxx@gmail.com\",\r\n        password=\"xxxxxxxxxx\"\r\n    )\r\n```\r\n\r\nDo note that a JWT is only valid for one hour and this code is not guaranteed to work.\r\n\r\n## Common issues\r\n\r\n1. Error `403`\r\n   Sometimes, I get error `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.\r\n\r\n## Badges\r\n[![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3.0-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\r\n[![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-310/)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5eroo%2Fjllm-wrapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F5eroo%2Fjllm-wrapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F5eroo%2Fjllm-wrapper/lists"}