├── characterai
├── types
│ ├── __init__.py
│ ├── user.py
│ ├── recent.py
│ ├── other.py
│ ├── chat2.py
│ ├── account.py
│ ├── character.py
│ └── chat1.py
├── errors.py
├── pycai
│ ├── __init__.py
│ ├── methods
│ │ ├── __init__.py
│ │ ├── users.py
│ │ ├── recent.py
│ │ ├── chats.py
│ │ ├── other.py
│ │ ├── utils.py
│ │ ├── chat1.py
│ │ ├── account.py
│ │ ├── characters.py
│ │ └── chat2.py
│ └── client.py
├── aiocai
│ ├── __init__.py
│ ├── methods
│ │ ├── __init__.py
│ │ ├── users.py
│ │ ├── recent.py
│ │ ├── chats.py
│ │ ├── other.py
│ │ ├── utils.py
│ │ ├── chat1.py
│ │ ├── account.py
│ │ ├── characters.py
│ │ └── chat2.py
│ └── client.py
├── __init__.py
└── auth.py
├── docs
├── _static
│ ├── theme.js
│ ├── search.svg
│ └── style.css
├── _templates
│ └── autosummary
│ │ ├── class.rst
│ │ └── method.rst
├── images
│ ├── logo.png
│ ├── title.png
│ └── full_logo.png
├── client.rst
├── errors.rst
├── support.rst
├── Makefile
├── make.bat
├── qna.rst
├── types
│ └── index.rst
├── auth.rst
├── methods
│ └── index.rst
├── index.rst
├── changelog.rst
├── conf.py
└── starting.rst
├── requirements.txt
├── .github
└── FUNDING.yml
├── examples
├── sync
│ ├── login.py
│ ├── auth.py
│ ├── chat1.py
│ └── chat2.py
└── async
│ ├── login.py
│ ├── auth.py
│ ├── chat1.py
│ └── chat2.py
├── .readthedocs.yml
├── setup.py
├── LICENSE
├── README.md
└── .gitignore
/characterai/types/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/_static/theme.js:
--------------------------------------------------------------------------------
1 | document.body.dataset.theme = 'dark';
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pydantic>=2.7.1
2 | websockets
3 | curl_cffi
4 |
--------------------------------------------------------------------------------
/docs/_templates/autosummary/class.rst:
--------------------------------------------------------------------------------
1 | {{name | underline}}
2 |
3 | .. autoclass:: {{fullname}}()
--------------------------------------------------------------------------------
/docs/_templates/autosummary/method.rst:
--------------------------------------------------------------------------------
1 | {{name | underline}}
2 |
3 | .. automethod:: {{fullname}}
--------------------------------------------------------------------------------
/docs/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kramcat/CharacterAI/HEAD/docs/images/logo.png
--------------------------------------------------------------------------------
/docs/images/title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kramcat/CharacterAI/HEAD/docs/images/title.png
--------------------------------------------------------------------------------
/docs/images/full_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kramcat/CharacterAI/HEAD/docs/images/full_logo.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: ['qiwi.com/n/KRAMCAT']
4 |
--------------------------------------------------------------------------------
/characterai/errors.py:
--------------------------------------------------------------------------------
1 | class CAIError(Exception):
2 | ...
3 |
4 | class ServerError(CAIError):
5 | ...
6 |
7 | class AuthError(CAIError):
8 | ...
9 |
10 | class NotFoundError(CAIError):
11 | ...
12 |
13 | class JSONError(CAIError):
14 | ...
--------------------------------------------------------------------------------
/examples/sync/login.py:
--------------------------------------------------------------------------------
1 | from characterai import pycai, sendCode, authUser
2 |
3 | email = input('Enter your email: ')
4 |
5 | code = sendCode(email)
6 |
7 | link = input('Enter the link: ')
8 |
9 | token = authUser(link, email)
10 |
11 | print(f'YOUR TOKEN: {token}')
12 |
--------------------------------------------------------------------------------
/characterai/pycai/__init__.py:
--------------------------------------------------------------------------------
1 | # ____ _________ ____
2 | # / __ \__ __/ ____/ | / _/
3 | # / /_/ / / / / / / /| | / /
4 | # / ____/ /_/ / /___/ ___ |_/ /
5 | # /_/ \__, /\____/_/ |_/___/
6 | # /____/
7 |
8 | from .client import pycai
--------------------------------------------------------------------------------
/characterai/aiocai/__init__.py:
--------------------------------------------------------------------------------
1 | # ___ _ _________ ____
2 | # ___ _ _________ ____
3 | # / | (_)___ / ____/ | / _/
4 | # / /| | / / __ \/ / / /| | / /
5 | # / ___ |/ / /_/ / /___/ ___ |_/ /
6 | # /_/ |_/_/\____/\____/_/ |_/___/
7 |
8 | from .client import aiocai
--------------------------------------------------------------------------------
/docs/_static/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | build:
4 | os: ubuntu-22.04
5 | tools:
6 | python: "3.11"
7 | jobs:
8 | post_install:
9 | - pip install sphinx-inline-tabs sphinx-copybutton furo sphinxcontrib-towncrier pydantic curl_cffi websockets
10 |
11 | sphinx:
12 | configuration: docs/conf.py
13 |
14 | formats: all
15 |
--------------------------------------------------------------------------------
/examples/sync/auth.py:
--------------------------------------------------------------------------------
1 | from characterai import pycai
2 |
3 | # Usually
4 | client = pycai.Client('TOKEN')
5 |
6 | print(client.get_me())
7 |
8 | client.close()
9 |
10 | # Via context manager
11 | with pycai.Client('TOKEN') as client:
12 | print(client.get_me())
13 |
14 | # Via the function
15 | print(pycai.get_me(token='TOKEN'))
--------------------------------------------------------------------------------
/examples/async/login.py:
--------------------------------------------------------------------------------
1 | from characterai import aiocai, sendCode, authUser
2 | import asyncio
3 |
4 | async def main():
5 | email = input('Enter your email: ')
6 |
7 | code = sendCode(email)
8 |
9 | link = input('Enter the link: ')
10 |
11 | token = authUser(link, email)
12 |
13 | print(f'YOUR TOKEN: {token}')
14 |
15 | asyncio.run(main())
16 |
--------------------------------------------------------------------------------
/characterai/aiocai/methods/__init__.py:
--------------------------------------------------------------------------------
1 | from .users import Users
2 | from .characters import Characters
3 | from .account import Account
4 | from .other import Other
5 | from .recent import Recent
6 | from .chats import Chats
7 | from .chat2 import ChatV2
8 |
9 | class Methods(
10 | Users, Characters,
11 | Account, Recent,
12 | Chats, Other, ChatV2
13 | ):
14 | ...
--------------------------------------------------------------------------------
/characterai/pycai/methods/__init__.py:
--------------------------------------------------------------------------------
1 | from .users import Users
2 | from .characters import Characters
3 | from .account import Account
4 | from .other import Other
5 | from .recent import Recent
6 | from .chats import Chats
7 | from .chat2 import ChatV2
8 |
9 | class Methods(
10 | Users, Characters,
11 | Account, Recent,
12 | Chats, Other, ChatV2
13 | ):
14 | ...
--------------------------------------------------------------------------------
/examples/sync/chat1.py:
--------------------------------------------------------------------------------
1 | from characterai import pycai
2 |
3 | token = 'YOUR TOKEN'
4 |
5 | client = pycai.Client(token)
6 |
7 | char = input('CHAR: ')
8 |
9 | new = client.chat1.new_chat(char)
10 |
11 | while True:
12 | text = input('YOU: ')
13 |
14 | message = client.chat1.send_message(
15 | new.id, new.tgt, text
16 | )
17 |
18 | print(f'{message.author}: {message.text}')
--------------------------------------------------------------------------------
/docs/client.rst:
--------------------------------------------------------------------------------
1 | ######
2 | Client
3 | ######
4 |
5 | The main class for working with the library
6 |
7 | It can also work as a context manager
8 |
9 | .. code-block:: python
10 |
11 | async with aiocai.Client('TOKEN') as client:
12 | await client.get_me()
13 |
14 | .. autoclass:: characterai.aiocai.client.aiocai.Client()
15 |
16 | .. autofunction:: characterai.aiocai.client.aiocai.Client.close
--------------------------------------------------------------------------------
/characterai/__init__.py:
--------------------------------------------------------------------------------
1 | # DISCLAIMER:
2 | # This is not an official library and is not coordinated with developers who may not like it.
3 | # You may use the library for any purpose and modify it as you wish,
4 | # but you must be sure to include the name KRAMCAT as the original author
5 |
6 | __version__ = '1.0.0a1'
7 |
8 | from .aiocai.client import aiocai
9 | from .pycai.client import pycai
10 | from .auth import sendCode, authUser, authGuest
--------------------------------------------------------------------------------
/examples/async/auth.py:
--------------------------------------------------------------------------------
1 | from characterai import aiocai
2 | import asyncio
3 |
4 | async def main():
5 | # Usually
6 | client = aiocai.Client('TOKEN')
7 |
8 | print(await client.get_me())
9 |
10 | await client.close()
11 |
12 | # Via context manager
13 | async with aiocai.Client('TOKEN') as client:
14 | print(await client.get_me())
15 |
16 | # Via the function
17 | print(await aiocai.get_me(token='TOKEN'))
18 |
19 | asyncio.run(main())
--------------------------------------------------------------------------------
/docs/errors.rst:
--------------------------------------------------------------------------------
1 | ######
2 | Errors
3 | ######
4 |
5 | ServerError
6 | ===========
7 |
8 | Any error on the server side
9 |
10 | AuthError
11 | =========
12 |
13 | It means that you have the wrong token
14 |
15 | NotFoundError
16 | =============
17 |
18 | The user's public account was not found
19 |
20 | JSONError
21 | =========
22 |
23 | The server response contains a response that cannot be decoded in JSON. These can be either individual characters or the entire text, which may not be JSON
--------------------------------------------------------------------------------
/docs/support.rst:
--------------------------------------------------------------------------------
1 | #######
2 | Support
3 | #######
4 |
5 | If you like this library and documentation, you can support my project through cryptocurrency
6 |
7 | Thank you so much for all donations, it really supports my work with the library and future projects
8 |
9 | | TON - ``UQBlGz8aw5tWaocR8gPppQe6SgTx-kkh5keInKtEzVOqPhdY``
10 | | BTC - ``bc1qghtyl43jd6xr66wwtrxkpe04sglqlwgcp04yl9``
11 | | ETH - ``0x1489B0DDCE07C029040331e4c66F5aA94D7B4d4e``
12 | | USDT (TRC20) - ``TJpvALv9YiL2khFBb7xfWrUDpvL5nYFs8u``
--------------------------------------------------------------------------------
/examples/async/chat1.py:
--------------------------------------------------------------------------------
1 | from characterai import aiocai
2 | import asyncio
3 |
4 | token = 'YOUR TOKEN'
5 |
6 | async def main():
7 | client = aiocai.Client(token)
8 |
9 | char = input('CHAR: ')
10 |
11 | new = await client.chat1.new_chat(char)
12 |
13 | while True:
14 | text = input('YOU: ')
15 |
16 | message = await client.chat1.send_message(
17 | new.id, new.tgt, text
18 | )
19 |
20 | print(f'{message.author}: {message.text}')
21 |
22 | asyncio.run(main())
--------------------------------------------------------------------------------
/examples/sync/chat2.py:
--------------------------------------------------------------------------------
1 | from characterai import pycai
2 |
3 | char = input('CHAR ID: ')
4 |
5 | client = pycai.Client('TOKEN')
6 |
7 | me = client.get_me()
8 |
9 | with client.connect() as chat:
10 | new, answer = chat.new_chat(
11 | char, me.id
12 | )
13 |
14 | print(f'{answer.name}: {answer.text}')
15 |
16 | while True:
17 | text = input('YOU: ')
18 |
19 | message = chat.send_message(
20 | char, new.chat_id, text
21 | )
22 |
23 | print(f'{message.name}: {message.text}')
--------------------------------------------------------------------------------
/examples/async/chat2.py:
--------------------------------------------------------------------------------
1 | from characterai import aiocai
2 | import asyncio
3 |
4 | async def main():
5 | char = input('CHAR ID: ')
6 |
7 | client = aiocai.Client('TOKEN')
8 |
9 | me = await client.get_me()
10 |
11 | async with await client.connect() as chat:
12 | new, answer = await chat.new_chat(
13 | char, me.id
14 | )
15 |
16 | print(f'{answer.name}: {answer.text}')
17 |
18 | while True:
19 | text = input('YOU: ')
20 |
21 | message = await chat.send_message(
22 | char, new.chat_id, text
23 | )
24 |
25 | print(f'{message.name}: {message.text}')
26 |
27 | asyncio.run(main())
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/characterai/pycai/methods/users.py:
--------------------------------------------------------------------------------
1 | from ...errors import NotFoundError
2 | from ...types import user
3 |
4 | from .utils import caimethod
5 |
6 | class Users:
7 | @caimethod
8 | def get_user(
9 | self, username: str, *, token: str = None
10 | ):
11 | """User info by nickname
12 |
13 | EXAMPLE::
14 |
15 | await client.get_user('USERNAME')
16 |
17 | Args:
18 | username (``str``):
19 | User nickname
20 |
21 | Returns:
22 | :obj:`~characterai.types.user.User`
23 | """
24 | data = self.request(
25 | 'chat/user/public/', token=token,
26 | data={'username': username}
27 | )
28 |
29 | if data['public_user'] == []:
30 | raise NotFoundError(
31 | f'User {username} not found.'
32 | )
33 |
34 | return user.User.model_validate(
35 | data['public_user']
36 | )
--------------------------------------------------------------------------------
/characterai/aiocai/methods/users.py:
--------------------------------------------------------------------------------
1 | from ...errors import NotFoundError
2 | from ...types import user
3 |
4 | from .utils import caimethod
5 |
6 | class Users:
7 | @caimethod
8 | async def get_user(
9 | self, username: str, *, token: str = None
10 | ):
11 | """User info by nickname
12 |
13 | EXAMPLE::
14 |
15 | await client.get_user('USERNAME')
16 |
17 | Args:
18 | username (``str``):
19 | User nickname
20 |
21 | Returns:
22 | :obj:`~characterai.types.user.User`
23 | """
24 | data = await self.request(
25 | 'chat/user/public/', token=token,
26 | data={'username': username}
27 | )
28 |
29 | if data['public_user'] == []:
30 | raise NotFoundError(
31 | f'User {username} not found.'
32 | )
33 |
34 | return user.User.model_validate(
35 | data['public_user']
36 | )
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | with open('README.md', encoding='utf-8') as f:
4 | readme = f.read()
5 |
6 | setup(
7 | name='characterai',
8 | version='1.0.0',
9 | description='An unofficial API for Character AI for Python',
10 | keywords='ai wrapper api library',
11 | long_description=readme,
12 | long_description_content_type='text/markdown',
13 | url='https://github.com/kramcat/characterai',
14 | author='kramcat',
15 | license='MIT',
16 | install_requires=['pydantic', 'curl_cffi', 'websockets'],
17 | packages=find_packages(include=['characterai*']),
18 | project_urls={
19 | 'Community': 'https://discord.gg/ZHJe3tXQkf',
20 | 'Source': 'https://github.com/kramcat/characterai',
21 | 'Documentation': 'https://docs.kram.cat',
22 | },
23 | classifiers=[
24 | 'Programming Language :: Python :: 3.10',
25 | 'License :: OSI Approved :: MIT License',
26 | ],
27 | )
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Mark
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/characterai/types/user.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 | from typing import List
3 |
4 | from .other import Avatar
5 | from .character import CharShort
6 |
7 | class User(BaseModel, Avatar):
8 | """User info
9 |
10 | Parameters:
11 | characters (List of :obj:`~characterai.types.character.CharShort`):
12 | List of user's public characters
13 |
14 | username (``str``):
15 | Public username
16 |
17 | name (``str``):
18 | Public name
19 |
20 | num_following (``int``):
21 | Number of users subscribed to the account
22 |
23 | num_followers (``int``):
24 | Number of users who are subscribed to the account
25 |
26 | avatar_file_name (``str``, *optional*):
27 | Path to the avatar on the server
28 |
29 | subscription_type (``str``):
30 | Type of c.ai subscription
31 |
32 | bio (``str``, *optional*):
33 | Account desctiption
34 |
35 | creator_info (``str``, *optional*):
36 | Author information. don't know what kind
37 |
38 | avatar (:obj:`~characterai.types.other.Avatar`):
39 | Avatar info
40 | """
41 | characters: List[CharShort]
42 | username: str
43 | name: str
44 | num_following: int
45 | num_followers: int
46 | avatar_file_name: str | None
47 | subscription_type: str
48 | bio: str | None
49 | creator_info: str | None
--------------------------------------------------------------------------------
/docs/qna.rst:
--------------------------------------------------------------------------------
1 | ####################
2 | Answers to Questions
3 | ####################
4 |
5 | What's ``author_id``?
6 | =====================
7 |
8 | This is your account ID, needed to create a new chat in chat2. It can be found in :obj:`~characterai.aiocai.methods.account.Account.Account.get_me()`.
9 |
10 | What's ``tgt``?
11 | ===============
12 |
13 | This is an old character ID type, needed for chat1. You can get it in :obj:`~characterai.types.character.Character.Character.get_char` under ``identifier``.
14 |
15 | What's the difference between chat1 and chat2?
16 | ==============================================
17 |
18 | For the average user there are no significant differences except that you can't send pictures in chat2 (actually you can, the library allows it, but not on the site)
19 |
20 | But on the server side there are huge differences. First of all, chat1 works via HTTPS requests, while chat2 works via WebSockets. Secondly, the requests and their responses are very different, and accordingly their logic is very different
21 |
22 | chat2 is currently the newest version of chat2, hardly worth waiting for chat3
23 |
24 | .. note::
25 |
26 | You can migrate chat1 to chat2 with :obj:`~characterai.aiocai.methods.chats.Chats.migrate`
27 |
28 | What's the difference between AioCAI and PyCAI?
29 | ===============================================
30 |
31 | AioCAI is the asynchronous version, PyCAI is the synchronous version
32 |
33 | Both versions support the same types and methods, just in PyCAI you don't need to use ``asyncio`` and write ``await`` and ``async``
--------------------------------------------------------------------------------
/characterai/pycai/client.py:
--------------------------------------------------------------------------------
1 | from .methods import Methods
2 | from .methods.chat1 import ChatV1
3 | from .methods.chat2 import WSConnect
4 | from .methods.utils import Request
5 |
6 | from curl_cffi.requests import Session
7 |
8 | class pycai(Methods, Request):
9 | chat1 = ChatV1()
10 | connect = WSConnect(start=False)
11 |
12 | class Client(Methods, Request):
13 | """CharacterAI client
14 |
15 | Args:
16 | token (``str``):
17 | Account auth token
18 |
19 | identifier (``str``):
20 | Which browser version to impersonate in the session
21 |
22 | **kwargs (``Any``):
23 | Supports all arguments from curl_cffi `Session `_
24 |
25 | """
26 | def __init__(
27 | self, token: str = None,
28 | identifier: str ='chrome120',
29 | **kwargs
30 | ):
31 | self.token = token
32 | self.session = Session(
33 | impersonate=identifier,
34 | headers={
35 | 'Authorization': f'Token {token}'
36 | },
37 | **kwargs
38 | )
39 |
40 | self.chat1 = ChatV1(self.session, token)
41 | self.connect = WSConnect(token, start=False)
42 |
43 | def __enter__(self):
44 | return self
45 |
46 | def __exit__(self, *args):
47 | self.session.close()
48 |
49 | def close(self):
50 | """If you won't be using the client in the future, please close it"""
51 | self.session.close()
--------------------------------------------------------------------------------
/characterai/pycai/methods/recent.py:
--------------------------------------------------------------------------------
1 | from ...types import character, recent
2 | from .utils import caimethod, validate
3 |
4 | class Recent:
5 | @caimethod
6 | def get_recent_chats(self, *, token: str = None):
7 | """Recent characters chatted with
8 |
9 | EXAMPLE::
10 |
11 | await client.get_recent_chats()
12 |
13 | Returns:
14 | List of :obj:`~characterai.types.character.CharShort`
15 | """
16 | data = self.request(
17 | 'chat/characters/recent/',
18 | token=token
19 | )
20 |
21 | return validate(
22 | character.CharShort,
23 | data['characters']
24 | )
25 |
26 | @caimethod
27 | def get_recent_rooms(self, *, token: str = None):
28 | """Recent rooms
29 |
30 | EXAMPLE::
31 |
32 | await client.get_recent_rooms()
33 |
34 | Returns:
35 | List of :obj:`~characterai.types.recent.Room`
36 | """
37 | data = self.request(
38 | 'chat/rooms/recent/',
39 | token=token
40 | )
41 |
42 | return validate(
43 | recent.Room, data['rooms']
44 | )
45 |
46 | @caimethod
47 | def get_recent(self, *, token: str = None):
48 | """Recent chats
49 |
50 | EXAMPLE::
51 |
52 | await client.get_recent()
53 |
54 | Returns:
55 | List of :obj:`~characterai.types.recent.Chat`
56 | """
57 | data = self.request(
58 | 'chats/recent/', neo=True,
59 | token=token
60 | )
61 |
62 | return validate(
63 | recent.Chat, data['chats']
64 | )
--------------------------------------------------------------------------------
/characterai/aiocai/methods/recent.py:
--------------------------------------------------------------------------------
1 | from ...types import character, recent
2 | from .utils import caimethod, validate
3 |
4 | class Recent:
5 | @caimethod
6 | async def get_recent_chats(self, *, token: str = None):
7 | """Recent characters chatted with
8 |
9 | EXAMPLE::
10 |
11 | await client.get_recent_chats()
12 |
13 | Returns:
14 | List of :obj:`~characterai.types.character.CharShort`
15 | """
16 | data = await self.request(
17 | 'chat/characters/recent/',
18 | token=token
19 | )
20 |
21 | return validate(
22 | character.CharShort,
23 | data['characters']
24 | )
25 |
26 | @caimethod
27 | async def get_recent_rooms(self, *, token: str = None):
28 | """Recent rooms
29 |
30 | EXAMPLE::
31 |
32 | await client.get_recent_rooms()
33 |
34 | Returns:
35 | List of :obj:`~characterai.types.recent.Room`
36 | """
37 | data = await self.request(
38 | 'chat/rooms/recent/',
39 | token=token
40 | )
41 |
42 | return validate(
43 | recent.Room, data['rooms']
44 | )
45 |
46 | @caimethod
47 | async def get_recent(self, *, token: str = None):
48 | """Recent chats
49 |
50 | EXAMPLE::
51 |
52 | await client.get_recent()
53 |
54 | Returns:
55 | List of :obj:`~characterai.types.recent.Chat`
56 | """
57 | data = await self.request(
58 | 'chats/recent/', neo=True,
59 | token=token
60 | )
61 |
62 | return validate(
63 | recent.Chat, data['chats']
64 | )
--------------------------------------------------------------------------------
/characterai/pycai/methods/chats.py:
--------------------------------------------------------------------------------
1 | from .utils import caimethod, validate
2 | from ...types import other
3 |
4 | class Chats:
5 | @caimethod
6 | def search(
7 | self, query: str, *, token: str = None
8 | ):
9 | """Search for characters by query
10 |
11 | EXAMPLE::
12 |
13 | await client.search('QUERY')
14 |
15 | Args:
16 | query (``str``):
17 | Query text
18 |
19 | Returns:
20 | List of :obj:`~characterai.types.other.QueryChar`
21 | """
22 | data = self.request(
23 | f'chat/characters/search/?query={query}'
24 | )
25 |
26 | return validate(
27 | other.QueryChar, data['characters']
28 | )
29 |
30 | @caimethod
31 | def create_room(
32 | self, name: str, chars: list,
33 | topic: str = '', token: str = None
34 | ) -> str:
35 | """Creating a room with a characters
36 |
37 | EXAMPLE::
38 |
39 | chars = [
40 | {
41 | 'value': 'CHAR_ID'
42 | 'label': 'CHAR_NAME',
43 | }
44 | ]
45 | await client.create_room('NAME', chars)
46 |
47 | Args:
48 | name (``str``):
49 | Room name
50 |
51 | chars (``list``):
52 | The characters in the room
53 |
54 | topic (``str``):
55 | Room theme description
56 |
57 | Returns:
58 | Room ID (``str``)
59 | """
60 | data = self.request(
61 | 'chat/room/create/',
62 | token=token, data={
63 | 'characters': chars,
64 | 'name': name,
65 | 'topic': topic,
66 | 'visibility': 'PRIVATE'
67 | }
68 | )
69 |
70 | return data['room']['external_id']
--------------------------------------------------------------------------------
/characterai/aiocai/client.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import sys
3 |
4 | from .methods import Methods
5 | from .methods.chat1 import ChatV1
6 | from .methods.chat2 import WSConnect
7 | from .methods.utils import Request
8 |
9 | from curl_cffi.requests import AsyncSession
10 |
11 | if sys.platform == 'win32':
12 | asyncio.set_event_loop_policy(
13 | asyncio.WindowsSelectorEventLoopPolicy()
14 | )
15 |
16 | class aiocai(Methods, Request):
17 | chat1 = ChatV1()
18 | connect = WSConnect(start=False)
19 |
20 | class Client(Methods, Request):
21 | """CharacterAI client
22 |
23 | Args:
24 | token (``str``):
25 | Account auth token
26 |
27 | identifier (``str``):
28 | Which browser version to impersonate in the session
29 |
30 | **kwargs (``Any``):
31 | Supports all arguments from curl_cffi `Session `_
32 |
33 | """
34 | def __init__(
35 | self, token: str = None,
36 | identifier: str ='chrome120',
37 | **kwargs
38 | ):
39 | self.token = token
40 | self.session = AsyncSession(
41 | impersonate=identifier,
42 | headers={
43 | 'Authorization': f'Token {token}'
44 | },
45 | **kwargs
46 | )
47 |
48 | self.chat1 = ChatV1(self.session, token)
49 | self.connect = WSConnect(token, start=False)
50 |
51 | async def __aenter__(self):
52 | return self
53 |
54 | async def __aexit__(self, *args):
55 | await self.session.close()
56 |
57 | async def close(self):
58 | """If you won't be using the client in the future, please close it"""
59 | await self.session.close()
--------------------------------------------------------------------------------
/characterai/aiocai/methods/chats.py:
--------------------------------------------------------------------------------
1 | from .utils import caimethod, validate
2 | from ...types import other
3 |
4 | class Chats:
5 | @caimethod
6 | async def search(
7 | self, query: str, *, token: str = None
8 | ):
9 | """Search for characters by query
10 |
11 | EXAMPLE::
12 |
13 | await client.search('QUERY')
14 |
15 | Args:
16 | query (``str``):
17 | Query text
18 |
19 | Returns:
20 | List of :obj:`~characterai.types.other.QueryChar`
21 | """
22 | data = await self.request(
23 | f'chat/characters/search/?query={query}'
24 | )
25 |
26 | return validate(
27 | other.QueryChar, data['characters']
28 | )
29 |
30 | @caimethod
31 | async def create_room(
32 | self, name: str, chars: list,
33 | topic: str = '', token: str = None
34 | ) -> str:
35 | """Creating a room with a characters
36 |
37 | EXAMPLE::
38 |
39 | chars = [
40 | {
41 | 'value': 'CHAR_ID'
42 | 'label': 'CHAR_NAME',
43 | }
44 | ]
45 | await client.create_room('NAME', chars)
46 |
47 | Args:
48 | name (``str``):
49 | Room name
50 |
51 | chars (``list``):
52 | The characters in the room
53 |
54 | topic (``str``):
55 | Room theme description
56 |
57 | Returns:
58 | Room ID (``str``)
59 | """
60 | data = await self.request(
61 | 'chat/room/create/',
62 | token=token, data={
63 | 'characters': chars,
64 | 'name': name,
65 | 'topic': topic,
66 | 'visibility': 'PRIVATE'
67 | }
68 | )
69 |
70 | return data['room']['external_id']
--------------------------------------------------------------------------------
/docs/types/index.rst:
--------------------------------------------------------------------------------
1 | Types
2 | =====
3 |
4 | All available server response types. Created with Pydantic, so they should be used as classes
5 |
6 | .. code-block::
7 |
8 | from characterai import aiocai
9 |
10 | user = await aiocai.get_me('TOKEN')
11 |
12 | print(user.username)
13 |
14 | .. warning:
15 | You may have errors due to incorrect typing. Please report it on Github or Discord
16 |
17 | Account
18 | -------
19 |
20 | .. currentmodule:: characterai.types.account
21 |
22 | .. autosummary::
23 | :toctree: view
24 | :nosignatures:
25 |
26 | Profile
27 | Persona
28 | PersonaShort
29 |
30 | Character
31 | ---------
32 |
33 | .. currentmodule:: characterai.types.character
34 |
35 | .. autosummary::
36 | :toctree: view
37 | :nosignatures:
38 |
39 | Character
40 | CharShort
41 | Categories
42 |
43 | Chat V1
44 | -------
45 |
46 | .. currentmodule:: characterai.types.chat1
47 |
48 | .. autosummary::
49 | :toctree: view
50 | :nosignatures:
51 |
52 | Message
53 | UserAccount
54 | User
55 | Participants
56 | Messages
57 | NewChat
58 | ChatHistory
59 | HisMessage
60 | HisMessages
61 | History
62 | Migrate
63 |
64 | Chat V2
65 | -------
66 |
67 | .. currentmodule:: characterai.types.chat2
68 |
69 | .. autosummary::
70 | :toctree: view
71 | :nosignatures:
72 |
73 | Candidate
74 | BotAnswer
75 | TurnData
76 | ChatData
77 | History
78 |
79 |
80 | Recent
81 | ------
82 |
83 | .. currentmodule:: characterai.types.recent
84 |
85 | .. autosummary::
86 | :toctree: view
87 | :nosignatures:
88 |
89 | Room
90 | Chat
91 |
92 | User
93 | ----
94 |
95 | .. currentmodule:: characterai.types.user
96 |
97 | .. autosummary::
98 | :toctree: view
99 | :nosignatures:
100 |
101 | User
102 |
103 |
104 | Other
105 | -----
106 |
107 | .. currentmodule:: characterai.types.other
108 |
109 | .. autosummary::
110 | :toctree: view
111 | :nosignatures:
112 |
113 | QueryChar
114 | Image
115 | Avatar
116 | Voice
--------------------------------------------------------------------------------
/docs/auth.rst:
--------------------------------------------------------------------------------
1 | #############
2 | Authorization
3 | #############
4 |
5 | To work with the library, you need a token. You can get it through authorization by email and as a guest
6 |
7 | .. warning::
8 |
9 | Please do not show your token to anyone. It can only be regenerated if you delete your account and create a new one
10 |
11 | Log In Via Email
12 | ================
13 |
14 | This code asks the user for their email address and sends them a confirmation link. The user then enters the link they received in their email and is given a token for their account
15 |
16 | .. code-block::
17 |
18 | from characterai import aiocai, sendCode, authUser
19 | import asyncio
20 |
21 | async def main():
22 | email = input('YOUR EMAIL: ')
23 |
24 | code = sendCode(email)
25 |
26 | link = input('CODE IN MAIL: ')
27 |
28 | token = authUser(link, email)
29 |
30 | print(f'YOUR TOKEN: {token}')
31 |
32 | asyncio.run(main())
33 |
34 | Log In As a Guest
35 | =================
36 |
37 | Guests can't chat
38 |
39 | .. code-block::
40 |
41 | from characterai import aiocai, authGuest
42 | import asyncio
43 |
44 | async def main():
45 | client = aiocai.Client(authGuest())
46 |
47 | info = await client.get_me(token=token)
48 |
49 | print(info)
50 |
51 | asyncio.run(main())
52 |
53 | After logging in, you can find account information by using the :obj:`~characterai.aiogram.methods.account.Account.get_me` method. If you are a guest, no information will be displayed
54 |
55 | Alternative Auth Methods
56 | ========================
57 |
58 | In addition to creating the ``Client`` class, you have the flexibility to work with both the class and individual functions.
59 |
60 | Context Manager
61 | ---------------
62 |
63 | When you create a ``Client`` class, its session will always remain active. If you don't use the library in your code later, you will need to manually close the session using the ``close()`` method
64 |
65 | And with this option, you won't need to manually close the session, as it will automatically close when the code finishes executing or an error occurs
66 |
67 | .. code-block::
68 |
69 | async with aiocai.Client('TOKEN') as client:
70 | print(await client.get_me())
71 |
72 | Via Function
73 | ------------
74 |
75 | Sometimes you only need to use 1 function from the library or another token, in that case, you can use the function without creating a new client and just specify the token
76 |
77 | .. code-block::
78 |
79 | await aiocai.get_me(token='TOKEN')
80 |
--------------------------------------------------------------------------------
/docs/methods/index.rst:
--------------------------------------------------------------------------------
1 | Methods
2 | =======
3 |
4 | All available API methods. All methods listed here are bound to the ``Client`` instance, except chat1, it is called through the class
5 |
6 | .. code-block::
7 |
8 | from characterai import pycai
9 |
10 | client = pycai.Client('TOKEN')
11 |
12 | client.get_me()
13 |
14 | client.chat1.get_history('CHAR_ID')
15 |
16 |
17 | Character
18 | ---------
19 |
20 | .. currentmodule:: characterai.aiocai.methods.characters.Characters
21 |
22 | .. autosummary::
23 | :toctree: view
24 | :nosignatures:
25 |
26 | get_char
27 | upvoted
28 | get_category
29 | get_recommended
30 | get_trending
31 | create_char
32 | update_char
33 |
34 |
35 | Account
36 | -------
37 |
38 | .. currentmodule:: characterai.aiocai.methods.account.Account
39 |
40 | .. autosummary::
41 | :toctree: view
42 | :nosignatures:
43 |
44 | get_me
45 | edit_account
46 | get_personas
47 | create_persona
48 | get_persona
49 | delete_persona
50 | followers
51 | following
52 | characters
53 |
54 |
55 | Chat V1
56 | -------
57 |
58 | .. currentmodule:: characterai.aiocai.methods.chat1.ChatV1
59 |
60 | .. autosummary::
61 | :toctree: view
62 | :nosignatures:
63 |
64 | get_histories
65 | get_history
66 | get_chat
67 | new_chat
68 | next_message
69 | delete_message
70 | send_message
71 | migrate
72 |
73 |
74 | Chat V2
75 | -------
76 |
77 | .. currentmodule:: characterai.aiocai.methods.chat2.ChatV2
78 |
79 | .. autosummary::
80 | :toctree: view
81 | :nosignatures:
82 |
83 | get_histories
84 | get_history
85 | get_chat
86 | new_chat
87 | next_message
88 | delete_message
89 | send_message
90 | edit_message
91 | pin
92 |
93 |
94 | Chats
95 | -----
96 |
97 | .. currentmodule:: characterai.aiocai.methods.chats.Chats
98 |
99 | .. autosummary::
100 | :toctree: view
101 | :nosignatures:
102 |
103 | search
104 | create_room
105 |
106 | Recent
107 | ------
108 |
109 | .. currentmodule:: characterai.aiocai.methods.recent.Recent
110 |
111 | .. autosummary::
112 | :toctree: view
113 | :nosignatures:
114 |
115 | get_recent_chats
116 | get_recent_rooms
117 | get_recent
118 |
119 | Users
120 | -----
121 |
122 | .. currentmodule:: characterai.aiocai.methods.users.Users
123 |
124 | .. autosummary::
125 | :toctree: view
126 | :nosignatures:
127 |
128 | get_user
129 |
130 | Other
131 | -----
132 |
133 | .. currentmodule:: characterai.aiocai.methods.other.Other
134 |
135 | .. autosummary::
136 | :toctree: view
137 | :nosignatures:
138 |
139 | get_voices
140 | create_image
141 | upload_image
142 | ping
--------------------------------------------------------------------------------
/characterai/pycai/methods/other.py:
--------------------------------------------------------------------------------
1 | from .utils import caimethod, validate
2 | from ...types import other
3 |
4 | from curl_cffi import CurlMime
5 |
6 | class Other:
7 | @caimethod
8 | def create_image(
9 | self, promt: str, *, token: str = None
10 | ):
11 | """Image generation by description
12 |
13 | EXAMPLE::
14 |
15 | await client.create_image('PROMT')
16 |
17 | Args:
18 | promt (``str``):
19 | Image description
20 |
21 | Returns:
22 | :obj:`~characterai.types.other.Image`
23 | """
24 | data = self.request(
25 | 'chat/generate-image/',
26 | token=token, data={
27 | 'image_description': promt
28 | }
29 | )
30 |
31 | return other.Image(
32 | url=data['image_rel_path'],
33 | type='CREATED'
34 | )
35 |
36 | @caimethod
37 | def upload_image(
38 | self, file: str, *, token: str = None
39 | ):
40 | """Uploading an image to the server
41 |
42 | EXAMPLE::
43 |
44 | await client.upload_image('FILENAME.PNG')
45 |
46 | Args:
47 | file (``str``):
48 | File path
49 |
50 | Returns:
51 | :obj:`~characterai.types.other.Image`
52 | """
53 | mp = CurlMime()
54 | mp.addpart(
55 | name='image',
56 | content_type='image/png',
57 | filename=file,
58 | local_path=file,
59 | )
60 |
61 | data = self.request(
62 | 'chat/upload-image/',
63 | data={}, multipart=mp,
64 | token=token
65 | )
66 |
67 | return other.Image(
68 | url=data['value']
69 | )
70 |
71 | @caimethod
72 | def ping(
73 | self, *, token: str = None
74 | ) -> bool:
75 | """Performance check
76 |
77 | EXAMPLE::
78 |
79 | await client.ping()
80 |
81 | Returns:
82 | ``bool``
83 | """
84 | data = self.request(
85 | 'ping/', neo=True,
86 | token=token
87 | )
88 |
89 | if data['status'] == 'pong':
90 | return True
91 |
92 | return False
93 |
94 | @caimethod
95 | def get_voices(
96 | self, *, token: str = None
97 | ) -> list:
98 | """List of available ready-made voices
99 |
100 | EXAMPLE::
101 |
102 | await client.get_voices()
103 |
104 | Returns:
105 | List of :obj:`~characterai.types.other.Voice`
106 | """
107 | data = self.request(
108 | 'chat/character/voices/',
109 | token=token
110 | )
111 |
112 | return validate(
113 | other.Voice, data['voices']
114 | )
--------------------------------------------------------------------------------
/docs/_static/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | --color-sidebar-item-background--hover: var(--color-background-hover);
3 | }
4 |
5 | .sidebar-drawer {
6 | background: #131316;
7 | }
8 |
9 | .main {
10 | background: #18181B;
11 | }
12 |
13 | .content-icon-container {
14 | display: none;
15 | }
16 |
17 | .toc-drawer {
18 | background: #18181B;
19 | }
20 |
21 | .sidebar-search-container {
22 | background: #131316;
23 | }
24 |
25 | h2 {
26 | font-size: 1.5em;
27 | }
28 |
29 | .sidebar-tree .caption, .sidebar-tree :not(.caption)>.caption-text {
30 | font-size: 0.9em;
31 | font-weight: 800;
32 | }
33 |
34 | blockquote {
35 | display: none;
36 | }
37 |
38 | table.docutils {
39 | border-radius: 0.5em;
40 | border: 1px solid var(--color-table-border);
41 | width: 100%;
42 | }
43 |
44 | article table.align-default {
45 | overflow: hidden;
46 | }
47 |
48 | a {
49 | text-decoration: none;
50 | text-decoration-color: transparent;
51 | }
52 |
53 | .social-icon {
54 | padding-left: 8px;
55 | }
56 |
57 | .sidebar-brand-text {
58 | display: none;
59 | }
60 |
61 | .sidebar-search {
62 | border: none;
63 | border-radius: 10px;
64 | border: none;
65 | background: #202024;
66 | margin: 0px 8px;
67 | }
68 |
69 | .sidebar-search-container::before {
70 | z-index: 11;
71 | mask-image: url('search.svg');
72 | }
73 |
74 | .sidebar-scroll, .toc-scroll, article[role="main"] * {
75 | scrollbar-width: none;
76 | }
77 |
78 | .sidebar-brand {
79 | margin-right: 0px 8px;
80 | }
81 |
82 | .sidebar-tree .current > .reference {
83 | border-radius: 20px 0px 0px 20px;
84 | }
85 |
86 | .sidebar-tree .reference:hover {
87 | color: var(--color-sidebar-link-text--top-level);
88 | border-radius: 20px 0px 0px 20px;
89 | }
90 |
91 | .tab-content {
92 | box-shadow: none;
93 | }
94 |
95 | .tab-set > label {
96 | padding: 1em 1em 0.5em;
97 | }
98 |
99 | .tab-label {
100 | background: #202020;
101 | border-radius: 10px 10px 0px 0px;
102 | }
103 |
104 | .theme-toggle {
105 | display: none;
106 | }
107 |
108 | .tab-set > input:checked + label {
109 | -webkit-tap-highlight-color: transparent;
110 | color: var(--color-content-foreground);
111 | background: #202020;
112 | }
113 |
114 | .tab-set > input:checked + label:hover {
115 | color: var(--color-content-foreground);
116 | background: #202020;
117 | }
118 |
119 | .tab-set > label {
120 | padding: 0.5em 1em;
121 | margin-left: 0px;
122 | border-bottom: none;
123 | padding: none;
124 | margin-left: none;
125 | background: none;
126 | }
127 |
128 | .tab-content > [class^="highlight-"]:first-child .highlight {
129 | border-top-right-radius: 10px;
130 | }
131 |
132 | .highlight {
133 | border-radius: 10px;
134 | }
135 |
136 | .mobile-header.scrolled {
137 | border-bottom: 1;
138 | box-shadow: none;
139 | }
--------------------------------------------------------------------------------
/characterai/aiocai/methods/other.py:
--------------------------------------------------------------------------------
1 | from .utils import caimethod, validate
2 | from ...types import other
3 |
4 | from curl_cffi import CurlMime
5 |
6 | class Other:
7 | @caimethod
8 | async def create_image(
9 | self, promt: str, *, token: str = None
10 | ):
11 | """Image generation by description
12 |
13 | EXAMPLE::
14 |
15 | await client.create_image('PROMT')
16 |
17 | Args:
18 | promt (``str``):
19 | Image description
20 |
21 | Returns:
22 | :obj:`~characterai.types.other.Image`
23 | """
24 | data = await self.request(
25 | 'chat/generate-image/',
26 | token=token, data={
27 | 'image_description': promt
28 | }
29 | )
30 |
31 | return other.Image(
32 | url=data['image_rel_path'],
33 | type='CREATED'
34 | )
35 |
36 | @caimethod
37 | async def upload_image(
38 | self, file: str, *, token: str = None
39 | ):
40 | """Uploading an image to the server
41 |
42 | EXAMPLE::
43 |
44 | await client.upload_image('FILENAME.PNG')
45 |
46 | Args:
47 | file (``str``):
48 | File path
49 |
50 | Returns:
51 | :obj:`~characterai.types.other.Image`
52 | """
53 | mp = CurlMime()
54 | mp.addpart(
55 | name='image',
56 | content_type='image/png',
57 | filename=file,
58 | local_path=file,
59 | )
60 |
61 | data = await self.request(
62 | 'chat/upload-image/',
63 | data={}, multipart=mp,
64 | token=token
65 | )
66 |
67 | return other.Image(
68 | url=data['value']
69 | )
70 |
71 | @caimethod
72 | async def ping(
73 | self, *, token: str = None
74 | ) -> bool:
75 | """Performance check
76 |
77 | EXAMPLE::
78 |
79 | await client.ping()
80 |
81 | Returns:
82 | ``bool``
83 | """
84 | data = await self.request(
85 | 'ping/', neo=True,
86 | token=token
87 | )
88 |
89 | if data['status'] == 'pong':
90 | return True
91 |
92 | return False
93 |
94 | @caimethod
95 | async def get_voices(
96 | self, *, token: str = None
97 | ) -> list:
98 | """List of available ready-made voices
99 |
100 | EXAMPLE::
101 |
102 | await client.get_voices()
103 |
104 | Returns:
105 | List of :obj:`~characterai.types.other.Voice`
106 | """
107 | data = await self.request(
108 | 'chat/character/voices/',
109 | token=token
110 | )
111 |
112 | return validate(
113 | other.Voice, data['voices']
114 | )
--------------------------------------------------------------------------------
/characterai/types/recent.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional, Any
2 | from pydantic import BaseModel, Field
3 | from datetime import datetime
4 |
5 | from .other import Avatar
6 |
7 | class Participant(BaseModel, Avatar):
8 | name: str
9 | avatar_file_name: str
10 |
11 | class Room(BaseModel):
12 | """Информация о комнатах с персонажами
13 |
14 | Parameters:
15 | external_id (``str``):
16 | Chat ID
17 |
18 | title (``str``):
19 | Room name
20 |
21 | description (``str``):
22 | Room description
23 |
24 | participants (List of ``Participant``):
25 | Characters in the room [name, avatar_file_name]
26 |
27 | img_gen_enabled (``bool``):
28 | Will it be possible to generate pictures
29 | """
30 | external_id: str
31 | title: str
32 | description: str
33 | participants: List[Participant]
34 | img_gen_enabled: bool
35 |
36 | class Name(BaseModel):
37 | ko: Optional[str] = None
38 | ru: Optional[str] = None
39 | ja_JP: Optional[str] = None
40 | zh_CN: Optional[str] = None
41 |
42 | class CharacterTranslations(BaseModel):
43 | name: Name
44 |
45 | class Chat(BaseModel, Avatar):
46 | """Информация о чате
47 |
48 | Parameters:
49 | chat_id (``str``):
50 | Chat ID
51 |
52 | create_time (:py:obj:`~datetime`):
53 | Chat creation time
54 |
55 | creator_id (``str``):
56 | ID of the user who created the chat
57 |
58 | character_id (``str``):
59 | Character ID
60 |
61 | state (``str``):
62 | Chat state
63 |
64 | type (``str``):
65 | Chat type
66 |
67 | visibility (``str``):
68 | Chat visibility (everyone, you or from link)
69 |
70 | character_name (``str``):
71 | Character name
72 |
73 | character_visibility (``str``):
74 | Character visibility (everyone, you or from link)
75 |
76 | character_translations (``CharacterTranslations``):
77 | Translations of character information
78 | [ko, ru, ja_JP, zh_CN]
79 |
80 | default_voice_id (``str``, *optional*):
81 | Default voice ID
82 |
83 | avatar_file_name (``str``):
84 | Path to the avatar on the server
85 |
86 | avatar (:obj:`~characterai.types.other.Avatar`):
87 | Avatar info
88 | """
89 | chat_id: str
90 | create_time: datetime
91 | creator_id: str
92 | character_id: str
93 | state: str
94 | type: str
95 | visibility: str
96 | character_name: str
97 | character_visibility: str
98 | character_translations: CharacterTranslations
99 | default_voice_id: Optional[str] = None
100 | avatar_file_name: str = Field(
101 | validation_alias='character_avatar_uri'
102 | )
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 |
2 | ######
3 | AioCAI
4 | ######
5 |
6 |
7 | .. image:: https://img.shields.io/pepy/dt/characterai?style=flat-square
8 | :target: https://pypi.org/project/characterai
9 | :alt: Total Downloads
10 |
11 | .. image:: https://img.shields.io/pypi/l/characterai?style=flat-square
12 | :target: https://opensource.org/licenses/MIT
13 | :alt: MIT License
14 |
15 | .. image:: https://img.shields.io/github/stars/kramcat/characterai?style=flat-square
16 | :target: https://github.com/kramcat/characterai
17 | :alt: Discord
18 |
19 | .. image:: https://img.shields.io/discord/1120066151515422772?style=flat-square
20 | :target: https://discord.com/invite/ZHJe3tXQkf
21 | :alt: Stars
22 |
23 |
24 | Welcome to the documentation for a synchronous/asynchronous unofficial library for CharacterAI using `curl_cffi `_
25 |
26 |
27 | 💻 Installation
28 | ---------------
29 |
30 | .. code-block:: bash
31 |
32 | pip install -U characterai
33 |
34 | .. warning::
35 |
36 | This version of the library is in alpha version, there may be bugs and errors. The library was developed without the participation of Character AI developers or their knowledge. To work with the library you need to know how to work with `asyncio `_
37 |
38 |
39 | 🔥 Features
40 | ===========
41 |
42 | - Supports logging in via email or as a guest
43 | - Does not use web browsers like: Pypeeter, Playwright, etc.
44 | - Supports uploading/downloading pictures
45 | - Has detailed documentation
46 | - Uses Pydantic
47 | - Asynchronous
48 |
49 |
50 | 📙 Simple Example
51 | -----------------
52 | .. literalinclude::
53 | ../examples/async/chat2.py
54 |
55 |
56 | 👥 Community
57 | --------------
58 | If you have any questions about our library or would like to discuss CharacterAI, LLM, or Neural Networks topics, please visit our Discord channel
59 |
60 | `discord.gg/ZHJe3tXQkf `_
61 |
62 |
63 | 📝 TODO List
64 | ------------
65 |
66 | - Character voice work
67 | - Community tab support
68 | - Add logging
69 | - Group chat support
70 | - Improved work with uploading pictures
71 |
72 |
73 | 💵 Support
74 | ----------
75 | | TON - ``EQCSMftGsV4iU2b9H7tuEURIwpcWpF_maw4yknMkVxDPKs6v``
76 | | BTC - ``bc1qghtyl43jd6xr66wwtrxkpe04sglqlwgcp04yl9``
77 | | ETH - ``0x1489B0DDCE07C029040331e4c66F5aA94D7B4d4e``
78 | | USDT (TRC20) - ``TJpvALv9YiL2khFBb7xfWrUDpvL5nYFs8u``
79 |
80 | You can contact me via `Telegram `_ or `Discord `_ if you need help with parsing services or want to write a library. I can also create bots and userbots for Telegram
81 |
82 |
83 | .. toctree::
84 | :hidden:
85 | :maxdepth: 3
86 | :caption: Getting Started
87 |
88 | starting
89 | auth
90 |
91 | .. toctree::
92 | :hidden:
93 | :maxdepth: 3
94 | :caption: Working with API
95 |
96 | client
97 | methods/index
98 | types/index
99 | errors
100 |
101 | .. toctree::
102 | :hidden:
103 | :maxdepth: 3
104 | :caption: More Info
105 |
106 | support
107 | qna
108 | changelog
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://pepy.tech/project/characterai)
4 | [](https://opensource.org/licenses/MIT)
5 | [](https://github.com/kramcat/characterai)
6 | [](https://discord.com/invite/ZHJe3tXQkf)
7 |
8 | Welcome to the documentation for a synchronous/asynchronous unofficial library for CharacterAI using [curl_cff](https://github.com/yifeikong/curl_cffi)
9 |
10 | ### 💻 Installation
11 | ```bash
12 | pip install git+https://github.com/kramcat/CharacterAI.git
13 | ```
14 |
15 | ### ⚠️ Warning
16 | This version of the library is in alpha version, there may be bugs and errors. The library was developed without the participation of Character AI developers or their knowledge. To work with the library you need to know how to work with [asyncio](https://docs.python.org/3/library/asyncio.html)
17 |
18 | ### 🔥 Features
19 | - Supports logging in via email or as a guest
20 | - Does not use web browsers like: Pypeeter, Playwright, etc.
21 | - Supports uploading/downloading pictures
22 | - Has detailed documentation
23 | - Uses Pydantic
24 | - Asynchronous
25 |
26 | ### 📙 Simple Example
27 | You need an account to use the library. To find out your token, you must [log in through the library](https://docs.kram.cat/auth.html)
28 | ```python
29 | from characterai import aiocai
30 | import asyncio
31 |
32 | async def main():
33 | char = input('CHAR ID: ')
34 |
35 | client = aiocai.Client('TOKEN')
36 |
37 | me = await client.get_me()
38 |
39 | async with await client.connect() as chat:
40 | new, answer = await chat.new_chat(
41 | char, me.id
42 | )
43 |
44 | print(f'{answer.name}: {answer.text}')
45 |
46 | while True:
47 | text = input('YOU: ')
48 |
49 | message = await chat.send_message(
50 | char, new.chat_id, text
51 | )
52 |
53 | print(f'{message.name}: {message.text}')
54 |
55 | asyncio.run(main())
56 | ```
57 |
58 | ### 📚 Documentation
59 | The documentation contains all the detailed information about functions and types. If you have any questions, first of all read whether there is an answer in the documentation
60 |
61 | **[docs.kram.cat](https://docs.kram.cat)**
62 |
63 | ### 👥 Community
64 | If you have any questions about our library or would like to discuss CharacterAI, LLM, or Neural Networks topics, please visit our Discord channel
65 |
66 | **[discord.gg/ZHJe3tXQkf](https://discord.com/invite/ZHJe3tXQkf)**
67 |
68 | ### 📝 TODO List
69 | - [ ] Character voice work
70 | - [ ] Community tab support
71 | - [ ] Add logging
72 | - [ ] Group chat support
73 | - [ ] Improved work with uploading pictures
74 |
75 | ### 💵 Support
76 | TON - `EQCSMftGsV4iU2b9H7tuEURIwpcWpF_maw4yknMkVxDPKs6v`
77 |
BTC - `bc1qghtyl43jd6xr66wwtrxkpe04sglqlwgcp04yl9`
78 |
ETH - `0x1489B0DDCE07C029040331e4c66F5aA94D7B4d4e`
79 |
USDT (TRC20) - `TJpvALv9YiL2khFBb7xfWrUDpvL5nYFs8u`
80 |
81 | You can contact me via [Telegram](https://t.me/kramcat) or [Discord](https://discordapp.com/users/480976972277874690) if you need help with parsing services or want to write a library. I can also create bots and userbots for Telegram
82 |
--------------------------------------------------------------------------------
/docs/changelog.rst:
--------------------------------------------------------------------------------
1 | #########
2 | Changelog
3 | #########
4 |
5 | 1.0.0a1
6 | =======
7 |
8 | - Adding authorization via mail and as a guest
9 | - Fix old bugs related to authorization by token and chat2
10 | - Added ability to download and upload pictures
11 | - Improved performance by switching to `curl_cffi`
12 | - Adding typing via Pydantic
13 | - Added new features: modify and pin character messages
14 | - Moved documentation to Sphinx Furo
15 | - PyAsyncCAI name changed to AioCAI
16 |
17 | 0.8.0 - Much faster, chat2, Fixes
18 | =================================
19 |
20 | - There is no Playwright anymore, another library is used that does not load the system (!!!)
21 | - Added chat2 support (new chats with a different design)
22 | - Deleted ``client.upload_image()``, ``client.start()``
23 | - chat2 is only available in the asynchronous version
24 | - Fixes and improvements
25 |
26 |
27 | 0.7.0 - Managing Posts and Characters, Upload Images, Create Rooms
28 | ==================================================================
29 |
30 | - Small fixes by KubaPro010
31 | - Private variables
32 | - ``start()`` for PyCAI
33 | - Added plus for ``start()``
34 | - Added timeout for ``start()``
35 | - Added ``ping()``, ``upload_image('PATH')``, ``user.update('USERNAME')``, ``character.create(...)``, ``character.update(...)``, ``character.voices()``, ``character.create_room(...)``
36 | - Added the post class
37 | - ``post.get_post('POST_ID')``
38 | - ``post.my_posts()``
39 | - ``post.get_posts('USERNAME')``
40 | - ``post.upvote('POST_ID')``
41 | - ``post.undo_upvote('POST_ID')``
42 | - ``post.send_comment('POST_ID', 'TEXT')``
43 | - ``post.delete_comment('MESSAGE_ID', 'POST_ID')``
44 | - ``post.create('HISTORY_ID', 'TITLE')``
45 | - ``post.delete('POST_ID')``
46 | - Some fixes for parameters, check documentation
47 | - Some optimization
48 | - Added kwargs for functions
49 |
50 |
51 | 0.6.0 - Documentation, New Functions, Fixes
52 | ===========================================
53 |
54 | - Name of pyCAI changed to PyCAI
55 | - Name of pyAsyncCAI changed to PyAsyncCAI
56 | - Written documentation
57 | - Fix error 500
58 | - Added more errors
59 | - Added ``chat.rate('CHAR', RATE)``
60 | - Added ``chat.next_message('CHAR')``
61 | - Added ``chat.get_chat('CHAR')``
62 | - Added ``chat.delete_message('HISTORY_ID', 'UUIDS_TO_DELETE')``
63 | - Fixed showing filtered messages
64 | - Code changed to PEP8
65 |
66 |
67 | 0.5.0 - POST methods, New chat.get_histories()
68 | ===============================================
69 |
70 | - Now some functions work via POST requests, it's faster and more reliable
71 | - Fixed ``chat.new_chat()``
72 | - ``chat.send_message()`` was rewritten
73 | - Instead of an error, it returns an error page
74 | - This function can show a message even if it is filtered
75 | - Fixed bugs with JSON
76 | - New ``chat.get_histories('CHAR')``
77 | - This function returns all the stories with the character
78 | - Find users by nickname with ``user.info('NICKNAME')``
79 | - This function returns information about the user
80 |
81 |
82 | 0.4.0 - New chat.send_message(), Async, Fixes
83 | =============================================
84 |
85 | - Add user.get_history in async
86 | - Fix ``chat.new_chat()``
87 | - New ``chat.send_message()``
88 | - New chat.get_history()
89 | - ``character.get_info()`` rename to ``character.info()`` and rewritten
90 | - Add character.search()
91 | - Add ``user.recent()``
92 | - Fix small bugs
93 |
94 |
95 | 0.3.0 - Fixed Functions, New Parameters
96 | =======================================
97 |
98 | - Fix broken functions
99 | - Remove ``user.get_history`` in async (I'll add in next version)
100 | - Add token parameter on all functions for custom token
101 | - Add wait parameter on all functions for waiting a responce when site is overloaded
102 | - Add headless parameter on pyCAI for browser (I don't know why, but suddenly you need)
103 | - Other changes (I don't remember which ones)
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 | sys.path.insert(0, os.path.abspath('../'))
3 |
4 | import characterai
5 |
6 | project = 'AioCAI'
7 | title = '1.0.0a1'
8 | copyright = ' 2023 – 2024, kramcat'
9 | author = 'kramcat'
10 | release = '1.0.0a1'
11 | language = 'en'
12 |
13 | extensions = [
14 | 'sphinx.ext.autodoc',
15 | 'sphinx.ext.autosummary',
16 | 'sphinx.ext.napoleon',
17 | 'sphinx_copybutton',
18 | 'sphinx_inline_tabs'
19 | ]
20 |
21 | autosummary_generate = True
22 |
23 | html_logo = 'images/title.png'
24 | html_title = 'AioCAI 1.0.0a1'
25 | highligh_language = 'python3'
26 | html_theme = 'furo'
27 | html_show_sphinx = False
28 |
29 | templates_path = ['_templates']
30 | html_static_path = ['_static']
31 | html_css_files = ['style.css']
32 | html_js_files = ['theme.js']
33 |
34 | html_theme_options = {
35 | 'footer_icons': [
36 | {
37 | 'name': 'GitHub',
38 | 'class': 'social-icon',
39 | 'url': 'https://github.com/kramcat/characterai',
40 | 'html': '''
41 |
44 | ''',
45 | },
46 | {
47 | 'name': 'Telegram',
48 | 'class': 'social-icon',
49 | 'url': 'https://t.me/kramdev',
50 | 'html': '''
51 |
54 | ''',
55 | },
56 | {
57 | 'name': 'Discord',
58 | 'class': 'social-icon',
59 | 'url': 'https://discord.gg/ZHJe3tXQkf',
60 | 'html': '''
61 |
64 | ''',
65 | }
66 | ],
67 | }
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
--------------------------------------------------------------------------------
/characterai/auth.py:
--------------------------------------------------------------------------------
1 | from .errors import AuthError, ServerError
2 |
3 | from curl_cffi.requests import Session
4 | import uuid
5 |
6 | URL = 'https://beta.character.ai'
7 |
8 | def sendCode(email: str) -> bool:
9 | with Session(impersonate='chrome120') as s:
10 | r = s.post(
11 | 'https://identitytoolkit.googleapis.com'
12 | '/v1/accounts:sendOobCode?key='
13 | 'AIzaSyAbLy_s6hJqVNr2ZN0UHHiCbJX1X8smTws',
14 | json={
15 | 'requestType': 'EMAIL_SIGNIN',
16 | 'email': email,
17 | 'clientType': 'CLIENT_TYPE_WEB',
18 | 'continueUrl': 'https://beta.character.ai',
19 | 'canHandleCodeInApp': True
20 | }
21 | )
22 |
23 | data = r.json()
24 |
25 | try:
26 | if data['email'] == email:
27 | return True
28 | except KeyError:
29 | raise ServerError(data['error']['message'])
30 |
31 | def authUser(link: str, email: str) -> str:
32 | with Session(impersonate='chrome120') as s:
33 | r = s.get(link, allow_redirects=True)
34 |
35 | oobCode = r.url.split('oobCode=')[1].split('&')[0]
36 |
37 | r = s.post(
38 | 'https://identitytoolkit.googleapis.com'
39 | '/v1/accounts:signInWithEmailLink?key='
40 | 'AIzaSyAbLy_s6hJqVNr2ZN0UHHiCbJX1X8smTws',
41 | headers={
42 | # Firebase key for GoogleAuth API
43 | 'X-Firebase-AppCheck': 'eyJraWQiOiJYcEhKU0EiLCJ'
44 | '0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIx'
45 | 'OjQ1ODc5NzcyMDY3NDp3ZWI6YjMzNGNhNDM2MWU5MzRkYWV'
46 | 'iOWQzYiIsImF1ZCI6WyJwcm9qZWN0c1wvNDU4Nzk3NzIwNjc'
47 | '0IiwicHJvamVjdHNcL2NoYXJhY3Rlci1haSJdLCJwcm92aWR'
48 | 'lciI6InJlY2FwdGNoYV9lbnRlcnByaXNlIiwiaXNzIjoiaHR0'
49 | 'cHM6XC9cL2ZpcmViYXNlYXBwY2hlY2suZ29vZ2xlYXBpcy5jb'
50 | '21cLzQ1ODc5NzcyMDY3NCIsImV4cCI6MTcxMTAxNzE2MiwiaWF'
51 | '0IjoxNzEwNDEyMzYyLCJqdGkiOiJkSXlkWVFPZEhnaTRmc2ZGU'
52 | 'DMtWHNZVU0zZG01eFY4R05ncDItOWxCQ2xVIn0.o2g6-5Pl7rj'
53 | 'iKdQ4X9bdOe6tDSVmdODFZUljHDnF5cNCik6masItwpeL3Yh6h'
54 | '78sQKNwuKzCUBFjsvDsEIdu71gW4lAuDxhKxljffX9nRuh8d0j-'
55 | 'ofmwq_4abpA3LdY12gIibvMigf3ncBQiJzu4SVQUKEdO810oUG8'
56 | 'G4RWlQfBIo-PpCO8jhyGZ0sjcklibEObq_4-ynMZnhTuIN_J183'
57 | '-RibxiKMjMTVaCcb1XfPxXi-zFr2NFVhSM1oTWSYmhseQ219ppH'
58 | 'A_-cQQIH6MwC0haHDsAAntjQkjbnG2HhPQrigdbeiXfpMGHAxLR'
59 | 'XXsgaPuEkjYFUPoIfIITgvkj5iJ-33vji2NgmDCpCmpxpx5wTHOC'
60 | '8OEZqSoCyi3mOkJNXTxOHmxvS-5glMrcgoipVJ3Clr-pes3-aI5Y'
61 | 'w7n3kmd4YfsKTadYuE8vyosq_MplEQKolRKj67CSNTsdt2fOsLCW'
62 | 'Nohduup6qJrUroUpN35R9JuUWgSy7Y4MI6NM-bKJ'
63 | },
64 | json={
65 | 'email': email,
66 | 'oobCode': oobCode
67 | }
68 | )
69 |
70 | data = r.json()
71 |
72 | try:
73 | idToken = data['idToken']
74 | except KeyError:
75 | raise AuthError(data['error']['message'])
76 |
77 |
78 | with Session(impersonate='chrome120') as s:
79 | r = s.post(
80 | f'{URL}/dj-rest-auth/google_idp/',
81 | json={
82 | 'id_token': idToken
83 | }
84 | )
85 |
86 | data = r.json()
87 |
88 | try:
89 | return data['key']
90 | except KeyError:
91 | raise AuthError(data['error'])
92 |
93 | def authGuest() -> str:
94 | with Session(impersonate='chrome120') as s:
95 | r = s.post(
96 | f'{URL}/chat/auth/lazy/',
97 | json={
98 | 'lazy_uuid': str(uuid.uuid1())
99 | }
100 | )
101 |
102 | data = r.json()
103 |
104 | try:
105 | return data['token']
106 | except KeyError:
107 | raise AuthError(data['error'])
--------------------------------------------------------------------------------
/docs/starting.rst:
--------------------------------------------------------------------------------
1 | ###########
2 | Quick Start
3 | ###########
4 |
5 | To better understand how the library works, let's take a simple example of a conversation between a user and a character
6 |
7 | I recommend using AioCAI instead of PyCAI when working with asynchronous libraries
8 |
9 | First, you need to create a skeleton for your project. In PyCAI, this involves simply importing the library
10 |
11 | .. tab:: AioCAI
12 |
13 | .. code-block::
14 |
15 | from characterai import aiocai
16 | import asyncio
17 |
18 | async def main():
19 | # YOUR CODE
20 |
21 | asyncio.run(main())
22 |
23 | .. tab:: PyCAI
24 |
25 | .. code-block::
26 |
27 | from characterai import pycai
28 |
29 | # YOUR CODE
30 |
31 | At the beginning of the code, we will ask the user for the character ID
32 |
33 | .. code-block::
34 |
35 | char = input('CHAR: ')
36 |
37 | And now you need to create a class :obj:`~characterai.aiocai.client.aiocai.Client`, which will be the main class you will be working with all the time
38 |
39 | The required argument is `token`, you can find out about the rest on the class page itself
40 |
41 | .. code-block::
42 |
43 | client = aiocai.Client('TOKEN')
44 |
45 |
46 | After that, we collect information about our account through :obj:`~characterai.aiocai.methods.account.Account.get_me`, It's necessary to create a new chat that requires an author ID
47 |
48 | .. tab:: AioCAI
49 |
50 | .. code-block::
51 |
52 | me = await client.get_me()
53 |
54 | .. tab:: PyCAI
55 |
56 | .. code-block::
57 |
58 | me = client.get_me()
59 |
60 | And now, let's move on to the chat. Once upon a time, c.ai had only an old version of the chat that worked via HTTPS. The new version (chat2) works via WebSockets, which means you need to maintain a connection with the server. In Python, this is done using context managers. For more information, please see the following object: `~characterai.aiocai.methods.utils.WSConnect`
61 |
62 | .. tab:: AioCAI
63 |
64 | .. code-block::
65 |
66 | async with await client.connect() as chat:
67 | # YOUR CODE
68 |
69 | .. tab:: PyCAI
70 |
71 | .. code-block::
72 |
73 | with client.connect() as chat:
74 | # YOUR CODE
75 |
76 | Now, we will create a new chat and communicate directly within it. The function will always return 2 variables: information about the new chat and a welcoming message
77 |
78 | After that, we will immediately display the greeting message for the character. If you do not wish to have this message displayed in new chats, you can set ``greeting=False`` in the function, and it will only return ``new``
79 |
80 | .. tab:: AioCAI
81 |
82 | .. code-block::
83 |
84 | new, answer = await chat.new_chat(
85 | char, me.id
86 | )
87 |
88 | .. tab:: PyCAI
89 |
90 | .. code-block::
91 |
92 | new, answer = chat.new_chat(
93 | char, me.id
94 | )
95 |
96 | And in the chat itself, which will run continuously, immediately show the input from a user's message
97 |
98 | .. code-block::
99 |
100 | while True:
101 | text = input('YOU: ')
102 |
103 | # YOUR CODE
104 |
105 | After that, we receive a message and see the character's answer
106 |
107 | .. tab:: AioCAI
108 |
109 | .. code-block::
110 |
111 | message = await chat.send_message(
112 | char, new.chat_id, text
113 | )
114 |
115 | print(f'{message.name}: {message.text}')
116 |
117 | .. tab:: PyCAI
118 |
119 | .. code-block::
120 |
121 | message = chat.send_message(
122 | char, new.chat_id, text
123 | )
124 |
125 | print(f'{message.name}: {message.text}')
126 |
127 | This information is enough to give you a basic understanding of the library. You can also find examples in the `examples `_ folder on GitHub
128 |
129 | For more information, please refer to the documentation. Before using a function or asking a question, make sure to read the documentation and use the search
--------------------------------------------------------------------------------
/characterai/pycai/methods/utils.py:
--------------------------------------------------------------------------------
1 | from characterai.pycai import client, methods
2 | from ...errors import (
3 | ServerError, AuthError,
4 | JSONError
5 | )
6 |
7 | from curl_cffi import CurlMime
8 |
9 | from functools import wraps
10 | import json
11 |
12 | class Request:
13 | def request(
14 | self, url: str, *, token: str = None,
15 | method: str = 'GET', data: dict = {},
16 | split: bool = False, neo: bool = False,
17 | multipart: CurlMime = None
18 | ):
19 | key = self.token or token
20 |
21 | if key == None:
22 | raise AuthError('No token')
23 |
24 | headers = {
25 | "Authorization": f"Token {key}"
26 | }
27 |
28 | link = f'https://plus.character.ai/{url}'
29 |
30 | if neo:
31 | link = link.replace('plus', 'neo')
32 |
33 | if multipart != None:
34 | r = self.session.post(
35 | link, headers=headers, data=data,
36 | multipart=multipart
37 | )
38 | elif data != {} or data:
39 | r = self.session.post(
40 | link, headers=headers, json=data
41 | )
42 | elif method == 'GET':
43 | r = self.session.get(
44 | link, headers=headers
45 | )
46 | elif method == 'PUT':
47 | r = self.session.put(
48 | link, headers=headers, json=data
49 | )
50 |
51 | if neo and not r.ok:
52 | try:
53 | raise ServerError(r.json()['comment'])
54 | except KeyError:
55 | raise ServerError(r.text)
56 |
57 | if r == 404:
58 | raise ServerError('Not Found')
59 | elif not r.ok:
60 | raise ServerError(r.status_code)
61 |
62 | text = r.text
63 |
64 | if '}\n{' in text:
65 | text = '{' + text.split('}\n{')[-1]
66 |
67 | try:
68 | res = json.loads(
69 | text.encode('utf-8')
70 | )
71 | except json.decoder.JSONDecodeError:
72 | raise JSONError(
73 | 'Unable to decode JSON.'
74 | f'Server response: {r.text}'
75 | )
76 |
77 | try:
78 | if res['force_login']:
79 | raise AuthError('Need Auth')
80 | elif res['status'] != 'OK' or res['abort']:
81 | raise ServerError(res['error'])
82 | elif res['error'] != None:
83 | raise ServerError(res['error'])
84 | except KeyError:
85 | return res
86 |
87 | def checkSession(args) -> bool:
88 | return any(
89 | isinstance(
90 | a, client.pycai.Client
91 | ) for a in args
92 | )
93 |
94 | def delClass(args) -> tuple:
95 | new = ()
96 |
97 | for a in args:
98 | if not isinstance(a, (
99 | methods.chat1.ChatV1
100 | )):
101 | new = (a, *new)
102 |
103 | return new[::-1]
104 |
105 | def caimethod(func):
106 | @wraps(func)
107 | def wrapper(*args, **kwargs):
108 | try:
109 | try:
110 | token = kwargs['token']
111 | except (AttributeError, KeyError):
112 | token = args[0].token
113 | except AttributeError:
114 | token = None
115 |
116 | temp = False
117 |
118 | if not checkSession(args):
119 | temp = True
120 |
121 | args = (client.pycai.Client(
122 | token=token
123 | ), *args)
124 |
125 | args = delClass(args)
126 |
127 | result = func(*args, **kwargs)
128 |
129 | # Closing the session if the function
130 | # was used through a library class
131 | if temp:
132 | args[0].close()
133 |
134 | return result
135 |
136 | return wrapper
137 |
138 | def flatten(d, parent_key='', sep=''):
139 | items = []
140 | for k, v in d.items():
141 | if v == '':
142 | v = None
143 |
144 | if isinstance(v, dict):
145 | items.extend(flatten(v, k, sep=sep).items())
146 | else:
147 | items.append((k, v))
148 | return dict(items)
149 |
150 | def validate(_class, data):
151 | return [
152 | _class(**a) for a in data
153 | ]
154 |
--------------------------------------------------------------------------------
/characterai/aiocai/methods/utils.py:
--------------------------------------------------------------------------------
1 | from characterai.aiocai import client, methods
2 | from ...errors import (
3 | ServerError, AuthError,
4 | JSONError
5 | )
6 |
7 | from curl_cffi import CurlMime
8 |
9 | from functools import wraps
10 | import json
11 |
12 | class Request:
13 | async def request(
14 | self, url: str, *, token: str = None,
15 | method: str = 'GET', data: dict = {},
16 | split: bool = False, neo: bool = False,
17 | multipart: CurlMime = None
18 | ):
19 | key = self.token or token
20 |
21 | if key == None:
22 | raise AuthError('No token')
23 |
24 | headers = {
25 | "Authorization": f"Token {key}"
26 | }
27 |
28 | link = f'https://plus.character.ai/{url}'
29 |
30 | if neo:
31 | link = link.replace('plus', 'neo')
32 |
33 | if multipart != None:
34 | r = await self.session.post(
35 | link, headers=headers, data=data,
36 | multipart=multipart
37 | )
38 | elif data != {} or data:
39 | r = await self.session.post(
40 | link, headers=headers, json=data
41 | )
42 | elif method == 'GET':
43 | r = await self.session.get(
44 | link, headers=headers
45 | )
46 | elif method == 'PUT':
47 | r = await self.session.put(
48 | link, headers=headers, json=data
49 | )
50 |
51 | if neo and not r.ok:
52 | try:
53 | raise ServerError(r.json()['comment'])
54 | except KeyError:
55 | raise ServerError(r.text)
56 |
57 | if r == 404:
58 | raise ServerError('Not Found')
59 | elif not r.ok:
60 | raise ServerError(r.status_code)
61 |
62 | text = r.text
63 |
64 | if '}\n{' in text:
65 | text = '{' + text.split('}\n{')[-1]
66 |
67 | try:
68 | res = json.loads(
69 | text.encode('utf-8')
70 | )
71 | except json.decoder.JSONDecodeError:
72 | raise JSONError(
73 | 'Unable to decode JSON.'
74 | f'Server response: {r.text}'
75 | )
76 |
77 | try:
78 | if res['force_login']:
79 | raise AuthError('Need Auth')
80 | elif res['status'] != 'OK' or res['abort']:
81 | raise ServerError(res['error'])
82 | elif res['error'] != None:
83 | raise ServerError(res['error'])
84 | except KeyError:
85 | return res
86 |
87 | async def close(self):
88 | return await self.session.close()
89 |
90 | def checkSession(args) -> bool:
91 | return any(
92 | isinstance(
93 | a, client.aiocai.Client
94 | ) for a in args
95 | )
96 |
97 | def delClass(args) -> tuple:
98 | new = ()
99 |
100 | for a in args:
101 | if not isinstance(a, (
102 | methods.chat1.ChatV1
103 | )):
104 | new = (a, *new)
105 |
106 | return new[::-1]
107 |
108 | def caimethod(func):
109 | @wraps(func)
110 | async def wrapper(*args, **kwargs):
111 | try:
112 | try:
113 | token = kwargs['token']
114 | except (AttributeError, KeyError):
115 | token = args[0].token
116 | except AttributeError:
117 | token = None
118 |
119 | temp = False
120 |
121 | if not checkSession(args):
122 | temp = True
123 |
124 | args = (client.aiocai.Client(
125 | token=token
126 | ), *args)
127 |
128 | args = delClass(args)
129 |
130 | result = await func(*args, **kwargs)
131 |
132 | # Closing the session if the function
133 | # was used through a library class
134 | if temp:
135 | await args[0].close()
136 |
137 | return result
138 |
139 | return wrapper
140 |
141 | def flatten(d, parent_key='', sep=''):
142 | items = []
143 | for k, v in d.items():
144 | if v == '':
145 | v = None
146 |
147 | if isinstance(v, dict):
148 | items.extend(flatten(v, k, sep=sep).items())
149 | else:
150 | items.append((k, v))
151 | return dict(items)
152 |
153 | def validate(_class, data):
154 | return [
155 | _class(**a) for a in data
156 | ]
157 |
--------------------------------------------------------------------------------
/characterai/types/other.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from typing import Optional
3 |
4 | from curl_cffi.requests import AsyncSession
5 | from pydantic import BaseModel
6 |
7 | class Avatar:
8 | """A class for working with avatars.
9 |
10 | It exists in all classes with ``avatar_file_name`` parameter.
11 |
12 | .. code-block:: python
13 |
14 | await data.avatar.download('FILE.PNG')
15 |
16 | print(data.avatar.url)
17 |
18 | Parameters:
19 | avatar (:obj:`~characterai.types.other.Image`, *property*):
20 | Avatar object
21 |
22 | url (``str``):
23 | Incomplete URL
24 |
25 | type (``str``):
26 | Avatar type
27 |
28 | .. autofunction:: characterai.types.other.Image.download
29 | """
30 | @property
31 | def avatar(self):
32 | return Image(
33 | url=self.avatar_file_name,
34 | icon='avatars'
35 | )
36 |
37 | class QueryChar(BaseModel):
38 | """Character in search
39 |
40 | Parameters:
41 | document_id (``str``):
42 | ID in search (?)
43 |
44 | external_id (``str``):
45 | Char ID
46 |
47 | title (``str``):
48 | Short character description
49 |
50 | greeting (``str``):
51 | Character greeting (first message)
52 |
53 | avatar_file_name (``str``):
54 | Path to the avatar on the server
55 |
56 | avatar (:obj:`~characterai.types.other.Avatar`):
57 | Avatar info
58 |
59 | visibility (``str``):
60 | Who can see the character (everyone, you or from link)
61 |
62 | participant__name (``str``):
63 | Character name
64 |
65 | participant__num_interactions (``float``):
66 | Number of interactions (chats) with the character
67 |
68 | user__username (``str``):
69 | Author name
70 |
71 | priority (``float``):
72 | Priority in the search
73 |
74 | upvotes (``int``, *optional*):
75 | Number of character likes
76 | """
77 | document_id: str
78 | external_id: str
79 | title: str
80 | greeting: str
81 | avatar_file_name: str
82 | visibility: str
83 | participant__name: str
84 | participant__num_interactions: float
85 | user__username: str
86 | priority: float
87 | search_score: float
88 | upvotes: Optional[int] = None
89 |
90 | class Image(BaseModel):
91 | """Image from server
92 |
93 | This class has a ``download()`` function that can be used
94 | to download a picture. If you are using the async version
95 | of the library, this function will be async
96 |
97 | Parameters:
98 | url (``str``):
99 | Incomplete URL
100 |
101 | type (``str``):
102 | Image type
103 |
104 | .. autofunction:: characterai.types.other.Image.download
105 | """
106 | url: str
107 | type: str = 'UPLOADED'
108 | icon: str = 'user'
109 |
110 | async def download(
111 | self, path: str = None,
112 | width: int = 400, type: str = 'user'
113 | ):
114 | """Download any picture
115 |
116 | EXAMPLE::
117 |
118 | await data.avatar.download('FILE.PNG')
119 |
120 | Parameters:
121 | path (``str``, *optional*):
122 | The path to the file or the file name.
123 | If no path is specified, a file will be
124 | created with a name from the server
125 |
126 | width (``int``, *optional*):
127 | File resolution. Default is 400
128 |
129 | Returns:
130 | Downloaded file
131 | """
132 | if type == 'CREATED':
133 | await asyncio.sleep(3)
134 | else:
135 | self.url = f'https://characterai.io/i/{width}/static/{self.icon}/{self.url}'
136 |
137 | async with AsyncSession() as s:
138 | data = await s.get(
139 | self.url
140 | )
141 |
142 | with open(
143 | path or self.url.split('/')[-1], 'wb'
144 | ) as f:
145 | f.write(data.content)
146 |
147 | class Voice(BaseModel):
148 | """Voice for character messages
149 |
150 | Parameters:
151 | id (``int``):
152 | Vote number
153 |
154 | name (``str``):
155 | Voice name
156 |
157 | voice_id (``str``):
158 | Voice ID
159 |
160 | country_code (``str``):
161 | Language that supports voice. Other languages
162 | are not voiced/skipped in the text
163 |
164 | lang_code (``str``):
165 | Same as ``country_code``, but there may be difference
166 | """
167 | id: int
168 | name: str
169 | voice_id: str
170 | country_code: str
171 | lang_code: str
--------------------------------------------------------------------------------
/characterai/types/chat2.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 | from datetime import datetime
3 | from typing import List, Optional
4 |
5 | class Author(BaseModel):
6 | """Message author
7 |
8 | Parameters:
9 | author_id (``str``):
10 | Account ID
11 |
12 | name (``str``):
13 | User name
14 |
15 | is_human (``bool``, *option*):
16 | Is the author an account
17 | """
18 | author_id: str
19 | name: str
20 | is_human: Optional[bool] = None
21 |
22 | class Editor(BaseModel):
23 | """Message editor
24 |
25 | Parameters:
26 | author_id (``str``):
27 | Account ID
28 |
29 | name (``str``, *optional*):
30 | User name
31 | """
32 | author_id: str
33 | name: Optional[str] = None
34 |
35 | class TurnKey(BaseModel):
36 | chat_id: str
37 | turn_id: str
38 |
39 | class Candidate(BaseModel):
40 | """Контент сообщения
41 |
42 | Parameters:
43 | candidate_id (``str``):
44 | Message ID
45 |
46 | create_time (:py:obj:`~datetime.datetime`):
47 | Date of message creation
48 |
49 | raw_content (``str``):
50 | Message text
51 |
52 | editor (:obj:`~characterai.types.chat2.Editor`):
53 | Information about who modified the message
54 |
55 | is_final (``bool``):
56 | Is this the last chunk of the message
57 |
58 | base_candidate_id (``str``):
59 | Date of message update
60 | """
61 | candidate_id: str
62 | create_time: datetime
63 | raw_content: str
64 | editor: Optional[Editor] = None
65 | is_final: bool
66 | base_candidate_id: Optional[str] = None
67 |
68 | class BotAnswer(BaseModel):
69 | """Message object
70 |
71 | Parameters:
72 | text (``str``):
73 | Message text
74 |
75 | id (``str``):
76 | Message ID
77 |
78 | name (``str``):
79 | Sender's name
80 |
81 | turn_key (``TurnKey``):
82 | Message and chat ID [chat_id, turn_id]
83 |
84 | create_time (:py:obj:`~datetime.datetime`):
85 | Date of message creation
86 |
87 | last_update_time (:py:obj:`~datetime.datetime`):
88 | Date of message update
89 |
90 | state (``str``):
91 | Message state
92 |
93 | author (:obj:`~characterai.types.chat2.Author`):
94 | Message author
95 |
96 | candidates (List of :obj:`~characterai.types.chat2.Candidate`):
97 | Message content
98 |
99 | primary_candidate_id (``str``):
100 | Message ID (?)
101 | """
102 | @property
103 | def text(self):
104 | return self.candidates[0].raw_content
105 |
106 | @property
107 | def id(self):
108 | return self.candidates[0].candidate_id
109 |
110 | @property
111 | def name(self):
112 | return self.author.name
113 |
114 | turn_key: TurnKey
115 | create_time: datetime
116 | last_update_time: datetime
117 | state: str
118 | author: Author
119 | candidates: List[Candidate]
120 | primary_candidate_id: str
121 |
122 | class TurnData(BaseModel):
123 | """Preview message
124 |
125 | Parameters:
126 | turn_key (``str``):
127 | Message ID
128 |
129 | create_time (:py:obj:`~datetime.datetime`):
130 | Date of message creation
131 |
132 | last_update_time (:py:obj:`~datetime.datetime`):
133 | Date of message update
134 |
135 | state (``str``):
136 | Message state
137 |
138 | author (:obj:`~characterai.types.chat2.Author`):
139 | Message author
140 |
141 | candidates (List of :obj:`~characterai.types.chat2.Candidate`):
142 | Message content
143 |
144 | primary_candidate_id (``str``):
145 | Message ID (?)
146 | """
147 | turn_key: TurnKey
148 | create_time: datetime
149 | last_update_time: datetime
150 | state: str
151 | author: Author
152 | candidates: List[Candidate]
153 | primary_candidate_id: str
154 |
155 | class ChatData(BaseModel):
156 | """Chat info
157 |
158 | Parameters:
159 | chat_id (``str``):
160 | Chat ID
161 |
162 | create_time (:py:obj:`~datetime.datetime`):
163 | Date of message creation
164 |
165 | creator_id (``str``):
166 | Author ID
167 |
168 | character_id (``str``):
169 | Character ID
170 |
171 | state (``str``):
172 | Chat state
173 |
174 | type (``str``):
175 | Chat type
176 |
177 | visibility (``str``):
178 | Who can see the chat room
179 |
180 | preview_turns (List of :obj:`~characterai.types.chat2.TurnData`, *optional*):
181 | Message's preview
182 | """
183 | chat_id: str
184 | create_time: datetime
185 | creator_id: str
186 | character_id: str
187 | state: str
188 | type: str
189 | visibility: str
190 | preview_turns: Optional[List[TurnData]] = None
191 |
192 | class Meta(BaseModel):
193 | next_token: str
194 |
195 | class History(BaseModel):
196 | """Chat history
197 |
198 | turns (List of :obj:`~characterai.types.chat2.TurnData`):
199 | Message information
200 |
201 | meta (``Meta``):
202 | I don't know what it is, maybe someone could use it
203 | """
204 | turns: List[TurnData]
205 | meta: Meta
--------------------------------------------------------------------------------
/characterai/pycai/methods/chat1.py:
--------------------------------------------------------------------------------
1 | from .utils import Request, caimethod, validate
2 | from ...types import chat1
3 |
4 | class ChatV1(Request):
5 | def __init__(self, session = None, token = None):
6 | self.session = session
7 | self.token = token
8 |
9 | @caimethod
10 | def send_message(
11 | self, chat_id: str, tgt: str, text: str,
12 | token: str = None, **kwargs
13 | ):
14 | """Sending a message to chat
15 |
16 | EXAMPLE::
17 |
18 | await client.chat1.send_message('CHAT_ID', 'TGT', 'TEXT')
19 |
20 | Args:
21 | chat_id (``str``):
22 | Chat or room ID
23 |
24 | tgt (``str``):
25 | Old character ID type
26 |
27 | text (``str``):
28 | Message text
29 |
30 | primary_msg_uuid (``str``, *optional*):
31 | Reply to the next generated message from
32 | :obj:`~characterai.aiocai.methods.chat1.ChatV1.next_message`
33 |
34 | Returns:
35 | :obj:`~characterai.types.chat1.Message`
36 | """
37 | data = self.request(
38 | 'chat/streaming/', token=token,
39 | data={
40 | 'history_external_id': chat_id,
41 | 'text': text,
42 | 'tgt': tgt,
43 | **kwargs
44 | }
45 | )
46 |
47 | return chat1.Message.model_validate(
48 | data
49 | )
50 |
51 | @caimethod
52 | def get_chat(
53 | self, char_id: str, chat_id: str,
54 | token: str = None
55 | ):
56 | """Get information about the chat
57 |
58 | EXAMPLE::
59 |
60 | await client.chat1.get_chat('CHAR', 'CHAT_ID')
61 |
62 | Args:
63 | char_id (``str``):
64 | Character ID
65 |
66 | chat_id (``str``):
67 | Room or chat ID
68 |
69 | Returns:
70 | :obj:`~characterai.types.chat1.ChatHistory`
71 | """
72 | data = self.request(
73 | 'chat/history/continue/',
74 | token=token, data={
75 | 'character_external_id': char_id,
76 | 'history_external_id': chat_id
77 | }
78 | )
79 |
80 | return chat1.ChatHistory.model_validate(
81 | data
82 | )
83 |
84 | @caimethod
85 | def new_chat(
86 | self, char_id: str, *, token: str = None
87 | ):
88 | """Create a new chat
89 |
90 | EXAMPLE::
91 |
92 | await client.chat1.new_chat('CHAR_ID')
93 |
94 | Args:
95 | char_id (``str``):
96 | Character ID
97 |
98 | Returns:
99 | :obj:`~characterai.types.chat1.NewChat`
100 | """
101 | data = self.request(
102 | 'chat/history/create/',
103 | token=token, data={
104 | 'character_external_id': char_id
105 | }
106 | )
107 |
108 | return chat1.NewChat.model_validate(
109 | data
110 | )
111 |
112 | @caimethod
113 | def next_message(
114 | self, chat_id: str, tgt: str,
115 | parent_msg_uuid: str, *,
116 | token: str = None, **kwargs
117 | ):
118 | """Generate an alternative answer
119 |
120 | EXAMPLE::
121 |
122 | msg = await client.chat1.send_message(
123 | 'CHAT_ID', 'TGT', 'TEXT'
124 | )
125 |
126 | await client.chat1.next_message(
127 | 'CHAT_ID', 'TGT', msg.last_user_msg_uuid
128 | )
129 |
130 | Args:
131 | chat_id (``str``):
132 | Chat ID
133 |
134 | tgt (``str``):
135 | Old character ID type
136 |
137 | parent_msg_uuid (``str``):
138 | ID of the message from which you
139 | want to get an alternative reply
140 |
141 | Returns:
142 | :obj:`~characterai.types.chat1.Message`
143 | """
144 | return chat1.Message.model_validate(
145 | self.request(
146 | 'chat/streaming/',
147 | token=token, data={
148 | 'history_external_id': chat_id,
149 | 'parent_msg_uuid': parent_msg_uuid,
150 | 'tgt': tgt,
151 | **kwargs
152 | }
153 | )
154 | )
155 |
156 | @caimethod
157 | def get_histories(
158 | self, char: str, *, num: int = 999,
159 | token: str = None
160 | ):
161 | """Get a list of your character's chat histories
162 |
163 | EXAMPLE::
164 |
165 | await client.chat1.get_histories('CHAR_ID')
166 |
167 | Args:
168 | char (``str``):
169 | Character ID
170 |
171 | num (``str``):
172 | Maximum number of chats
173 |
174 | Returns:
175 | :obj:`~characterai.types.chat1.History`
176 | """
177 | data = self.request(
178 | 'chat/character/histories_v2/',
179 | token=token, data={
180 | 'external_id': char,
181 | 'number': num
182 | }
183 | )
184 |
185 | return validate(
186 | chat1.History,
187 | data['histories']
188 | )
189 |
190 | @caimethod
191 | def get_history(
192 | self, chat_id: str,
193 | *, token: str = None
194 | ):
195 | """Get chat history
196 |
197 | EXAMPLE::
198 |
199 | await client.chat1.get_history('CHAT_ID')
200 |
201 | Args:
202 | chat_id (``str``):
203 | Chat ID
204 |
205 | Returns:
206 | :obj:`~characterai.types.chat1.History`
207 | """
208 | data = self.request(
209 | 'chat/history/msgs/user/?'
210 | 'history_external_id='
211 | f'{chat_id}', token=token
212 | )
213 |
214 | return chat1.HisMessages.model_validate(
215 | data
216 | )
217 |
218 | @caimethod
219 | def delete_message(
220 | self, chat_id: str, uuids: list,
221 | *, token: str = None
222 | ) -> bool:
223 | """Deleting messages. Returns ``True`` on success.
224 |
225 | EXAMPLE::
226 |
227 | await client.chat1.delete_message('CHAT_ID', ['UUID'])
228 |
229 | Args:
230 | chat_id (``str``):
231 | Chat ID
232 |
233 | uuids (List of ``str``):
234 | List of message IDs to be deleted
235 |
236 | Returns:
237 | ``bool``
238 | """
239 | self.request(
240 | 'chat/history/msgs/delete/',
241 | token=token, data={
242 | 'history_id': chat_id,
243 | 'uuids_to_delete': uuids
244 | }
245 | )
246 |
247 | return True
248 |
249 | @caimethod
250 | def migrate(
251 | self, chat_id: str, *,
252 | token: str = None
253 | ):
254 | """Migrate chat1 to chat2
255 |
256 | EXAMPLE::
257 |
258 | await client.chat1.migrate('CHAT_ID')
259 |
260 | Args:
261 | chat_id (``str``):
262 | Chat1 ID
263 |
264 | Returns:
265 | :obj:`~characterai.types.chat1.Migrate`
266 | """
267 | self.request(
268 | f'migration/{chat_id}',
269 | token=token, data=True, neo=True
270 | )
271 |
272 | data = self.request(
273 | f'migration/{chat_id}',
274 | token=token, neo=True
275 | )
276 |
277 | return chat1.Migrate.model_validate(
278 | data['migration']
279 | )
--------------------------------------------------------------------------------
/characterai/pycai/methods/account.py:
--------------------------------------------------------------------------------
1 | from ...types import account, character
2 | from .utils import flatten, caimethod, validate
3 |
4 | import uuid
5 |
6 | class Account:
7 | @caimethod
8 | def get_me(self, *, token: str = None):
9 | """Information about your account
10 |
11 | EXAMPLE::
12 |
13 | await client.get_me()
14 |
15 | Returns:
16 | :obj:`~characterai.types.account.Profile`
17 | """
18 | data = self.request(
19 | 'chat/user/', token=token
20 | )
21 |
22 | name = data['user']['user']['username']
23 |
24 | if name == 'ANONYMOUS':
25 | return account.Anonymous()
26 | elif name.startswith('Guest'):
27 | return account.Guest.model_validate(
28 | flatten(data)
29 | )
30 |
31 | return account.Profile.model_validate(
32 | flatten(data)
33 | )
34 |
35 | @caimethod
36 | def edit_account(
37 | self, *, token: str = None, **kwargs
38 | ) -> bool:
39 | """Change your account information. Returns ``True`` on success.
40 |
41 | EXAMPLE::
42 |
43 | await client.edit_account()
44 |
45 | Args:
46 | username (``str``, *optional*):
47 | New nickname
48 |
49 | name (``str``, *optional*):
50 | New name
51 |
52 | avatar_type (``str``, *optional*):
53 | The type of the new avatar
54 |
55 | avatar_rel_path (``str``, *optional*):
56 | A new avatar. You need to provide a link to it. Files are uploaded
57 | via :obj:`~characterai.aiocai.methods.other.Other.upload_image`
58 |
59 | bio (``str``, *optional*):
60 | New description
61 |
62 | Returns:
63 | ``bool``
64 | """
65 | user = self.request(
66 | 'chat/user/', token=token
67 | )
68 |
69 | info = user['user']
70 | avatar = info['user']['account']
71 |
72 | settings = {
73 | 'username': info['user']['username'],
74 | 'name': info['name'],
75 | 'avatar_type': avatar['avatar_type'],
76 | 'avatar_rel_path': avatar['avatar_file_name'],
77 | 'bio': info['bio']
78 | }
79 |
80 | for k in kwargs:
81 | settings[k] = kwargs[k]
82 |
83 | self.request(
84 | 'chat/user/update/',
85 | token=token, data=settings
86 | )
87 |
88 | return True
89 |
90 | @caimethod
91 | def get_personas(
92 | self, *, token: str = None
93 | ) -> list:
94 | """Get all the personas in your account
95 |
96 | EXAMPLE::
97 |
98 | await client.get_personas()
99 |
100 | Returns:
101 | List of :obj:`~characterai.types.account.PersonaShort`
102 | """
103 | data = self.request(
104 | 'chat/personas/?force_refresh=1',
105 | token=token
106 | )
107 |
108 | return validate(
109 | account.PersonaShort,
110 | data['personas']
111 | )
112 |
113 | @caimethod
114 | def create_persona(
115 | self, title: str, *,
116 | token: str = None,
117 | definition: str = '',
118 | custom_id: str = None
119 | ):
120 | """Create persona
121 |
122 | EXAMPLE::
123 |
124 | await client.create_persona('TITLE')
125 |
126 | Args:
127 | title (``str``):
128 | Persona's name
129 |
130 | definition (``str``, *optional*):
131 | Persona's definition
132 |
133 | custom_id (``str``, *optional*):
134 | Your UUID for a persona. If you don't provide it,
135 | it will be generated automatically
136 |
137 | Returns:
138 | :obj:`~characterai.types.account.Persona`
139 | """
140 | identifier = custom_id or f'id:{uuid.uuid1()}'
141 |
142 | data = self.request(
143 | 'chat/persona/create/',
144 | token=token, data={
145 | 'title': title,
146 | 'name': title,
147 | 'identifier': identifier,
148 | 'categories': [],
149 | 'visibility': 'PUBLIC',
150 | 'copyable': False,
151 | 'description': 'This is my persona.',
152 | 'greeting': 'Hello! This is my persona',
153 | 'definition': definition,
154 | 'avatar_rel_path': '',
155 | 'img_gen_enabled': False,
156 | 'strip_img_prompt_from_msg': False,
157 | }
158 | )
159 |
160 | return account.Persona.model_validate(
161 | data['persona']
162 | )
163 |
164 | @caimethod
165 | def get_persona(
166 | self, persona_id: str, *, token: str = None
167 | ):
168 | """Get information about a persona
169 |
170 | EXAMPLE::
171 |
172 | await client.get_persona('ID')
173 |
174 | Args:
175 | persona_id (``str``):
176 | Persona's UUID
177 |
178 | Returns:
179 | :obj:`~characterai.types.account.Persona`
180 | """
181 | data = self.request(
182 | f'chat/persona/?id={persona_id}',
183 | token=token
184 | )
185 |
186 | return account.Persona.model_validate(
187 | data['persona']
188 | )
189 |
190 | @caimethod
191 | def delete_persona(
192 | self, persona_id: str, *, token: str = None
193 | ):
194 | """Delete persona
195 |
196 | EXAMPLE::
197 |
198 | await client.delete_persona('ID')
199 |
200 | Args:
201 | persona_id (``str``):
202 | Persona's UUID
203 |
204 | Returns:
205 | :obj:`~characterai.types.account.Persona`
206 | """
207 | persona = self.request(
208 | f'chat/persona/?id={persona_id}',
209 | token=token
210 | )
211 |
212 | data = self.request(
213 | 'chat/persona/update/',
214 | token=token, data={
215 | 'archived': True,
216 | **persona['persona']
217 | }
218 | )
219 |
220 | return account.Persona.model_validate(
221 | data['persona']
222 | )
223 |
224 |
225 | @caimethod
226 | def followers(
227 | self, *, token: str = None
228 | ) -> list:
229 | """Get your subscribers
230 |
231 | EXAMPLE::
232 |
233 | await client.followers()
234 |
235 | Returns:
236 | List of ``str``
237 | """
238 | return (self.request(
239 | 'chat/user/followers/',
240 | token=token
241 | ))['followers']
242 |
243 | @caimethod
244 | def following(
245 | self, *, token: str = None
246 | ) -> list:
247 | """Get those you subscribe to
248 |
249 | EXAMPLE::
250 |
251 | await client.following()
252 |
253 | Returns:
254 | List of ``str``
255 | """
256 | return (self.request(
257 | 'chat/user/following/',
258 | token=token
259 | ))['following']
260 |
261 | @caimethod
262 | def characters(
263 | self, *, token: str = None
264 | ):
265 | """Get your public characters
266 |
267 | EXAMPLE::
268 |
269 | await client.characters()
270 |
271 | Returns:
272 | List of :obj:`~characterai.types.character.CharShort`
273 | """
274 | data = self.request(
275 | 'chat/characters/?scope=user',
276 | token=token
277 | )
278 |
279 | return validate(
280 | character.CharShort,
281 | data['characters']
282 | )
283 |
--------------------------------------------------------------------------------
/characterai/aiocai/methods/chat1.py:
--------------------------------------------------------------------------------
1 | from .utils import Request, caimethod, validate
2 | from ...types import chat1
3 |
4 | class ChatV1(Request):
5 | def __init__(self, session = None, token = None):
6 | self.session = session
7 | self.token = token
8 |
9 | @caimethod
10 | async def send_message(
11 | self, chat_id: str, tgt: str, text: str,
12 | token: str = None, **kwargs
13 | ):
14 | """Sending a message to chat
15 |
16 | EXAMPLE::
17 |
18 | await client.chat1.send_message('CHAT_ID', 'TGT', 'TEXT')
19 |
20 | Args:
21 | chat_id (``str``):
22 | Chat or room ID
23 |
24 | tgt (``str``):
25 | Old character ID type
26 |
27 | text (``str``):
28 | Message text
29 |
30 | primary_msg_uuid (``str``, *optional*):
31 | Reply to the next generated message from
32 | :obj:`~characterai.aiocai.methods.chat1.ChatV1.next_message`
33 |
34 | Returns:
35 | :obj:`~characterai.types.chat1.Message`
36 | """
37 | data = await self.request(
38 | 'chat/streaming/',
39 | token=token, data={
40 | 'history_external_id': chat_id,
41 | 'text': text,
42 | 'tgt': tgt,
43 | **kwargs
44 | }
45 | )
46 |
47 | return chat1.Message.model_validate(
48 | data
49 | )
50 |
51 | @caimethod
52 | async def get_chat(
53 | self, char_id: str, chat_id: str,
54 | token: str = None
55 | ):
56 | """Get information about the chat
57 |
58 | EXAMPLE::
59 |
60 | await client.chat1.get_chat('CHAR', 'CHAT_ID')
61 |
62 | Args:
63 | char_id (``str``):
64 | Character ID
65 |
66 | chat_id (``str``):
67 | Room or chat ID
68 |
69 | Returns:
70 | :obj:`~characterai.types.chat1.ChatHistory`
71 | """
72 | data = await self.request(
73 | 'chat/history/continue/',
74 | token=token, data={
75 | 'character_external_id': char_id,
76 | 'history_external_id': chat_id
77 | }
78 | )
79 |
80 | return chat1.ChatHistory.model_validate(
81 | data
82 | )
83 |
84 | @caimethod
85 | async def new_chat(
86 | self, char_id: str, *, token: str = None
87 | ):
88 | """Create a new chat
89 |
90 | EXAMPLE::
91 |
92 | await client.chat1.new_chat('CHAR_ID')
93 |
94 | Args:
95 | char_id (``str``):
96 | Character ID
97 |
98 | Returns:
99 | :obj:`~characterai.types.chat1.NewChat`
100 | """
101 | data = await self.request(
102 | 'chat/history/create/',
103 | token=token, data={
104 | 'character_external_id': char_id
105 | }
106 | )
107 |
108 | return chat1.NewChat.model_validate(
109 | data
110 | )
111 |
112 | @caimethod
113 | async def next_message(
114 | self, chat_id: str, tgt: str,
115 | parent_msg_uuid: str, *,
116 | token: str = None, **kwargs
117 | ):
118 | """Generate an alternative answer
119 |
120 | EXAMPLE::
121 |
122 | msg = await client.chat1.send_message(
123 | 'CHAT_ID', 'TGT', 'TEXT'
124 | )
125 |
126 | await client.chat1.next_message(
127 | 'CHAT_ID', 'TGT', msg.last_user_msg_uuid
128 | )
129 |
130 | Args:
131 | chat_id (``str``):
132 | Chat ID
133 |
134 | tgt (``str``):
135 | Old character ID type
136 |
137 | parent_msg_uuid (``str``):
138 | ID of the message from which you
139 | want to get an alternative reply
140 |
141 | Returns:
142 | :obj:`~characterai.types.chat1.Message`
143 | """
144 | return chat1.Message.model_validate(
145 | await self.request(
146 | 'chat/streaming/',
147 | token=token, data={
148 | 'history_external_id': chat_id,
149 | 'parent_msg_uuid': parent_msg_uuid,
150 | 'tgt': tgt,
151 | **kwargs
152 | }
153 | )
154 | )
155 |
156 | @caimethod
157 | async def get_histories(
158 | self, char: str, *, num: int = 999,
159 | token: str = None
160 | ):
161 | """Get a list of your character's chat histories
162 |
163 | EXAMPLE::
164 |
165 | await client.chat1.get_histories('CHAR_ID')
166 |
167 | Args:
168 | char (``str``):
169 | Character ID
170 |
171 | num (``str``):
172 | Maximum number of chats
173 |
174 | Returns:
175 | :obj:`~characterai.types.chat1.History`
176 | """
177 | data = await self.request(
178 | 'chat/character/histories_v2/',
179 | token=token, data={
180 | 'external_id': char,
181 | 'number': num
182 | }
183 | )
184 |
185 | return validate(
186 | chat1.History,
187 | data['histories']
188 | )
189 |
190 | @caimethod
191 | async def get_history(
192 | self, chat_id: str,
193 | *, token: str = None
194 | ):
195 | """Get chat history
196 |
197 | EXAMPLE::
198 |
199 | await client.chat1.get_history('CHAT_ID')
200 |
201 | Args:
202 | chat_id (``str``):
203 | Chat ID
204 |
205 | Returns:
206 | :obj:`~characterai.types.chat1.History`
207 | """
208 | data = await self.request(
209 | 'chat/history/msgs/user/?'
210 | 'history_external_id='
211 | f'{chat_id}', token=token
212 | )
213 |
214 | return chat1.HisMessages.model_validate(
215 | data
216 | )
217 |
218 | @caimethod
219 | async def delete_message(
220 | self, chat_id: str, uuids: list,
221 | *, token: str = None
222 | ) -> bool:
223 | """Deleting messages. Returns ``True`` on success.
224 |
225 | EXAMPLE::
226 |
227 | await client.chat1.delete_message('CHAT_ID', ['UUID'])
228 |
229 | Args:
230 | chat_id (``str``):
231 | Chat ID
232 |
233 | uuids (List of ``str``):
234 | List of message IDs to be deleted
235 |
236 | Returns:
237 | ``bool``
238 | """
239 | await self.request(
240 | 'chat/history/msgs/delete/',
241 | token=token, data={
242 | 'history_id': chat_id,
243 | 'uuids_to_delete': uuids
244 | }
245 | )
246 |
247 | return True
248 |
249 | @caimethod
250 | async def migrate(
251 | self, chat_id: str, *,
252 | token: str = None
253 | ):
254 | """Migrate chat1 to chat2
255 |
256 | EXAMPLE::
257 |
258 | await client.chat1.migrate('CHAT_ID')
259 |
260 | Args:
261 | chat_id (``str``):
262 | Chat1 ID
263 |
264 | Returns:
265 | :obj:`~characterai.types.chat1.Migrate`
266 | """
267 | await self.request(
268 | f'migration/{chat_id}',
269 | token=token, data=True, neo=True
270 | )
271 |
272 | data = await self.request(
273 | f'migration/{chat_id}',
274 | token=token, neo=True
275 | )
276 |
277 | return chat1.Migrate.model_validate(
278 | data['migration']
279 | )
--------------------------------------------------------------------------------
/characterai/types/account.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from pydantic import BaseModel
3 | from typing import Any, List, Optional
4 |
5 | from .other import Avatar
6 |
7 | class Anonymous(BaseModel):
8 | username: str = 'ANONYMOUS'
9 |
10 | class Guest(BaseModel):
11 | username: str
12 | id: int
13 | account: Optional[Any] = None
14 | is_staff: bool = False
15 | subscription: Optional[int] = None
16 | is_human: bool = True
17 | name: str
18 | email: Optional[str] = None
19 | hidden_characters: list
20 | blocked_users: list
21 |
22 | class Profile(BaseModel, Avatar):
23 | """Your account info
24 |
25 | Parameters:
26 | name (``str``):
27 | Your name
28 |
29 | avatar_type (``str``):
30 | Avatar status, uploaded or not
31 |
32 | avatar_file_name (``str``, *optional*):
33 | Path to the avatar on the server
34 |
35 | avatar (:obj:`~characterai.types.other.Avatar`):
36 | Avatar info
37 |
38 | onboarding_complete (``bool``):
39 | For pop-up banners (?)
40 |
41 | mobile_onboarding_complete (``int``, *optional*):
42 | For mobile pop-up banners (?)
43 |
44 | bio (``str``, *optional*):
45 | Account description
46 |
47 | username (``str``):
48 | Public user nickname
49 |
50 | id (``str``):
51 | ID аккаунта, также ``author_id``
52 |
53 | first_name (``str``, *optional*):
54 | Account email
55 |
56 | is_staff (``bool``):
57 | Is the account an employee of the service
58 |
59 | subscription (``int``, *optional*):
60 | Subscription type
61 |
62 | is_human (``bool``):
63 | Is the account a human
64 |
65 | email (``str``):
66 | Your email
67 |
68 | needs_to_acknowledge_policy (``str``):
69 | Should the user agreement be accepted
70 |
71 | suspended_until (:py:obj:`~datetime.datetime`, *optional*):
72 | Account lockout end date
73 |
74 | hidden_characters (List of ``str``):
75 | Hidden characters
76 |
77 | blocked_users (List of ``str``):
78 | Blocked users
79 | """
80 | name: Optional[str] = None
81 | avatar_type: Optional[str] = None
82 | onboarding_complete: bool
83 | avatar_file_name: str | None
84 | mobile_onboarding_complete: int | None
85 | bio: str | None
86 | username: str
87 | id: int
88 | first_name: str | None
89 | is_staff: bool
90 | subscription: Optional[dict] = None
91 | is_human: bool
92 | email: str
93 | needs_to_acknowledge_policy: bool
94 | suspended_until: datetime | None
95 | hidden_characters: List[str]
96 | blocked_users: List[str]
97 |
98 | class Persona(BaseModel, Avatar):
99 | """Информация о вашей персоне
100 |
101 | Parameters:
102 | external_id (``str``):
103 | Persona ID
104 |
105 | title (``str``):
106 | Persona title
107 |
108 | name (``str``):
109 | Persona name
110 |
111 | visibiility (``str``):
112 | Persona visibility
113 |
114 | copyable (``bool``):
115 | Can other users copy a persona
116 |
117 | greeting (``str``):
118 | Persona greeting (?)
119 |
120 | description (``str``):
121 | Persona description
122 |
123 | identifier (``str``):
124 | Persona ID
125 |
126 | avatar_file_name (``str``):
127 | Path to the avatar on the server
128 |
129 | avatar (:obj:`~characterai.types.other.Avatar`):
130 | Avatar info
131 |
132 | songs (``list``):
133 | Songs list (?)
134 |
135 | img_gen_enabled (``bool``):
136 | Can a persona generate pictures (?)
137 |
138 | base_img_prompt (``str``):
139 | Basic prompt for generating pictures (?)
140 |
141 | img_prompt_regex (``str``):
142 | Regex for a picture prompt (?)
143 |
144 | strip_img_prompt_from_msg (``str``):
145 | Remove the prompt from the message (?)
146 |
147 | definition (``str``):
148 | Persona definition
149 |
150 | default_voice_id (``str``):
151 | Default voice ID (?)
152 |
153 | starter_prompts (``Any``):
154 | Prompts for a start (?)
155 |
156 | comments_enabled (``bool``):
157 | Can comment on the persona (?)
158 |
159 | categories (``list``):
160 | Persona categories (?)
161 |
162 | user__username (``str``):
163 | User name
164 |
165 | participant__name (``str``):
166 | Person's name, same as ``name``
167 |
168 | participant__user__username (``str``):
169 | Persona ID
170 |
171 | num_interactions (``str``):
172 | Number of interactions with the person (?)
173 |
174 | voice_id (``str``):
175 | Voice ID (?)
176 | """
177 | external_id: str
178 | title: str
179 | name: str
180 | visibility: str
181 | copyable: bool
182 | greeting: str
183 | description: str
184 | identifier: str
185 | avatar_file_name: str
186 | songs: list
187 | img_gen_enabled: bool
188 | base_img_prompt: str
189 | img_prompt_regex: str
190 | strip_img_prompt_from_msg: bool
191 | definition: str
192 | default_voice_id: str
193 | starter_prompts: Any
194 | comments_enabled: bool
195 | categories: list
196 | user__username: str
197 | participant__name: str
198 | participant__user__username: str
199 | num_interactions: int
200 | voice_id: str
201 |
202 | class PersonaShort(BaseModel, Avatar):
203 | """Короткая информация о вашей персоне
204 |
205 | Parameters:
206 | external_id (``str``):
207 | Persona ID
208 |
209 | title (``str``, *optional*):
210 | Persona title
211 |
212 | greeting (``str``):
213 | Persona greeting (?)
214 |
215 | description (``str``, *optional*):
216 | Persona description
217 |
218 | definition (``str``):
219 | Persona definition
220 |
221 | avatar_file_name (``str``):
222 | Path to the avatar on the server
223 |
224 | avatar (:obj:`~characterai.types.other.Avatar`):
225 | Avatar info
226 |
227 | visibiility (``str``):
228 | Persona visibility
229 |
230 | copyable (``bool``):
231 | Can other users copy a persona
232 |
233 | participant__name (``str``):
234 | Person's name, same as ``name``
235 |
236 | participant__num_interactions (``str``):
237 | Number of interactions with the person (?)
238 |
239 | user__id (``int``):
240 | User ID, and also ``author_id``
241 |
242 | user__username (``str``):
243 | User name
244 |
245 | img_gen_enabled (``bool``):
246 | Can a persona generate pictures (?)
247 |
248 | default_voice_id (``str``, *optional*):
249 | Default voice ID (?)
250 |
251 | is_persona (``bool``):
252 | Is the object a person
253 | """
254 | external_id: str
255 | title: str | None
256 | greeting: str
257 | description: str | None
258 | definition: str
259 | avatar_file_name: str | None
260 | visibility: str
261 | copyable: bool
262 | participant__name: str
263 | participant__num_interactions: int
264 | user__id: int
265 | user__username: str
266 | img_gen_enabled: bool
267 | default_voice_id: str | None
268 | is_persona: bool
269 |
--------------------------------------------------------------------------------
/characterai/aiocai/methods/account.py:
--------------------------------------------------------------------------------
1 | from ...types import account, character
2 | from .utils import flatten, caimethod, validate
3 |
4 | import uuid
5 |
6 | class Account:
7 | @caimethod
8 | async def get_me(self, *, token: str = None):
9 | """Information about your account
10 |
11 | EXAMPLE::
12 |
13 | await client.get_me()
14 |
15 | Returns:
16 | :obj:`~characterai.types.account.Profile`
17 | """
18 | data = await self.request(
19 | 'chat/user/', token=token
20 | )
21 |
22 | name = data['user']['user']['username']
23 |
24 | if name == 'ANONYMOUS':
25 | return account.Anonymous()
26 | elif name.startswith('Guest'):
27 | return account.Guest.model_validate(
28 | flatten(data)
29 | )
30 |
31 | return account.Profile.model_validate(
32 | flatten(data)
33 | )
34 |
35 | @caimethod
36 | async def edit_account(
37 | self, *, token: str = None, **kwargs
38 | ) -> bool:
39 | """Change your account information. Returns ``True`` on success.
40 |
41 | EXAMPLE::
42 |
43 | await client.edit_account()
44 |
45 | Args:
46 | username (``str``, *optional*):
47 | New nickname
48 |
49 | name (``str``, *optional*):
50 | New name
51 |
52 | avatar_type (``str``, *optional*):
53 | The type of the new avatar
54 |
55 | avatar_rel_path (``str``, *optional*):
56 | A new avatar. You need to provide a link to it. Files are uploaded
57 | via :obj:`~characterai.aiocai.methods.other.Other.upload_image`
58 |
59 | bio (``str``, *optional*):
60 | New description
61 |
62 | Returns:
63 | ``bool``
64 | """
65 | user = await self.request(
66 | 'chat/user/', token=token
67 | )
68 |
69 | info = user['user']
70 | avatar = info['user']['account']
71 |
72 | settings = {
73 | 'username': info['user']['username'],
74 | 'name': info['name'],
75 | 'avatar_type': avatar['avatar_type'],
76 | 'avatar_rel_path': avatar['avatar_file_name'],
77 | 'bio': info['bio']
78 | }
79 |
80 | for k in kwargs:
81 | settings[k] = kwargs[k]
82 |
83 | await self.request(
84 | 'chat/user/update/',
85 | token=token, data=settings
86 | )
87 |
88 | return True
89 |
90 | @caimethod
91 | async def get_personas(
92 | self, *, token: str = None
93 | ) -> list:
94 | """Get all the personas in your account
95 |
96 | EXAMPLE::
97 |
98 | await client.get_personas()
99 |
100 | Returns:
101 | List of :obj:`~characterai.types.account.PersonaShort`
102 | """
103 | data = await self.request(
104 | 'chat/personas/?force_refresh=1',
105 | token=token
106 | )
107 |
108 | return validate(
109 | account.PersonaShort,
110 | data['personas']
111 | )
112 |
113 | @caimethod
114 | async def create_persona(
115 | self, title: str, *,
116 | token: str = None,
117 | definition: str = '',
118 | custom_id: str = None
119 | ):
120 | """Create persona
121 |
122 | EXAMPLE::
123 |
124 | await client.create_persona('TITLE')
125 |
126 | Args:
127 | title (``str``):
128 | Persona's name
129 |
130 | definition (``str``, *optional*):
131 | Persona's definition
132 |
133 | custom_id (``str``, *optional*):
134 | Your UUID for a persona. If you don't provide it,
135 | it will be generated automatically
136 |
137 | Returns:
138 | :obj:`~characterai.types.account.Persona`
139 | """
140 | identifier = custom_id or f'id:{uuid.uuid1()}'
141 |
142 | data = await self.request(
143 | 'chat/persona/create/',
144 | token=token, data={
145 | 'title': title,
146 | 'name': title,
147 | 'identifier': identifier,
148 | 'categories': [],
149 | 'visibility': 'PUBLIC',
150 | 'copyable': False,
151 | 'description': 'This is my persona.',
152 | 'greeting': 'Hello! This is my persona',
153 | 'definition': definition,
154 | 'avatar_rel_path': '',
155 | 'img_gen_enabled': False,
156 | 'strip_img_prompt_from_msg': False,
157 | }
158 | )
159 |
160 | return account.Persona.model_validate(
161 | data['persona']
162 | )
163 |
164 | @caimethod
165 | async def get_persona(
166 | self, persona_id: str, *, token: str = None
167 | ):
168 | """Get information about a persona
169 |
170 | EXAMPLE::
171 |
172 | await client.get_persona('ID')
173 |
174 | Args:
175 | persona_id (``str``):
176 | Persona's UUID
177 |
178 | Returns:
179 | :obj:`~characterai.types.account.Persona`
180 | """
181 | data = await self.request(
182 | f'chat/persona/?id={persona_id}',
183 | token=token
184 | )
185 |
186 | return account.Persona.model_validate(
187 | data['persona']
188 | )
189 |
190 | @caimethod
191 | async def delete_persona(
192 | self, persona_id: str, *, token: str = None
193 | ):
194 | """Delete persona
195 |
196 | EXAMPLE::
197 |
198 | await client.delete_persona('ID')
199 |
200 | Args:
201 | persona_id (``str``):
202 | Persona's UUID
203 |
204 | Returns:
205 | :obj:`~characterai.types.account.Persona`
206 | """
207 | persona = await self.request(
208 | f'chat/persona/?id={persona_id}',
209 | token=token
210 | )
211 |
212 | data = await self.request(
213 | 'chat/persona/update/',
214 | token=token, data={
215 | 'archived': True,
216 | **persona['persona']
217 | }
218 | )
219 |
220 | return account.Persona.model_validate(
221 | data['persona']
222 | )
223 |
224 |
225 | @caimethod
226 | async def followers(
227 | self, *, token: str = None
228 | ) -> list:
229 | """Get your subscribers
230 |
231 | EXAMPLE::
232 |
233 | await client.followers()
234 |
235 | Returns:
236 | List of ``str``
237 | """
238 | return (await self.request(
239 | 'chat/user/followers/',
240 | token=token
241 | ))['followers']
242 |
243 | @caimethod
244 | async def following(
245 | self, *, token: str = None
246 | ) -> list:
247 | """Get those you subscribe to
248 |
249 | EXAMPLE::
250 |
251 | await client.following()
252 |
253 | Returns:
254 | List of ``str``
255 | """
256 | return (await self.request(
257 | 'chat/user/following/',
258 | token=token
259 | ))['following']
260 |
261 | @caimethod
262 | async def characters(
263 | self, *, token: str = None
264 | ):
265 | """Get your public characters
266 |
267 | EXAMPLE::
268 |
269 | await client.characters()
270 |
271 | Returns:
272 | List of :obj:`~characterai.types.character.CharShort`
273 | """
274 | data = await self.request(
275 | 'chat/characters/?scope=user',
276 | token=token
277 | )
278 |
279 | return validate(
280 | character.CharShort,
281 | data['characters']
282 | )
283 |
--------------------------------------------------------------------------------
/characterai/types/character.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, Field
2 | from typing import Optional, List
3 | from datetime import datetime
4 |
5 | from .other import Avatar
6 |
7 | class Promts(BaseModel):
8 | phrases: list
9 |
10 | class Character(BaseModel, Avatar):
11 | """Character info
12 |
13 | Parameters:
14 | external_id (``str``):
15 | Character ID
16 |
17 | title (``str``, *optional*):
18 | Short character description
19 |
20 | name (``str``):
21 | Character name
22 |
23 | visibility (``str``):
24 | Character visibility (everyone, you or from link)
25 |
26 | copyable (``bool``):
27 | Can other users copy a character
28 |
29 | greeting (``str``):
30 | Character greeting (first message)
31 |
32 | description (``str``, *optional*):
33 | Character description
34 |
35 | identifier (``str``):
36 | Character ID
37 |
38 | avatar_file_name (``str``):
39 | Path to the avatar on the server
40 |
41 | avatar (:obj:`~characterai.types.other.Avatar`):
42 | Avatar info
43 |
44 | songs (``list``):
45 | List of songs (?)
46 |
47 | img_gen_enabled (``bool``):
48 | Can the character generate pictures
49 |
50 | base_img_prompt (``str``, *optional*):
51 | Basic prompt for generating pictures
52 |
53 | img_prompt_regex (``str``, *optional*):
54 | Regex for a picture prompt
55 |
56 | strip_img_prompt_from_msg (``str``):
57 | Remove the prompt from the message
58 |
59 | definition (``str``):
60 | Character definition
61 |
62 | default_voice_id (``str``):
63 | Default voice ID
64 |
65 | starter_prompts (``Promts``, *optional*):
66 | Prompts for start (?)
67 |
68 | comments_enabled (``bool``):
69 | Can comment on the character (?)
70 |
71 | user__username (``str``):
72 | Author nickname
73 |
74 | participant__name (``str``):
75 | Character name, same as ``name``
76 |
77 | participant__user__username (``str``):
78 | Character ID
79 |
80 | participant__num_interactions (``int``, *optional*):
81 | Number of interactions (chats) with the character
82 |
83 | voice_id (``str``, *optional*):
84 | Voice ID
85 |
86 | usage (``str``, *optional*):
87 | How well the character is used (?)
88 |
89 | upvotes (``int``, *optional*):
90 | Number of likes
91 | """
92 | external_id: str
93 | title: str | None
94 | name: str
95 | visibility: str
96 | copyable: bool
97 | greeting: str
98 | description: str | None
99 | identifier: str
100 | avatar_file_name: str | None
101 | songs: list
102 | img_gen_enabled: bool
103 | base_img_prompt: str | None
104 | img_prompt_regex: str | None
105 | strip_img_prompt_from_msg: bool
106 | default_voice_id: str | None
107 | starter_prompts: Optional[Promts] = None
108 | comments_enabled: bool
109 | user__username: str
110 | participant__name: str
111 | participant__num_interactions: Optional[int] = None
112 | participant__user__username: str
113 | voice_id: str | None
114 | usage: Optional[str] = None
115 | upvotes: Optional[int] = None
116 |
117 | class CharShort(BaseModel, Avatar):
118 | """Краткая информация о персонаже
119 |
120 | Parameters:
121 | external_id (``str``):
122 | Character ID
123 |
124 | title (``str``, *optional*):
125 | Short character description
126 |
127 | name (``str``, *optional*):
128 | Character name
129 |
130 | greeting (``str``):
131 | Character greeting (first message)
132 |
133 | description (``str``, *optional*):
134 | Character description
135 |
136 | avatar_file_name (``str``):
137 | Path to the avatar on the server
138 |
139 | avatar (:obj:`~characterai.types.other.Avatar`):
140 | Avatar info
141 |
142 | visibility (``str``, *optional*):
143 | Character visibility (everyone, you or from link)
144 |
145 | copyable (``bool``, *optional*):
146 | Can other users copy a character
147 |
148 | participant__name (``str``):
149 | Character name, same as ``name``
150 |
151 | user__id (``int``, *optional*):
152 | Author ID (?)
153 |
154 | user__username (``str``):
155 | Author nickname
156 |
157 | img_gen_enabled (``bool``):
158 | Can the character generate pictures
159 |
160 | participant__num_interactions (``int``, *optional*):
161 | Number of interactions (chats) with the character
162 |
163 | default_voice_id (``str``, *optional*):
164 | Default voice ID
165 |
166 | upvotes (``int``, *optional*):
167 | Number of likes
168 |
169 | max_last_interaction (:py:obj:`~datetime.datetime`, *optional*):
170 | Maximum time of the last interaction with the character (?)
171 | """
172 | external_id: str
173 | title: str | None
174 | description: Optional[str | None] = None
175 | greeting: str
176 | avatar_file_name: str | None
177 | visibility: Optional[str] = None
178 | copyable: Optional[str | bool] = None
179 | participant__name: str
180 | user__id: Optional[int] = None
181 | user__username: str
182 | img_gen_enabled: bool
183 | participant__num_interactions: Optional[int] = None
184 | default_voice_id: Optional[str] = None
185 | upvotes: Optional[int] = None
186 | max_last_interaction: Optional[datetime] = None
187 |
188 | class Categories(BaseModel):
189 | """List of categories
190 |
191 | Parameters:
192 | animals (List of :obj:`~characterai.types.character.CharShort`):
193 | Animals bots
194 |
195 | anime (List of :obj:`~characterai.types.character.CharShort`):
196 | Anime bots
197 |
198 | anime_game (List of :obj:`~characterai.types.character.CharShort`):
199 | Anime game bots
200 |
201 | chinese (List of :obj:`~characterai.types.character.CharShort`):
202 | Chinese bots
203 |
204 | comedy (List of :obj:`~characterai.types.character.CharShort`):
205 | Comedy bots
206 |
207 | discussion (List of :obj:`~characterai.types.character.CharShort`):
208 | Discussion bots
209 |
210 | famous (List of :obj:`~characterai.types.character.CharShort`):
211 | Famous bots
212 |
213 | game (List of :obj:`~characterai.types.character.CharShort`):
214 | Game bots
215 |
216 | games (List of :obj:`~characterai.types.character.CharShort`):
217 | Games bots
218 |
219 | helpers (List of :obj:`~characterai.types.character.CharShort`):
220 | Helpers bots
221 |
222 | image (List of :obj:`~characterai.types.character.CharShort`):
223 | Image bots
224 |
225 | movies (List of :obj:`~characterai.types.character.CharShort`):
226 | Movies bots
227 |
228 | philosophy (List of :obj:`~characterai.types.character.CharShort`):
229 | Philosophy bots
230 |
231 | politics (List of :obj:`~characterai.types.character.CharShort`):
232 | Politics bots
233 |
234 | religion (List of :obj:`~characterai.types.character.CharShort`):
235 | Religion bots
236 |
237 | vtuber (List of :obj:`~characterai.types.character.CharShort`):
238 | VTuber bots
239 | """
240 | animals: List[CharShort] = Field(validation_alias='Animals')
241 | anime: List[CharShort] = Field(validation_alias='Anime')
242 | anime_game: List[CharShort] = Field(validation_alias='Anime Game Characters')
243 | books: List[CharShort] = Field(validation_alias='Books')
244 | chinese: List[CharShort] = Field(validation_alias='Chinese')
245 | comedy: List[CharShort] = Field(validation_alias='Comedy')
246 | discussion: List[CharShort] = Field(validation_alias='Discussion')
247 | famous: List[CharShort] = Field(validation_alias='Famous People')
248 | game: List[CharShort] = Field(validation_alias='Game Characters')
249 | games: List[CharShort] = Field(validation_alias='Games')
250 | helpers: List[CharShort] = Field(validation_alias='Helpers')
251 | image: List[CharShort] = Field(validation_alias='Image Generating')
252 | movies: List[CharShort] = Field(validation_alias='Movies & TV')
253 | philosophy: List[CharShort] = Field(validation_alias='Philosophy')
254 | politics: List[CharShort] = Field(validation_alias='Politics')
255 | religion: List[CharShort] = Field(validation_alias='Religion')
256 | vtuber: List[CharShort] = Field(validation_alias='VTuber')
--------------------------------------------------------------------------------
/characterai/pycai/methods/characters.py:
--------------------------------------------------------------------------------
1 | from .utils import caimethod, validate
2 | from ...types import character
3 |
4 | import uuid
5 |
6 | class Characters:
7 | @caimethod
8 | def get_char(
9 | self, external_id: str,
10 | *, token: str = None
11 | ):
12 | """Get information about the character
13 |
14 | EXAMPLE::
15 |
16 | await client.get_char('ID')
17 |
18 | Args:
19 | external_id (``str``): Character ID
20 |
21 | Returns:
22 | :obj:`~characterai.types.character.Character`
23 | """
24 | data = self.request(
25 | 'chat/character/info/', token=token,
26 | data={'external_id': external_id}
27 | )
28 |
29 | return character.Character.model_validate(
30 | data['character']
31 | )
32 |
33 | @caimethod
34 | def upvoted(self, *, token: str = None):
35 | """The list of characters you have given a voice to
36 |
37 | EXAMPLE::
38 |
39 | await client.upvoted()
40 |
41 | Returns:
42 | List of :obj:`~characterai.types.character.CharShort`
43 | """
44 | data = self.request(
45 | 'chat/user/characters/upvoted/',
46 | token=token
47 | )
48 |
49 | return validate(
50 | character.CharShort,
51 | data['characters']
52 | )
53 |
54 | @caimethod
55 | def get_category(
56 | self, name: str = 'All',
57 | *, token: str = None
58 | ):
59 | """Get characters from categories
60 |
61 | EXAMPLE::
62 |
63 | await client.get_category()
64 |
65 | Args:
66 | name (``str``): Category name
67 |
68 | Returns:
69 | :obj:`~characterai.types.character.Categories` | List of :obj:`~characterai.types.character.CharShort`
70 | """
71 | data = self.request(
72 | 'chat/curated_categories'
73 | '/characters/',
74 | )
75 |
76 | categories = data['characters_by_curated_category']
77 |
78 | if name != 'All':
79 | return character.CharShort.model_validate(
80 | categories[name]
81 | )
82 |
83 | return character.Categories.model_validate(
84 | categories
85 | )
86 |
87 | @caimethod
88 | def get_recommended(
89 | self, *, token: str = None
90 | ):
91 | """Get a list of recommended characters
92 |
93 | EXAMPLE::
94 |
95 | await client.get_recommended()
96 |
97 | Returns:
98 | List of :obj:`~characterai.types.character.CharShort`
99 | """
100 | data = self.request(
101 | 'chat/characters/trending/'
102 | )
103 |
104 | return validate(
105 | character.CharShort,
106 | data['trending_characters']
107 | )
108 |
109 | @caimethod
110 | def get_trending(
111 | self, *, token: str = None
112 | ):
113 | """Get a list of trending characters
114 |
115 | EXAMPLE::
116 |
117 | await client.get_trending()
118 |
119 | Returns:
120 | List of :obj:`~characterai.types.character.CharShort`
121 | """
122 | data = self.request(
123 | 'recommendation/v1/user',
124 | token=token, neo=True
125 | )
126 |
127 | return validate(
128 | character.CharShort,
129 | data['characters']
130 | )
131 |
132 | @caimethod
133 | def create_char(
134 | self,
135 | name: str,
136 | greeting: str,
137 | *,
138 | tgt: str = None,
139 | title: str = '',
140 | visinility: str = 'PRIVATE',
141 | copyable: bool = True,
142 | description: str = '',
143 | definition: str = '',
144 | avatar_path: str = '',
145 | token: str = None,
146 | **kwargs
147 | ):
148 | """Create a character
149 |
150 | EXAMPLE::
151 |
152 | await client.create_char('NAME', 'GREETING')
153 |
154 | Args:
155 | name (``str``):
156 | Character name
157 |
158 | greeting (``str``):
159 | Character greeting
160 |
161 | tgt (``str``, *optional*):
162 | Old type Character ID
163 |
164 | title (``str``, *optional*):
165 | Short description of the character
166 |
167 | visibility (``str``, *optional*):
168 | Character visibility
169 |
170 | copyable (``bool``, *optional*):
171 | Ability to copy a character
172 |
173 | description (``str``, *optional*):
174 | Character description
175 |
176 | definition (``str``, *optional*:
177 | Character definition (memory, chat examples)
178 |
179 | avatar_path (``str``, *optional*):
180 | Path to the character's avatar on the c.ai server.
181 | Example: ``uploaded/2022/12/26/some_id.webp``
182 |
183 | Returns:
184 | :obj:`~characterai.types.character.Character`
185 | """
186 | tgt = f'id:{uuid.uuid4()}' or tgt
187 |
188 | data = self.request(
189 | 'chat/character/create/',
190 | token=token, data={
191 | 'title': title,
192 | 'name': name,
193 | 'identifier': tgt,
194 | 'visibility': visinility,
195 | 'copyable': copyable,
196 | 'description': description,
197 | 'greeting': greeting,
198 | 'definition': definition,
199 | 'avatar_rel_path': avatar_path,
200 | **kwargs
201 | }
202 | )
203 |
204 | return character.Character.model_validate(
205 | data['character']
206 | )
207 |
208 | @caimethod
209 | def update_char(
210 | self,
211 | char: str,
212 | greeting: str,
213 | name: str,
214 | *,
215 | title: str = '',
216 | visinility: str = 'PRIVATE',
217 | copyable: bool = True,
218 | description: str = '',
219 | definition: str = '',
220 | token: str = None,
221 | **kwargs
222 | ):
223 | """Editing a character
224 |
225 | EXAMPLE::
226 |
227 | await client.create_char('CHAR_ID', 'NAME', 'GREETING')
228 |
229 | Args:
230 | char (``str``):
231 | Character ID
232 |
233 | name (``str``, *optional*):
234 | Character name
235 |
236 | greeting (``str``, *optional*):
237 | Character greeting
238 |
239 | title (``str``, *optional*):
240 | Short description of the character
241 |
242 | visibility (``str``, *optional*):
243 | Character visibility
244 |
245 | copyable (``bool``, *optional*):
246 | Ability to copy a character
247 |
248 | description (``str``, *optional*):
249 | Character description
250 |
251 | definition (``str``, *optional*):
252 | Character definition (memory, chat examples)
253 |
254 | default_voice_id (``str``, *optional*):
255 | Voice ID for TTS
256 |
257 | voice_id (``str``, *optional*):
258 | Voice ID for TTS
259 |
260 | strip_img_prompt_from_msg (``bool``, *optional*):
261 | Remove the picture hint from the message.
262 | I guess that means the characters
263 | won't be able to see the pictures (?)
264 |
265 | base_img_prompt (``str``, *optional*):
266 | Default promt for pictures (?)
267 |
268 | img_gen_enabled (``bool``, *optional*):
269 | Can pictures be generated
270 |
271 | avatar_rel_path (``str``, *optional*):
272 | Path to the character's avatar on the c.ai server.
273 | Example: ``uploaded/2022/12/26/some_id.webp``
274 |
275 | categories (``list``, *optional*):
276 | Character сategories
277 |
278 | archived (``bool``, *optional*):
279 | Is the character archived
280 |
281 | Returns:
282 | :obj:`~characterai.types.character.Character`
283 | """
284 | info = self.request(
285 | 'chat/character/info/', token=token,
286 | data={'external_id': char}
287 | )['character']
288 |
289 | data = self.request(
290 | 'chat/character/update/',
291 | token=token, data={
292 | 'external_id': char or info['external_id']['external_id'],
293 | 'name': name or info['name'],
294 | 'greeting': greeting or info['greeting'],
295 | 'title': title or info['title'],
296 | 'visibility': visibility or info['visibility'],
297 | 'copyable': copyable or info['copyable'],
298 | 'description': description or info['description'],
299 | 'definition': definition or info['definition'],
300 | 'default_voice_id': default_voice_id or info['default_voice_id'],
301 | 'voice_id': voice_id or info['voice_id'],
302 | 'strip_img_prompt_from_msg': strip_img_prompt_from_msg \
303 | or info['strip_img_prompt_from_msg'],
304 | 'base_img_prompt': base_img_prompt or info['base_img_prompt'],
305 | 'img_gen_enabled': img_gen_enabled or info['img_gen_enabled'],
306 | 'avatar_rel_path': avatar_rel_path or info['avatar_file_name'],
307 | 'categories': categories or [],
308 | 'archived': archived or None
309 | }
310 | )
311 |
312 | return character.Character.model_validate(
313 | data['character']
314 | )
--------------------------------------------------------------------------------
/characterai/aiocai/methods/characters.py:
--------------------------------------------------------------------------------
1 | from .utils import caimethod, validate
2 | from ...types import character
3 |
4 | import uuid
5 |
6 | class Characters:
7 | @caimethod
8 | async def get_char(
9 | self, external_id: str,
10 | *, token: str = None
11 | ):
12 | """Get information about the character
13 |
14 | EXAMPLE::
15 |
16 | await client.get_char('ID')
17 |
18 | Args:
19 | external_id (``str``): Character ID
20 |
21 | Returns:
22 | :obj:`~characterai.types.character.Character`
23 | """
24 | data = await self.request(
25 | 'chat/character/info/', token=token,
26 | data={'external_id': external_id}
27 | )
28 |
29 | return character.Character.model_validate(
30 | data['character']
31 | )
32 |
33 | @caimethod
34 | async def upvoted(self, *, token: str = None):
35 | """The list of characters you have given a voice to
36 |
37 | EXAMPLE::
38 |
39 | await client.upvoted()
40 |
41 | Returns:
42 | List of :obj:`~characterai.types.character.CharShort`
43 | """
44 | data = await self.request(
45 | 'chat/user/characters/upvoted/',
46 | token=token
47 | )
48 |
49 | return validate(
50 | character.CharShort,
51 | data['characters']
52 | )
53 |
54 | @caimethod
55 | async def get_category(
56 | self, name: str = 'All',
57 | *, token: str = None
58 | ):
59 | """Get characters from categories
60 |
61 | EXAMPLE::
62 |
63 | await client.get_category()
64 |
65 | Args:
66 | name (``str``): Category name
67 |
68 | Returns:
69 | :obj:`~characterai.types.character.Categories` | List of :obj:`~characterai.types.character.CharShort`
70 | """
71 | data = await self.request(
72 | 'chat/curated_categories'
73 | '/characters/',
74 | )
75 |
76 | categories = data['characters_by_curated_category']
77 |
78 | if name != 'All':
79 | return character.CharShort.model_validate(
80 | categories[name]
81 | )
82 |
83 | return character.Categories.model_validate(
84 | categories
85 | )
86 |
87 | @caimethod
88 | async def get_recommended(
89 | self, *, token: str = None
90 | ):
91 | """Get a list of recommended characters
92 |
93 | EXAMPLE::
94 |
95 | await client.get_recommended()
96 |
97 | Returns:
98 | List of :obj:`~characterai.types.character.CharShort`
99 | """
100 | data = await self.request(
101 | 'chat/characters/trending/'
102 | )
103 |
104 | return validate(
105 | character.CharShort,
106 | data['trending_characters']
107 | )
108 |
109 | @caimethod
110 | async def get_trending(
111 | self, *, token: str = None
112 | ):
113 | """Get a list of trending characters
114 |
115 | EXAMPLE::
116 |
117 | await client.get_trending()
118 |
119 | Returns:
120 | List of :obj:`~characterai.types.character.CharShort`
121 | """
122 | data = await self.request(
123 | 'recommendation/v1/user',
124 | token=token, neo=True
125 | )
126 |
127 | return validate(
128 | character.CharShort,
129 | data['characters']
130 | )
131 |
132 | @caimethod
133 | async def create_char(
134 | self,
135 | name: str,
136 | greeting: str,
137 | *,
138 | tgt: str = None,
139 | title: str = '',
140 | visibility: str = 'PRIVATE',
141 | copyable: bool = True,
142 | description: str = '',
143 | definition: str = '',
144 | avatar_path: str = '',
145 | token: str = None,
146 | **kwargs
147 | ):
148 | """Create a character
149 |
150 | EXAMPLE::
151 |
152 | await client.create_char('NAME', 'GREETING')
153 |
154 | Args:
155 | name (``str``):
156 | Character name
157 |
158 | greeting (``str``):
159 | Character greeting
160 |
161 | tgt (``str``, *optional*):
162 | Old type Character ID
163 |
164 | title (``str``, *optional*):
165 | Short description of the character
166 |
167 | visibility (``str``, *optional*):
168 | Character visibility
169 |
170 | copyable (``bool``, *optional*):
171 | Ability to copy a character
172 |
173 | description (``str``, *optional*):
174 | Character description
175 |
176 | definition (``str``, *optional*:
177 | Character definition (memory, chat examples)
178 |
179 | avatar_path (``str``, *optional*):
180 | Path to the character's avatar on the c.ai server.
181 | Example: ``uploaded/2022/12/26/some_id.webp``
182 |
183 | Returns:
184 | :obj:`~characterai.types.character.Character`
185 | """
186 | tgt = f'id:{uuid.uuid4()}' or tgt
187 |
188 | data = await self.request(
189 | 'chat/character/create/',
190 | token=token, data={
191 | 'title': title,
192 | 'name': name,
193 | 'identifier': tgt,
194 | 'visibility': visibility,
195 | 'copyable': copyable,
196 | 'description': description,
197 | 'greeting': greeting,
198 | 'definition': definition,
199 | 'avatar_rel_path': avatar_path,
200 | **kwargs
201 | }
202 | )
203 |
204 | return character.Character.model_validate(
205 | data['character']
206 | )
207 |
208 | @caimethod
209 | async def update_char(
210 | self,
211 | char: str,
212 | *,
213 | greeting: str = '',
214 | name: str = '',
215 | title: str = '',
216 | visibility: str = 'PRIVATE',
217 | copyable: bool = True,
218 | description: str = '',
219 | definition: str = '',
220 | archived: bool = False,
221 | default_voice_id: str = '',
222 | voice_id: str = '',
223 | strip_img_prompt_from_msg: bool = False,
224 | base_img_prompt: str = '',
225 | img_gen_enabled: bool = False,
226 | avatar_rel_path: str = '',
227 | categories: list = [],
228 | token: str = None
229 | ):
230 | """Editing a character
231 |
232 | EXAMPLE::
233 |
234 | await client.create_char('CHAR_ID', 'NAME', 'GREETING')
235 |
236 | Args:
237 | char (``str``):
238 | Character ID
239 |
240 | name (``str``, *optional*):
241 | Character name
242 |
243 | greeting (``str``, *optional*):
244 | Character greeting
245 |
246 | title (``str``, *optional*):
247 | Short description of the character
248 |
249 | visibility (``str``, *optional*):
250 | Character visibility
251 |
252 | copyable (``bool``, *optional*):
253 | Ability to copy a character
254 |
255 | description (``str``, *optional*):
256 | Character description
257 |
258 | definition (``str``, *optional*):
259 | Character definition (memory, chat examples)
260 |
261 | default_voice_id (``str``, *optional*):
262 | Voice ID for TTS
263 |
264 | voice_id (``str``, *optional*):
265 | Voice ID for TTS
266 |
267 | strip_img_prompt_from_msg (``bool``, *optional*):
268 | Remove the picture hint from the message.
269 | I guess that means the characters
270 | won't be able to see the pictures (?)
271 |
272 | base_img_prompt (``str``, *optional*):
273 | Default promt for pictures (?)
274 |
275 | img_gen_enabled (``bool``, *optional*):
276 | Can pictures be generated
277 |
278 | avatar_rel_path (``str``, *optional*):
279 | Path to the character's avatar on the c.ai server.
280 | Example: ``uploaded/2022/12/26/some_id.webp``
281 |
282 | categories (``list``, *optional*):
283 | Character сategories
284 |
285 | archived (``bool``, *optional*):
286 | Is the character archived
287 |
288 | Returns:
289 | :obj:`~characterai.types.character.Character`
290 | """
291 | charInfo = await self.request(
292 | 'chat/character/info/', token=token,
293 | data={'external_id': char}
294 | )
295 |
296 | info = charInfo['character']
297 |
298 | data = await self.request(
299 | 'chat/character/update/',
300 | token=token, data={
301 | 'external_id': char or info['external_id']['external_id'],
302 | 'name': name or info['name'],
303 | 'greeting': greeting or info['greeting'],
304 | 'title': title or info['title'],
305 | 'visibility': visibility or info['visibility'],
306 | 'copyable': copyable or info['copyable'],
307 | 'description': description or info['description'],
308 | 'definition': definition or info['definition'],
309 | 'default_voice_id': default_voice_id or info['default_voice_id'],
310 | 'voice_id': voice_id or info['voice_id'],
311 | 'strip_img_prompt_from_msg': strip_img_prompt_from_msg \
312 | or info['strip_img_prompt_from_msg'],
313 | 'base_img_prompt': base_img_prompt or info['base_img_prompt'],
314 | 'img_gen_enabled': img_gen_enabled or info['img_gen_enabled'],
315 | 'avatar_rel_path': avatar_rel_path or info['avatar_file_name'],
316 | 'categories': categories or [],
317 | 'archived': archived or None
318 | }
319 | )
320 |
321 | return character.Character.model_validate(
322 | data['character']
323 | )
--------------------------------------------------------------------------------
/characterai/types/chat1.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, Field
2 | from datetime import datetime
3 | from typing import List, Optional
4 |
5 | from .other import Avatar
6 |
7 | class Replies(BaseModel):
8 | """Message info
9 |
10 | Parameters:
11 | text (``str``):
12 | Message text
13 |
14 | uuid (``str``):
15 | Message UUID
16 |
17 | id (``int``):
18 | Message ID
19 | """
20 | text: str
21 | uuid: str
22 | id: int
23 |
24 | class Participant(BaseModel):
25 | name: str
26 |
27 | class SrcChar(BaseModel, Avatar):
28 | """Character info in the message
29 |
30 | Parameters:
31 | name (``str``, *property*):
32 | Character name
33 |
34 | avatar_file_name (``str``):
35 | Path to the avatar on the server
36 |
37 | avatar (:obj:`~characterai.types.other.Avatar`):
38 | Avatar info
39 | """
40 | participant: Participant
41 | avatar_file_name: Optional[str] = None
42 |
43 | @property
44 | def name(self):
45 | return self.participant.name
46 |
47 | class Message(BaseModel):
48 | """Информация о сообщении типа chat1
49 |
50 | Parameters:
51 | replies (List of :obj:`~characterai.types.chat1.Replies`):
52 | Message object
53 |
54 | src_char (:obj:`~characterai.types.SrcChar`):
55 | Character info
56 |
57 | is_final_chunk (``bool``):
58 | Whether the message (chunk) is the last in generation
59 |
60 | last_user_msg_id (``int``):
61 | ID последнего сообщения пользователя
62 |
63 | last_user_msg_uuid (``str``):
64 | UUID of the last user message
65 |
66 | id (``int``, *property*):
67 | Message ID
68 |
69 | text (``str``, *property*):
70 | Message text
71 |
72 | uuid (``str``, *property*):
73 | Message UUID
74 |
75 | author (``str``, *property*):
76 | Author name
77 | """
78 | replies: List[Replies]
79 | src_char: SrcChar
80 | is_final_chunk: bool
81 | last_user_msg_id: int
82 | last_user_msg_uuid: str
83 |
84 | @property
85 | def id(self):
86 | return self.replies[0].id
87 |
88 | @property
89 | def text(self):
90 | return self.replies[0].text
91 |
92 | @property
93 | def uuid(self):
94 | return self.replies[0].uuid
95 |
96 | @property
97 | def author(self):
98 | return self.src_char.participant.name
99 |
100 | class UserAccount(BaseModel, Avatar):
101 | """Your account
102 |
103 | Parameters:
104 | name (``str``):
105 | Your name
106 |
107 | avatar_type (``str``):
108 | Avatar status (uploaded or not)
109 |
110 | onboarding_complete (``bool``):
111 | For pop-up banners (?)
112 |
113 | mobile_onboarding_complete (``int``, *optional*):
114 | For mobile pop-up banners (?)
115 |
116 | avatar_file_name (``str``):
117 | Path to the avatar on the server
118 |
119 | avatar (:obj:`~characterai.types.other.Avatar`):
120 | Avatar info
121 | """
122 | name: str
123 | avatar_type: str
124 | onboarding_complete: bool
125 | avatar_file_name: str
126 | mobile_onboarding_complete: int
127 |
128 | class User(BaseModel):
129 | """Object in chat
130 |
131 | Parameters:
132 | username (``str``):
133 | Your nickname or character ID
134 |
135 | id (``bool``):
136 | Object ID
137 |
138 | first_name (``str``):
139 | Your email or character name
140 |
141 | account (:obj:`~characterai.types.chat1.UserAccount`, *optional*):
142 | Your account information
143 |
144 | is_staff (``bool``):
145 | Is the object an employee of the service
146 | """
147 | username: str
148 | id: int
149 | first_name: str
150 | account: Optional[UserAccount] = None
151 | is_staff: bool
152 |
153 | class Participants(BaseModel):
154 | """Objects in chat
155 |
156 | Parameters:
157 | user (:obj:`~characterai.types.chat1.User`):
158 | Object information
159 |
160 | is_human (``bool``):
161 | Is a human
162 |
163 | name (``str``):
164 | Object name
165 |
166 | num_interactions (``int``):
167 | Total number of chats
168 | """
169 | user: User
170 | is_human: bool
171 | name: str
172 | num_interactions: int
173 |
174 | class Messages(BaseModel):
175 | """Сообщения в чате
176 |
177 | Parameters:
178 | deleted (``bool``):
179 | Has the message been deleted
180 |
181 | id (``int``):
182 | Message ID
183 |
184 | text (``str``):
185 | Message text
186 |
187 | image_prompt_text (``str``):
188 | Picture generation promt
189 |
190 | image_rel_path (``str``):
191 | URL path to the picture
192 |
193 | is_alternative (``str``):
194 | Is the message alternatively generated
195 |
196 | responsible_user__username (``str``):
197 | Nickname of the character's author
198 |
199 | src__is_human (``bool``):
200 | Is the message from the object a person
201 |
202 | src__name (``str``):
203 | Character name
204 |
205 | src__user__username (``str``):
206 | Nickname of the character's author
207 |
208 | src_char (:obj:`~characterai.types.SrcChar`):
209 | Character object
210 | """
211 | deleted: bool
212 | id: int = Field(validation_alias='id ')
213 | image_prompt_text: str
214 | image_rel_path: str
215 | is_alternative: bool
216 | responsible_user__username: str
217 | src__character__avatar_file_name: str
218 | src__is_human: bool
219 | src__name: str
220 | src__user__username: str
221 | src_char: SrcChar
222 | text: str
223 |
224 | class NewChat(BaseModel):
225 | """New chat info
226 |
227 | Parameters:
228 | title (``str``):
229 | Chat title (?)
230 |
231 | participants (List of :obj:`~characterai.types.chat1.Participants`):
232 | Objects in chat
233 |
234 | external_id (``str``):
235 | Chat ID
236 |
237 | last_interaction (:py:obj:`~datetime.datetime`):
238 | Date of last message
239 |
240 | created (:py:obj:`~datetime.datetime`):
241 | Chat creation date
242 |
243 | type (``str``):
244 | Chat type (chat or room)
245 |
246 | description (``str``):
247 | Chat description (?)
248 |
249 | speech (``str``):
250 | WebRTC voice
251 |
252 | status (``str``):
253 | Status of function execution
254 |
255 | has_more (``bool``):
256 | Are there any more chat messages
257 |
258 | messages (List of :obj:`~characterai.types.chat1.Messages`):
259 | Messages list
260 |
261 | id (``str``):
262 | Chat ID
263 |
264 | tgt (``str``, *property*):
265 | Old character ID type
266 | """
267 | title: str
268 | participants: List[Participants]
269 | external_id: str
270 | created: datetime
271 | last_interaction: datetime
272 | type: str
273 | description: str
274 | speech: str
275 | status: str
276 | has_more: bool
277 | messages: List[Messages]
278 |
279 | id: str = Field(
280 | validation_alias='external_id'
281 | )
282 |
283 | @property
284 | def tgt(self):
285 | return self.participants[1].user.username
286 |
287 | class Avatars(BaseModel, Avatar):
288 | name: str
289 |
290 | avatar_file_name: Optional[str] = \
291 | Field(validation_alias='user__account__avatar_file_name') or \
292 | Field(validation_alias='character__avatar_file_name')
293 |
294 | class ChatHistory(BaseModel):
295 | """Chat history
296 |
297 | Parameters:
298 | title (``str``):
299 | Chat name (for rooms)
300 |
301 | participants (List of :obj:`~characterai.types.chat1.Participants`):
302 | Objects in chat
303 |
304 | external_id (``str``):
305 | Chat ID
306 |
307 | last_interaction (:py:obj:`~datetime.datetime`):
308 | Date of last message
309 |
310 | created (:py:obj:`~datetime.datetime`):
311 | Chat creation date
312 |
313 | type (``str``):
314 | Chat type (chat or room)
315 |
316 | avatars (List of :obj:`~characterai.types.chat1.Avatars`):
317 | Avatars of objects in chat
318 |
319 | room_img_gen_enabled (``bool``):
320 | Can the pictures be generated
321 | """
322 | title: str
323 | participants: List[Participants]
324 | external_id: str
325 | created: datetime
326 | last_interaction: datetime
327 | type: str
328 | description: str
329 | avatars: List[Avatars]
330 | room_img_gen_enabled: bool
331 |
332 | class HisMessage(BaseModel):
333 | """Message in the chat history list
334 |
335 | Parameters:
336 | id (``str``):
337 | Message ID
338 |
339 | uuid (``str``):
340 | Message UUID
341 |
342 | text (``str``):
343 | Message text
344 |
345 | src (``str``):
346 | User message ID
347 |
348 | tgt (``str``):
349 | Old character ID type
350 |
351 | is_alternative (``bool``, *optional*):
352 | Is the message alternatively generated
353 |
354 | image_rel_path (``str``):
355 | Path to the picture, if available
356 |
357 | image_prompt_text (``str``):
358 | Promt for the generated image
359 |
360 | deleted (``bool``, *optional*):
361 | Has the message been deleted
362 |
363 | src__name (``str``):
364 | User name
365 |
366 | src__user__username (``str``):
367 | User nickname
368 |
369 | src__is_human (``bool``, *optional*):
370 | Is the user a human/account
371 |
372 | src__character__avatar_file_name (``str``, *optional*):
373 | URL link to avatar
374 |
375 | src_char (:obj:`~characterai.types.SrcChar`):
376 | Character info
377 |
378 | responsible_user__username (``str``, *optional*):
379 | I don't know what it is
380 | """
381 | id: int
382 | uuid: str
383 | text: str
384 | src: str
385 | tgt: str
386 | is_alternative: Optional[bool] = None
387 | image_rel_path: str
388 | image_prompt_text: str
389 | deleted: Optional[bool] = None
390 | src__name: str
391 | src__user__username: str
392 | src__is_human: Optional[bool] = None
393 | src__character__avatar_file_name: Optional[str] = None
394 | src_char: SrcChar
395 | responsible_user__username: Optional[str] = None
396 |
397 | class History(BaseModel):
398 | """Chat history info
399 |
400 | Parameters:
401 | external_id (``str``):
402 | Chat ID
403 |
404 | last_interaction (:py:obj:`~datetime.datetime`):
405 | Date of last message
406 |
407 | created (:py:obj:`~datetime.datetime`):
408 | Chat creation date
409 |
410 | msgs (List of :obj:`~characterai.types.chat1.HisMessage`):
411 | Messages list
412 | """
413 | external_id: str
414 | last_interaction: datetime
415 | created: datetime
416 | msgs: List[HisMessage]
417 |
418 | class HisMessages(BaseModel):
419 | """Chat message list
420 |
421 | Parameters:
422 | messages (List of :obj:`~characterai.types.chat1.HisMessage`):
423 | Messages
424 |
425 | next_page (``int``):
426 | Next page number
427 |
428 | has_more (``bool``):
429 | Is there any more messages
430 | """
431 | messages: List[HisMessage]
432 | next_page: int
433 | has_more: bool
434 |
435 | class Migrate(BaseModel):
436 | """Info about migrating from chat1 to chat2
437 |
438 | Parameters:
439 | id (``str``):
440 | New chat ID
441 |
442 | create_time (:py:obj:`~datetime.datetime`):
443 | Date of migration creation
444 |
445 | last_update (:py:obj:`~datetime.datetime`):
446 | Date of data update
447 |
448 | status (``str``):
449 | Migration status
450 |
451 | properties (``str``):
452 | Additional migration options (?)
453 | """
454 | id: str = Field(validation_alias='migrationId')
455 | create_time: datetime = Field(validation_alias='createTime')
456 | last_update: datetime = Field(validation_alias='lastUpdateTime')
457 | status: str
458 | properties: str
--------------------------------------------------------------------------------
/characterai/pycai/methods/chat2.py:
--------------------------------------------------------------------------------
1 | import json
2 | from websockets import exceptions
3 | from websockets.sync import client as websockets
4 | import uuid
5 |
6 | from .utils import Request, caimethod, validate
7 | from ...errors import ServerError
8 | from ...types import chat2
9 |
10 | class ChatV2(Request):
11 | def __init__(
12 | self, session = None,
13 | token: str = None
14 | ):
15 | self.session = session
16 | self.token = token
17 |
18 | @caimethod
19 | def get_histories(
20 | self, char: str, *,
21 | preview: int = 2, token: str = None
22 | ):
23 | """Get a list of your character's chat histories
24 |
25 | EXAMPLE::
26 |
27 | await client.get_chat('CHAR')
28 |
29 | Args:
30 | char (``str``):
31 | Character ID
32 |
33 | preview (``int``, *optional*):
34 | The number of recent messages
35 | that will be shown
36 |
37 | Returns:
38 | List of :obj:`~characterai.types.chat2.ChatData`
39 | """
40 | data = self.request(
41 | f'chats/?character_ids={char}'
42 | f'&num_preview_turns={preview}',
43 | token=token, neo=True
44 | )
45 |
46 | return validate(
47 | chat2.ChatData,
48 | data['chats']
49 | )
50 |
51 | @caimethod
52 | def get_history(
53 | self, chat_id: str, *,
54 | token: str = None
55 | ):
56 | """Get chat history
57 |
58 | EXAMPLE::
59 |
60 | await client.get_history('CHAT_ID')
61 |
62 | Args:
63 | chat_id (``str``):
64 | Chat ID
65 |
66 | Returns:
67 | :obj:`~characterai.types.chat2.History`
68 | """
69 | return chat2.History.model_validate(
70 | self.request(
71 | f'turns/{chat_id}/',
72 | token=token, neo=True
73 | )
74 | )
75 |
76 | @caimethod
77 | def get_chat(
78 | self, char: str, *,
79 | token: str = None
80 | ):
81 | """Get information about the last chat
82 |
83 | EXAMPLE::
84 |
85 | await client.get_chat('CHAR')
86 |
87 | Args:
88 | char (``str``):
89 | Character ID
90 |
91 | Returns:
92 | :obj:`~characterai.types.chat2.ChatData`
93 | """
94 | return chat2.ChatData.model_validate(
95 | (self.request(
96 | f'chats/recent/{char}',
97 | token=token, neo=True
98 | ))['chats'][0]
99 | )
100 |
101 | @caimethod
102 | def pin(
103 | self, pinned: bool, chat_id: str,
104 | turn_id: str, *, token: str = None
105 | ):
106 | """Pin chat messages
107 |
108 | This is to make sure characters
109 | don't forget certain things
110 | and they will always remember
111 | what's in the pinned posts
112 |
113 | EXAMPLE::
114 |
115 | await client.pin(PINNED, 'CHAT_ID', 'TURN_ID')
116 |
117 | Args:
118 | pinned (``bool``):
119 | ``True`` pin, ``False`` unpin
120 |
121 | chat_id (``str``):
122 | Chat ID
123 |
124 | turn_id (``str``):
125 | Message ID
126 |
127 | Returns:
128 | :obj:`~characterai.types.chat2.BotAnswer`
129 | """
130 | return chat2.BotAnswer.model_validate(
131 | (self.request(
132 | 'turn/pin', neo=True,
133 | token=token, data={
134 | 'is_pinned': pinned,
135 | 'turn_key': {
136 | 'chat_id': chat_id,
137 | 'turn_id': turn_id
138 | }
139 | }
140 | ))['turn']
141 | )
142 |
143 | def delete_message(
144 | self, chat_id: str, ids: list
145 | ) -> bool:
146 | """Deleting messages. Returns ``True`` on success
147 |
148 | EXAMPLE::
149 |
150 | await client.delete_message('CHAT_ID', ['UUID'])
151 |
152 | Args:
153 | chat_id (``str``):
154 | Chat ID
155 |
156 | ids (List of ``str``):
157 | List of message IDs to be deleted
158 |
159 | Returns:
160 | ``bool``
161 | """
162 | self.ws.send(json.dumps({
163 | 'command':'remove_turns',
164 | 'payload': {
165 | 'chat_id': chat_id,
166 | 'turn_ids': ids
167 | }
168 | }))
169 |
170 | response = json.loads(self.ws.recv())
171 |
172 | if response['command'] == 'neo_error':
173 | raise ServerError(response['comment'])
174 |
175 | return True
176 |
177 | def next_message(
178 | self, char: str, chat_id: str, turn_id: str,
179 | *, tts: bool = False, lang: str = 'English'
180 | ):
181 | """Generate an alternative answer
182 |
183 | EXAMPLE::
184 |
185 | await client.next_message(
186 | 'CHAR', 'CHAT_ID', 'MSG_ID'
187 | )
188 |
189 | Args:
190 | char (``str``):
191 | Character ID
192 |
193 | chat_id (``str``):
194 | Chat ID
195 |
196 | turn_id (``str``):
197 | Message ID
198 |
199 | tts (``bool``, *optional*):
200 | Generate audio for the message
201 |
202 | lang (``str``, *optional*):
203 | The language of your message.
204 | That's the language you're most
205 | likely to respond in.
206 |
207 | Returns:
208 | :obj:`~characterai.types.chat2.BotAnswer`
209 | """
210 | self.ws.send(json.dumps({
211 | 'command':'generate_turn_candidate',
212 | 'payload': {
213 | 'tts_enabled': tts,
214 | 'selected_language': lang,
215 | 'character_id': char,
216 | 'turn_key': {
217 | 'turn_id': turn_id,
218 | 'chat_id': chat_id
219 | }
220 | }
221 | }))
222 |
223 | while True:
224 | response = json.loads(self.ws.recv())
225 |
226 | try:
227 | turn = response['turn']
228 | except:
229 | raise ServerError(response['comment'])
230 |
231 | if not turn['author']['author_id'].isdigit():
232 | try:
233 | turn['candidates'][0]['is_final']
234 | except: ...
235 | else:
236 | return chat2.BotAnswer.model_validate(
237 | turn
238 | )
239 |
240 | def new_chat(
241 | self, char: str, creator_id: str,
242 | *, greeting: bool = True, chat_id: str = None
243 | ):
244 | """Editing the message text
245 |
246 | EXAMPLE::
247 |
248 | await client.new_chat('CHAR', 'CREATOR_ID')
249 |
250 | Args:
251 | char (``str``):
252 | Character ID
253 |
254 | creator_id (``str``):
255 | Your account ID. Can be found at
256 | :obj:`~characterai.aiocai.methods.chat2.ChatV2.get_me`
257 |
258 | greeting (``bool``, *optional*):
259 | If ``False``, the new chat will be
260 | without the character's first message
261 |
262 | chat_id (``str``, *optional*):
263 | You can specify your chat ID,
264 | it can be any ``str``
265 |
266 | Returns:
267 | :obj:`~characterai.types.chat2.BotAnswer`
268 | """
269 | chat_id = str(uuid.uuid4()) or chat_id
270 |
271 | if isinstance(creator_id, int):
272 | creator_id = str(creator_id)
273 |
274 | self.ws.send(json.dumps({
275 | 'command': 'create_chat',
276 | 'payload': {
277 | 'chat': {
278 | 'chat_id': chat_id,
279 | 'creator_id': creator_id,
280 | 'visibility': 'VISIBILITY_PRIVATE',
281 | 'character_id': char,
282 | 'type': 'TYPE_ONE_ON_ONE'
283 | },
284 | 'with_greeting': greeting
285 | }
286 | }))
287 |
288 | response = json.loads(self.ws.recv())
289 | try: response['chat']
290 | except KeyError:
291 | raise ServerError(response['comment'])
292 | else:
293 | answer = chat2.BotAnswer.model_validate(
294 | json.loads(
295 | self.ws.recv()
296 | )['turn']
297 | )
298 |
299 | response = chat2.ChatData.model_validate(
300 | response['chat']
301 | )
302 |
303 | return response, answer
304 |
305 | def send_message(
306 | self, char: str, chat_id: str, text: str,
307 | author: dict = {}, *, image: str = None,
308 | custom_id: str = None
309 | ):
310 | """Sending a message to chat
311 |
312 | EXAMPLE::
313 |
314 | await client.send_message('CHAR', 'CHAT_ID', 'TEXT')
315 |
316 | Args:
317 | char (``str``):
318 | Character ID
319 |
320 | chat_id (``str``):
321 | Chat ID
322 |
323 | text (``str``):
324 | Message text
325 |
326 | custom_id (``str``, *optional*):
327 | Its ID for the message, can be any ``str``
328 |
329 | image (``str``, *optional*):
330 | Attach image to message. This should
331 | be the URL path on the server
332 |
333 | Returns:
334 | :obj:`~characterai.types.chat2.BotAnswer`
335 | """
336 | turn_key = {
337 | 'chat_id': chat_id
338 | }
339 |
340 | if custom_id != None:
341 | turn_key['turn_id'] = custom_id
342 |
343 | message = {
344 | 'command': 'create_and_generate_turn',
345 | 'payload': {
346 | 'character_id': char,
347 | 'turn': {
348 | 'turn_key': turn_key,
349 | 'author': author,
350 | 'candidates': [
351 | {
352 | 'raw_content': text,
353 | 'tti_image_rel_path': image
354 | }
355 | ]
356 | }
357 | }
358 | }
359 |
360 | self.ws.send(json.dumps(message))
361 |
362 | while True:
363 | response = json.loads(self.ws.recv())
364 |
365 | try:
366 | turn = response['turn']
367 | except:
368 | raise ServerError(response['comment'])
369 |
370 | if not turn['author']['author_id'].isdigit():
371 | try:
372 | turn['candidates'][0]['is_final']
373 | except: ...
374 | else:
375 | return chat2.BotAnswer.model_validate(
376 | turn
377 | )
378 |
379 | def edit_message(
380 | self, chat_id: str, message_id: str,
381 | text: str, *, token: str = None
382 | ):
383 | """Edit the message text
384 |
385 | EXAMPLE::
386 |
387 | await client.edit_message('CHAT_ID', 'MSG_ID', 'TEXT')
388 |
389 | Args:
390 | chat_id (``str``):
391 | Chat ID
392 |
393 | message_id (``str``):
394 | Message ID
395 |
396 | text (``str``):
397 | New message text
398 |
399 | Returns:
400 | :obj:`~characterai.types.chat2.BotAnswer`
401 | """
402 | self.ws.send(json.dumps({
403 | 'command':'edit_turn_candidate',
404 | 'payload': {
405 | 'turn_key': {
406 | 'chat_id': chat_id,
407 | 'turn_id': message_id
408 | },
409 | 'new_candidate_raw_content': text
410 | }
411 | }))
412 |
413 | response = json.loads(self.ws.recv())
414 |
415 | try: response['turn']
416 | except KeyError:
417 | raise ServerError(response['comment'])
418 | else:
419 | return chat2.BotAnswer.model_validate(
420 | response['turn']
421 | )
422 |
423 | class WSConnect(ChatV2):
424 | def __init__(
425 | self, token: str = None,
426 | *, start: bool = True
427 | ):
428 | if not start:
429 | self.token = token
430 |
431 | def __enter__(self):
432 | return self
433 |
434 | def __exit__(self, *args):
435 | self.close()
436 |
437 | def _connect(self):
438 | cookie = f'HTTP_AUTHORIZATION="Token {self.token}"'
439 |
440 | try:
441 | self.ws = websockets.connect(
442 | 'wss://neo.character.ai/ws/',
443 | additional_headers={
444 | 'Cookie': cookie
445 | }
446 | )
447 | except exceptions.InvalidStatusCode as e:
448 | if e.status_code == 403:
449 | raise ServerError('Wrong token')
450 |
451 | return self
452 |
453 | def __call__(
454 | self, token: str = None,
455 | *, start: bool = True
456 | ):
457 | self.token = token or self.token
458 |
459 | return self._connect()
460 |
461 | def close(self):
462 | return self.ws.close()
463 |
--------------------------------------------------------------------------------
/characterai/aiocai/methods/chat2.py:
--------------------------------------------------------------------------------
1 | import json
2 | import websockets
3 | from websockets import exceptions
4 | import uuid
5 |
6 | from .utils import Request, caimethod, validate
7 | from ...errors import ServerError
8 | from ...types import chat2
9 |
10 | class ChatV2(Request):
11 | def __init__(
12 | self, session = None,
13 | token: str = None
14 | ):
15 | self.session = session
16 | self.token = token
17 |
18 | @caimethod
19 | async def get_histories(
20 | self, char: str, *,
21 | preview: int = 2, token: str = None
22 | ):
23 | """Get a list of your character's chat histories
24 |
25 | EXAMPLE::
26 |
27 | await client.get_chat('CHAR')
28 |
29 | Args:
30 | char (``str``):
31 | Character ID
32 |
33 | preview (``int``, *optional*):
34 | The number of recent messages
35 | that will be shown
36 |
37 | Returns:
38 | List of :obj:`~characterai.types.chat2.ChatData`
39 | """
40 | data = await self.request(
41 | f'chats/?character_ids={char}'
42 | f'&num_preview_turns={preview}',
43 | token=token, neo=True
44 | )
45 |
46 | return validate(
47 | chat2.ChatData,
48 | data['chats']
49 | )
50 |
51 | @caimethod
52 | async def get_history(
53 | self, chat_id: str, *,
54 | token: str = None
55 | ):
56 | """Get chat history
57 |
58 | EXAMPLE::
59 |
60 | await client.get_history('CHAT_ID')
61 |
62 | Args:
63 | chat_id (``str``):
64 | Chat ID
65 |
66 | Returns:
67 | :obj:`~characterai.types.chat2.History`
68 | """
69 | return chat2.History.model_validate(
70 | await self.request(
71 | f'turns/{chat_id}/',
72 | token=token, neo=True
73 | )
74 | )
75 |
76 | @caimethod
77 | async def get_chat(
78 | self, char: str, *,
79 | token: str = None
80 | ):
81 | """Get information about the last chat
82 |
83 | EXAMPLE::
84 |
85 | await client.get_chat('CHAR')
86 |
87 | Args:
88 | char (``str``):
89 | Character ID
90 |
91 | Returns:
92 | :obj:`~characterai.types.chat2.ChatData`
93 | """
94 | return chat2.ChatData.model_validate(
95 | (await self.request(
96 | f'chats/recent/{char}',
97 | token=token, neo=True
98 | ))['chats'][0]
99 | )
100 |
101 | @caimethod
102 | async def pin(
103 | self, pinned: bool, chat_id: str,
104 | turn_id: str, *, token: str = None
105 | ):
106 | """Pin chat messages
107 |
108 | This is to make sure characters
109 | don't forget certain things
110 | and they will always remember
111 | what's in the pinned posts
112 |
113 | EXAMPLE::
114 |
115 | await client.pin(PINNED, 'CHAT_ID', 'TURN_ID')
116 |
117 | Args:
118 | pinned (``bool``):
119 | ``True`` pin, ``False`` unpin
120 |
121 | chat_id (``str``):
122 | Chat ID
123 |
124 | turn_id (``str``):
125 | Message ID
126 |
127 | Returns:
128 | :obj:`~characterai.types.chat2.BotAnswer`
129 | """
130 | return chat2.BotAnswer.model_validate(
131 | (await self.request(
132 | 'turn/pin', neo=True,
133 | token=token, data={
134 | 'is_pinned': pinned,
135 | 'turn_key': {
136 | 'chat_id': chat_id,
137 | 'turn_id': turn_id
138 | }
139 | }
140 | ))['turn']
141 | )
142 |
143 | async def delete_message(
144 | self, chat_id: str, ids: list
145 | ) -> bool:
146 | """Deleting messages. Returns ``True`` on success
147 |
148 | EXAMPLE::
149 |
150 | await client.delete_message('CHAT_ID', ['UUID'])
151 |
152 | Args:
153 | chat_id (``str``):
154 | Chat ID
155 |
156 | ids (List of ``str``):
157 | List of message IDs to be deleted
158 |
159 | Returns:
160 | ``bool``
161 | """
162 | await self.ws.send(json.dumps({
163 | 'command':'remove_turns',
164 | 'payload': {
165 | 'chat_id': chat_id,
166 | 'turn_ids': ids
167 | }
168 | }))
169 |
170 | response = json.loads(await self.ws.recv())
171 |
172 | if response['command'] == 'neo_error':
173 | raise ServerError(response['comment'])
174 |
175 | return True
176 |
177 | async def next_message(
178 | self, char: str, chat_id: str, turn_id: str,
179 | *, tts: bool = False, lang: str = 'English'
180 | ):
181 | """Generate an alternative answer
182 |
183 | EXAMPLE::
184 |
185 | await client.next_message(
186 | 'CHAR', 'CHAT_ID', 'MSG_ID'
187 | )
188 |
189 | Args:
190 | char (``str``):
191 | Character ID
192 |
193 | chat_id (``str``):
194 | Chat ID
195 |
196 | turn_id (``str``):
197 | Message ID
198 |
199 | tts (``bool``, *optional*):
200 | Generate audio for the message
201 |
202 | lang (``str``, *optional*):
203 | The language of your message.
204 | That's the language you're most
205 | likely to respond in.
206 |
207 | Returns:
208 | :obj:`~characterai.types.chat2.BotAnswer`
209 | """
210 | await self.ws.send(json.dumps({
211 | 'command':'generate_turn_candidate',
212 | 'payload': {
213 | 'tts_enabled': tts,
214 | 'selected_language': lang,
215 | 'character_id': char,
216 | 'turn_key': {
217 | 'turn_id': turn_id,
218 | 'chat_id': chat_id
219 | }
220 | }
221 | }))
222 |
223 | while True:
224 | response = json.loads(await self.ws.recv())
225 |
226 | try:
227 | turn = response['turn']
228 | except:
229 | raise ServerError(response['comment'])
230 |
231 | if not turn['author']['author_id'].isdigit():
232 | try:
233 | turn['candidates'][0]['is_final']
234 | except: ...
235 | else:
236 | return chat2.BotAnswer.model_validate(
237 | turn
238 | )
239 |
240 | async def new_chat(
241 | self, char: str, creator_id: str,
242 | *, greeting: bool = True, chat_id: str = None
243 | ):
244 | """Editing the message text
245 |
246 | EXAMPLE::
247 |
248 | await client.new_chat('CHAR', 'CREATOR_ID')
249 |
250 | Args:
251 | char (``str``):
252 | Character ID
253 |
254 | creator_id (``str``):
255 | Your account ID. Can be found at
256 | :obj:`~characterai.aiocai.methods.chat2.ChatV2.get_me`
257 |
258 | greeting (``bool``, *optional*):
259 | If ``False``, the new chat will be
260 | without the character's first message
261 |
262 | chat_id (``str``, *optional*):
263 | You can specify your chat ID,
264 | it can be any ``str``
265 |
266 | Returns:
267 | :obj:`~characterai.types.chat2.BotAnswer`
268 | """
269 | chat_id = str(uuid.uuid4()) or chat_id
270 |
271 | if isinstance(creator_id, int):
272 | creator_id = str(creator_id)
273 |
274 | await self.ws.send(json.dumps({
275 | 'command': 'create_chat',
276 | 'payload': {
277 | 'chat': {
278 | 'chat_id': chat_id,
279 | 'creator_id': creator_id,
280 | 'visibility': 'VISIBILITY_PRIVATE',
281 | 'character_id': char,
282 | 'type': 'TYPE_ONE_ON_ONE'
283 | },
284 | 'with_greeting': greeting
285 | }
286 | }))
287 |
288 | response = json.loads(await self.ws.recv())
289 | try: response['chat']
290 | except KeyError:
291 | raise ServerError(response['comment'])
292 | else:
293 | answer = chat2.BotAnswer.model_validate(
294 | json.loads(
295 | await self.ws.recv()
296 | )['turn']
297 | )
298 |
299 | response = chat2.ChatData.model_validate(
300 | response['chat']
301 | )
302 |
303 | return response, answer
304 |
305 | async def send_message(
306 | self, char: str, chat_id: str, text: str,
307 | author: dict = {}, *, image: str = None,
308 | custom_id: str = None
309 | ):
310 | """Sending a message to chat
311 |
312 | EXAMPLE::
313 |
314 | await client.send_message('CHAR', 'CHAT_ID', 'TEXT')
315 |
316 | Args:
317 | char (``str``):
318 | Character ID
319 |
320 | chat_id (``str``):
321 | Chat ID
322 |
323 | text (``str``):
324 | Message text
325 |
326 | custom_id (``str``, *optional*):
327 | Its ID for the message, can be any ``str``
328 |
329 | image (``str``, *optional*):
330 | Attach image to message. This should
331 | be the URL path on the server
332 |
333 | Returns:
334 | :obj:`~characterai.types.chat2.BotAnswer`
335 | """
336 | turn_key = {
337 | 'chat_id': chat_id
338 | }
339 |
340 | if custom_id != None:
341 | turn_key['turn_id'] = custom_id
342 |
343 | message = {
344 | 'command': 'create_and_generate_turn',
345 | 'payload': {
346 | 'character_id': char,
347 | 'turn': {
348 | 'turn_key': turn_key,
349 | 'author': author,
350 | 'candidates': [
351 | {
352 | 'raw_content': text,
353 | 'tti_image_rel_path': image
354 | }
355 | ]
356 | }
357 | }
358 | }
359 |
360 | await self.ws.send(json.dumps(message))
361 |
362 | while True:
363 | response = json.loads(await self.ws.recv())
364 |
365 | try:
366 | turn = response['turn']
367 | except:
368 | raise ServerError(response['comment'])
369 |
370 | if not turn['author']['author_id'].isdigit():
371 | try:
372 | turn['candidates'][0]['is_final']
373 | except: ...
374 | else:
375 | return chat2.BotAnswer.model_validate(
376 | turn
377 | )
378 |
379 | async def edit_message(
380 | self, chat_id: str, message_id: str,
381 | text: str, *, token: str = None
382 | ):
383 | """Edit the message text
384 |
385 | EXAMPLE::
386 |
387 | await client.edit_message('CHAT_ID', 'MSG_ID', 'TEXT')
388 |
389 | Args:
390 | chat_id (``str``):
391 | Chat ID
392 |
393 | message_id (``str``):
394 | Message ID
395 |
396 | text (``str``):
397 | New message text
398 |
399 | Returns:
400 | :obj:`~characterai.types.chat2.BotAnswer`
401 | """
402 | await self.ws.send(json.dumps({
403 | 'command':'edit_turn_candidate',
404 | 'payload': {
405 | 'turn_key': {
406 | 'chat_id': chat_id,
407 | 'turn_id': message_id
408 | },
409 | 'new_candidate_raw_content': text
410 | }
411 | }))
412 |
413 | response = json.loads(await self.ws.recv())
414 |
415 | try: response['turn']
416 | except KeyError:
417 | raise ServerError(response['comment'])
418 | else:
419 | return chat2.BotAnswer.model_validate(
420 | response['turn']
421 | )
422 |
423 | class WSConnect(ChatV2):
424 | def __init__(
425 | self, token: str = None,
426 | *, start: bool = True
427 | ):
428 | if not start:
429 | self.token = token
430 |
431 | async def __call__(
432 | self, token: str = None,
433 | *, start: bool = True
434 | ):
435 | self.token = token or self.token
436 |
437 | if not start:
438 | return None
439 |
440 | return await self.__aenter__(self.token)
441 |
442 | async def __aenter__(
443 | self, token: str = None
444 | ):
445 | cookie = f'HTTP_AUTHORIZATION="Token {self.token}"'
446 | try:
447 | self.ws = await websockets.connect(
448 | 'wss://neo.character.ai/ws/',
449 | extra_headers={
450 | 'Cookie': cookie
451 | }
452 | )
453 | except exceptions.InvalidStatusCode as e:
454 | if e.status_code == 403:
455 | raise ServerError('Wrong token')
456 |
457 | return self
458 |
459 | async def __aexit__(self, *args):
460 | await self.close()
461 |
462 | async def close(self):
463 | return await self.ws.close()
--------------------------------------------------------------------------------